├── .gitignore ├── LICENSE ├── README.md ├── assets └── blue-white.png ├── docs ├── AuthError.html ├── MoneyButtonClient.html ├── RestError.html ├── auth-error.js.html ├── errors.js.html ├── fonts │ ├── OpenSans-Bold-webfont.eot │ ├── OpenSans-Bold-webfont.svg │ ├── OpenSans-Bold-webfont.woff │ ├── OpenSans-BoldItalic-webfont.eot │ ├── OpenSans-BoldItalic-webfont.svg │ ├── OpenSans-BoldItalic-webfont.woff │ ├── OpenSans-Italic-webfont.eot │ ├── OpenSans-Italic-webfont.svg │ ├── OpenSans-Italic-webfont.woff │ ├── OpenSans-Light-webfont.eot │ ├── OpenSans-Light-webfont.svg │ ├── OpenSans-Light-webfont.woff │ ├── OpenSans-LightItalic-webfont.eot │ ├── OpenSans-LightItalic-webfont.svg │ ├── OpenSans-LightItalic-webfont.woff │ ├── OpenSans-Regular-webfont.eot │ ├── OpenSans-Regular-webfont.svg │ └── OpenSans-Regular-webfont.woff ├── get-money-button-client.js.html ├── index.html ├── index.js.html ├── module.html#.exports ├── rest-error.js.html ├── scripts │ ├── linenumber.js │ └── prettify │ │ ├── Apache-License-2.0.txt │ │ ├── lang-css.js │ │ └── prettify.js └── styles │ ├── jsdoc-default.css │ ├── prettify-jsdoc.css │ └── prettify-tomorrow.css ├── package.json ├── rollup.config.js ├── src ├── auth-error.js ├── banner.js ├── config.js ├── get-money-button-client.js ├── index.browser.js ├── index.js └── rest-error.js └── test ├── babel.js ├── config.js ├── index.js ├── mocha.opts ├── resources ├── not-found.json └── user.json └── util.js /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Open BSV License 2 | Copyright (c) 2019 Yours Inc. 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 | 1 - The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 2 - The Software, and any software that is derived from the Software or parts thereof, 14 | can only be used on the Bitcoin SV blockchains. The Bitcoin SV blockchains are defined, 15 | for purposes of this license, as the Bitcoin blockchain containing block height #556767 16 | with the hash "000000000000000001d956714215d96ffc00e0afda4cd0a96c96f8d802b1662b" and 17 | the test blockchains that are supported by the un-modified Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @moneybutton/api-client 2 | -------------------------------------------------------------------------------- /assets/blue-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moneybutton/api-client/9d152aa94bedbd8b470ca3ec15336da3c2863fc6/assets/blue-white.png -------------------------------------------------------------------------------- /docs/AuthError.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Class: AuthError 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Class: AuthError

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 |
30 | 31 |

AuthError()

32 | 33 | 34 |
35 | 36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 |

new AuthError()

45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 |
Source:
91 |
94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 |
102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 |
122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 |
143 | 144 |
145 | 146 | 147 | 148 | 149 |
150 | 151 | 154 | 155 |
156 | 157 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /docs/RestError.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Class: RestError 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Class: RestError

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 |
30 | 31 |

RestError()

32 | 33 | 34 |
35 | 36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 |

new RestError()

45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 |
Source:
91 |
94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 |
102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 |
122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 |
143 | 144 |
145 | 146 | 147 | 148 | 149 |
150 | 151 | 154 | 155 |
156 | 157 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /docs/auth-error.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Source: auth-error.js 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Source: auth-error.js

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
/**
30 |  * Authentication API error.
31 |  */
32 | export default class AuthError {
33 |   /**
34 |    * @param {string} title - Error title.
35 |    * @param {string} detail - Error detail.
36 |    */
37 |   constructor (title, detail) {
38 |     this.title = title
39 |     this.detail = detail
40 |     this.message = detail !== undefined ? detail : title
41 |   }
42 | }
43 | 
44 |
45 |
46 | 47 | 48 | 49 | 50 |
51 | 52 | 55 | 56 |
57 | 58 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /docs/errors.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Source: errors.js 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Source: errors.js

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
/**
30 |  *
31 |  */
32 | export class AuthError {
33 |   constructor (title, detail) {
34 |     this.title = title
35 |     this.detail = detail
36 |     this.message = detail !== undefined ? detail : title
37 |   }
38 | }
39 | 
40 | /**
41 |  *
42 |  */
43 | export class RestError {
44 |   constructor (status, title, detail) {
45 |     this.status = status
46 |     this.title = title
47 |     this.detail = detail
48 |     this.message = detail !== undefined ? detail : title
49 |   }
50 | }
51 | 
52 |
53 |
54 | 55 | 56 | 57 | 58 |
59 | 60 | 63 | 64 |
65 | 66 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moneybutton/api-client/9d152aa94bedbd8b470ca3ec15336da3c2863fc6/docs/fonts/OpenSans-Bold-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moneybutton/api-client/9d152aa94bedbd8b470ca3ec15336da3c2863fc6/docs/fonts/OpenSans-Bold-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-BoldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moneybutton/api-client/9d152aa94bedbd8b470ca3ec15336da3c2863fc6/docs/fonts/OpenSans-BoldItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-BoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moneybutton/api-client/9d152aa94bedbd8b470ca3ec15336da3c2863fc6/docs/fonts/OpenSans-BoldItalic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moneybutton/api-client/9d152aa94bedbd8b470ca3ec15336da3c2863fc6/docs/fonts/OpenSans-Italic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moneybutton/api-client/9d152aa94bedbd8b470ca3ec15336da3c2863fc6/docs/fonts/OpenSans-Italic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moneybutton/api-client/9d152aa94bedbd8b470ca3ec15336da3c2863fc6/docs/fonts/OpenSans-Light-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moneybutton/api-client/9d152aa94bedbd8b470ca3ec15336da3c2863fc6/docs/fonts/OpenSans-Light-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-LightItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moneybutton/api-client/9d152aa94bedbd8b470ca3ec15336da3c2863fc6/docs/fonts/OpenSans-LightItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-LightItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moneybutton/api-client/9d152aa94bedbd8b470ca3ec15336da3c2863fc6/docs/fonts/OpenSans-LightItalic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moneybutton/api-client/9d152aa94bedbd8b470ca3ec15336da3c2863fc6/docs/fonts/OpenSans-Regular-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moneybutton/api-client/9d152aa94bedbd8b470ca3ec15336da3c2863fc6/docs/fonts/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /docs/get-money-button-client.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Source: get-money-button-client.js 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Source: get-money-button-client.js

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
import {
  30 |   toResourceObject,
  31 |   toNewResourceObject,
  32 |   fromResourceObject,
  33 |   fromResourceObjectsOfType,
  34 |   toJsonApiData,
  35 |   toJsonApiDataIncluding,
  36 |   fromJsonApiData,
  37 |   fromJsonApiDataIncluding
  38 | } from '@moneybutton/json-api'
  39 | import fetch from 'isomorphic-fetch'
  40 | import moment from 'moment'
  41 | import queryString from 'query-string'
  42 | import uuid from 'uuid'
  43 | 
  44 | import AuthError from './auth-error'
  45 | import config from './config'
  46 | import RestError from './rest-error'
  47 | 
  48 | const API_REST_URI = config.get('MONEY_BUTTON_API_REST_URI')
  49 | const API_AUTH_URI = config.get('MONEY_BUTTON_API_AUTH_URI')
  50 | 
  51 | const LOGIN_PASSWORD_HMAC_KEY = 'yours login password'
  52 | 
  53 | const STORAGE_NAMESPACE = 'mb_js_client'
  54 | const OAUTH_REDIRECT_URI_KEY = [STORAGE_NAMESPACE, 'oauth_redirect_uri'].join(':')
  55 | const OAUTH_STATE_KEY = [STORAGE_NAMESPACE, 'oauth_state'].join(':')
  56 | const OAUTH_ACCESS_TOKEN_KEY = [STORAGE_NAMESPACE, 'oauth_access_token'].join(':')
  57 | const OAUTH_EXPIRATION_TIME_KEY = [STORAGE_NAMESPACE, 'oauth_expiration_time'].join(':')
  58 | const OAUTH_REFRESH_TOKEN_KEY = [STORAGE_NAMESPACE, 'oauth_refresh_token'].join(':')
  59 | 
  60 | /**
  61 |  * @param {Storage} webStorage - Object conforming to the Storage Web API.
  62 |  * @param {Crypto} webCrypto - Object conforming to the Crypto Web API.
  63 |  * @param {Location} webLocation - Object conforming to the Location Web API.
  64 |  */
  65 | export default function getMoneyButtonClient (webStorage, webCrypto, webLocation) {
  66 |   if (!webStorage) {
  67 |     throw new Error('Missing required web storage object.')
  68 |   }
  69 |   if (!webCrypto || !webCrypto.subtle) {
  70 |     throw new Error('Missing required web crypto object.')
  71 |   }
  72 |   if (!webLocation) {
  73 |     throw new Error('Missing required web location object.')
  74 |   }
  75 |   /**
  76 |    *
  77 |    */
  78 |   class MoneyButtonClient {
  79 |     /**
  80 |      * Creates an instance of Money Button for the given OAuth client.
  81 |      *
  82 |      * @param {string} clientId - OAuth client's identifier.
  83 |      * @param {string} clientSecret - OAuth client's secret.
  84 |      */
  85 |     constructor (clientId, clientSecret = null) {
  86 |       this.clientId = clientId
  87 |       this.clientSecret = clientSecret
  88 |     }
  89 | 
  90 |     /**
  91 |      * Logs in the user with the given email and password.
  92 |      *
  93 |      * @param {string} email
  94 |      * @param {string} password
  95 |      * @returns {undefined}
  96 |      */
  97 |     async logIn (email, password) {
  98 |       if (await this.isLoggedIn()) {
  99 |         this.logOut()
 100 |       }
 101 |       const loginPassword = await MoneyButtonClient._computeHmac256(
 102 |         LOGIN_PASSWORD_HMAC_KEY,
 103 |         password
 104 |       )
 105 |       await this._doResourceOwnerPasswordCredentialsGrantAccessTokenRequest(
 106 |         email,
 107 |         loginPassword,
 108 |         'general_access:write'
 109 |       )
 110 |     }
 111 | 
 112 |     /**
 113 |      * Determines whether a user is currently logged-in.
 114 |      *
 115 |      * @returns {boolean}
 116 |      */
 117 |     async isLoggedIn () {
 118 |       const accessToken = await this.getValidAccessToken()
 119 |       return accessToken !== null
 120 |     }
 121 | 
 122 |     /**
 123 |      * Retrieves a valid access token for the currently logged-in user.
 124 |      * Returns null if no user is currently logged-in.
 125 |      *
 126 |      * @returns {string|null}
 127 |      */
 128 |     async getValidAccessToken () {
 129 |       let accessToken = this.getAccessToken()
 130 |       if (
 131 |         accessToken !== null &&
 132 |         moment().isBefore(moment(this.getExpirationTime()))
 133 |       ) {
 134 |         return accessToken
 135 |       }
 136 |       const refreshToken = this.getRefreshToken()
 137 |       if (refreshToken === null) {
 138 |         return null
 139 |       }
 140 |       accessToken = null
 141 |       try {
 142 |         await this._doRefreshAccessTokenRequest(refreshToken)
 143 |         accessToken = this.getAccessToken()
 144 |       } catch (err) {
 145 |         if (!(err instanceof AuthError)) {
 146 |           throw err
 147 |         }
 148 |       }
 149 |       return accessToken
 150 |     }
 151 | 
 152 |     /**
 153 |      * Logs out the current logged-in user, if any.
 154 |      */
 155 |     logOut () {
 156 |       this.clearAccessToken()
 157 |       this.clearExpirationTime()
 158 |       this.clearRefreshToken()
 159 |     }
 160 | 
 161 |     /**
 162 |      * Finishes the email verification process with the access token generated
 163 |      * during signup.
 164 |      *
 165 |      * @param {string} accessToken - auth API access token
 166 |      * @returns {object}
 167 |      */
 168 |     async verifyEmail (accessToken) {
 169 |       const json = await this._doPostRequest(
 170 |         '/v1/auth/email_verification',
 171 |         {},
 172 |         {},
 173 |         accessToken
 174 |       )
 175 |       return fromResourceObject(fromJsonApiData(json), 'email_verifications')
 176 |     }
 177 | 
 178 |     /**
 179 |      * Retrieves the currently logged user's identity.
 180 |      *
 181 |      * @returns {object}
 182 |      */
 183 |     async getIdentity () {
 184 |       const json = await this._doGetRequest('/v1/auth/user_identity')
 185 |       return fromResourceObject(fromJsonApiData(json), 'user_identities')
 186 |     }
 187 | 
 188 |     /**
 189 |      * Returns an object with two keys:
 190 |      * - loggedIn: a boolean indicating whether there is a currently logged-in user.
 191 |      * - user: if loggedIn is true, this is an object with the user's attributes.
 192 |      *
 193 |      * @returns {object}
 194 |      */
 195 |     async whoAmI () {
 196 |       const loggedIn = await this.isLoggedIn()
 197 |       if (!loggedIn) {
 198 |         return { loggedIn }
 199 |       }
 200 |       const { id } = await this.getIdentity()
 201 |       const user = await this.getUser(id)
 202 |       return { loggedIn, user }
 203 |     }
 204 | 
 205 |     /**
 206 |      * Changes the currently logged-in user's password.
 207 |      *
 208 |      * @param {string} password
 209 |      * @param {string} encryptedMnemonic
 210 |      * @param {string} xpub
 211 |      * @param {string} language
 212 |      * @returns {object}
 213 |      */
 214 |     async changePassword (
 215 |       password,
 216 |       encryptedMnemonic,
 217 |       xpub,
 218 |       language
 219 |     ) {
 220 |       const loginPassword = await MoneyButtonClient._computeHmac256(
 221 |         LOGIN_PASSWORD_HMAC_KEY,
 222 |         password
 223 |       )
 224 |       const body = toJsonApiDataIncluding(
 225 |         toNewResourceObject('users', {
 226 |           password: loginPassword
 227 |         }),
 228 |         [
 229 |           toNewResourceObject('wallets', {
 230 |             encryptedMnemonic,
 231 |             xpub,
 232 |             language
 233 |           })
 234 |         ]
 235 |       )
 236 |       const json = await this._doPostRequest('/v1/auth/password_change', body)
 237 |       return fromResourceObject(fromJsonApiData(json), 'password_changes')
 238 |     }
 239 | 
 240 |     /**
 241 |      * Resets the currently logged-in user's password by using the access token
 242 |      * generated during the "I forgot my password" flow.
 243 |      *
 244 |      * @param {string} accessToken - auth API access token
 245 |      * @param {string} password
 246 |      * @param {string} encryptedMnemonic
 247 |      * @param {string} xpub
 248 |      * @param {boolean} forceCreate
 249 |      * @param {string} walletLanguage
 250 |      * @returns {object}
 251 |      */
 252 |     async resetPassword (
 253 |       accessToken,
 254 |       password,
 255 |       encryptedMnemonic,
 256 |       xpub,
 257 |       forceCreate,
 258 |       walletLanguage
 259 |     ) {
 260 |       const loginPassword = await MoneyButtonClient._computeHmac256(
 261 |         LOGIN_PASSWORD_HMAC_KEY,
 262 |         password
 263 |       )
 264 |       const body = toJsonApiDataIncluding(
 265 |         toNewResourceObject('users', {
 266 |           password: loginPassword
 267 |         }),
 268 |         [
 269 |           toNewResourceObject('wallets', {
 270 |             encryptedMnemonic,
 271 |             xpub,
 272 |             language: walletLanguage
 273 |           })
 274 |         ]
 275 |       )
 276 |       const query = forceCreate ? { forceCreate: 'true' } : {}
 277 |       const json = await this._doPostRequest(
 278 |         '/v1/auth/password_reset',
 279 |         body,
 280 |         query,
 281 |         accessToken
 282 |       )
 283 |       return fromResourceObject(fromJsonApiData(json), 'password_resets')
 284 |     }
 285 | 
 286 |     /**
 287 |      * Sends a password reset email to begin the "I forgot my password" flow.
 288 |      *
 289 |      * @param {string} email
 290 |      * @returns {object}
 291 |      */
 292 |     async sendPasswordReset (email) {
 293 |       if (await this.isLoggedIn()) {
 294 |         this.logOut()
 295 |       }
 296 |       await this._doClientCredentialsGrantAccessTokenRequest(
 297 |         'auth.password_reset_email:write'
 298 |       )
 299 |       const attributes = { email }
 300 |       const body = toJsonApiData(toNewResourceObject('users', attributes))
 301 |       const json = await this._doPostRequest('/v1/auth/password_reset_email', body)
 302 |       this.logOut()
 303 |       return fromResourceObject(fromJsonApiData(json), 'password_reset_emails')
 304 |     }
 305 | 
 306 |     /**
 307 |      * Creates a new user account with the given email and password.
 308 |      *
 309 |      * @param {string} email
 310 |      * @param {string} password
 311 |      * @returns {object}
 312 |      */
 313 |     async signUp (email, password) {
 314 |       if (await this.isLoggedIn()) {
 315 |         this.logOut()
 316 |       }
 317 |       await this._doClientCredentialsGrantAccessTokenRequest(
 318 |         'auth.signup:write'
 319 |       )
 320 |       const loginPassword = await MoneyButtonClient._computeHmac256(
 321 |         LOGIN_PASSWORD_HMAC_KEY,
 322 |         password
 323 |       )
 324 |       const attributes = {
 325 |         email,
 326 |         password: loginPassword
 327 |       }
 328 |       const body = toJsonApiData(toNewResourceObject('users', attributes))
 329 |       const json = await this._doPostRequest('/v1/auth/signup', body)
 330 |       await this.logIn(email, password)
 331 |       return fromResourceObject(fromJsonApiData(json), 'signups')
 332 |     }
 333 | 
 334 |     /**
 335 |      * [Browser only] Starts the authorization flow which allows third-party applications
 336 |      * to request access to user resources on their behalf. This function will
 337 |      * redirect the user's window to the Money Button's authorization flow page.
 338 |      *
 339 |      * @param {string} scope - scope to be requested to the user.
 340 |      * @param {string} redirectUri - URI where the authorization response will be handled.
 341 |      * @returns {undefined}
 342 |      */
 343 |     requestAuthorization (
 344 |       scope,
 345 |       redirectUri
 346 |     ) {
 347 |       if (typeof scope !== 'string' || scope.length === 0) {
 348 |         throw new Error(`Invalid scope requested: ${scope}.`)
 349 |       }
 350 |       if (typeof redirectUri !== 'string' || redirectUri.length === 0) {
 351 |         throw new Error(`Invalid return URI: ${redirectUri}.`)
 352 |       }
 353 |       this._doAuthorizationCodeGrantAuthorizationRequest(redirectUri, scope)
 354 |     }
 355 | 
 356 |     /**
 357 |      * [Browser only] Finishes the authorization flow started by {@link requestAuthorization}.
 358 |      * If successful, after calling this function, the client will be able to perform requests
 359 |      * on behalf of the user as long as they are within the scope requested when starting the
 360 |      * authorization flow.
 361 |      *
 362 |      * @returns {undefined}
 363 |      */
 364 |     async handleAuthorizationResponse () {
 365 |       const { error, code, state } = this._getUrlQuery()
 366 |       await this._handleAuthorizationCodeGrantAuthorizationResponse(error, code, state)
 367 |     }
 368 | 
 369 |     /**
 370 |      * See: https://tools.ietf.org/html/rfc6749#page-24.
 371 |      *
 372 |      * @private
 373 |      * @param {string} redirectUri
 374 |      * @param {string} scope
 375 |      */
 376 |     _doAuthorizationCodeGrantAuthorizationRequest (
 377 |       redirectUri,
 378 |       scope
 379 |     ) {
 380 |       if (this.clientSecret !== null) {
 381 |         throw new Error([
 382 |           'Grant `authentication_code` can only be performed by ',
 383 |           'a public client (that is, a client with no client secret).'
 384 |         ].join(''))
 385 |       }
 386 |       const state = uuid.v4()
 387 |       this._setRedirectUri(redirectUri)
 388 |       this._setState(state)
 389 |       const authorizationUri = [
 390 |         `${API_AUTH_URI}/oauth/v1/authorize`,
 391 |         queryString.stringify({
 392 |           response_type: 'code',
 393 |           client_id: this.clientId,
 394 |           redirect_uri: redirectUri,
 395 |           scope,
 396 |           state
 397 |         })
 398 |       ].join('?')
 399 |       this._redirectToUri(authorizationUri)
 400 |     }
 401 | 
 402 |     /**
 403 |      * See: https://tools.ietf.org/html/rfc6749#page-26.
 404 |      *
 405 |      * @private
 406 |      */
 407 |     async _handleAuthorizationCodeGrantAuthorizationResponse (
 408 |       error,
 409 |       code,
 410 |       state
 411 |     ) {
 412 |       const expectedState = this._getState()
 413 |       if (expectedState === null || state !== expectedState) {
 414 |         throw new Error('Invalid OAuth state.')
 415 |       }
 416 |       if (error !== undefined) {
 417 |         throw new AuthError('Authorization failed.', error)
 418 |       }
 419 |       if (code === undefined) {
 420 |         throw new Error('Missing OAuth authorization code.')
 421 |       }
 422 |       await this._doAuthorizationCodeGrantAccessTokenRequest(code)
 423 |     }
 424 | 
 425 |     /**
 426 |      * See: https://tools.ietf.org/html/rfc6749#page-29.
 427 |      *
 428 |      * @private
 429 |      */
 430 |     async _doAuthorizationCodeGrantAccessTokenRequest (
 431 |       code
 432 |     ) {
 433 |       const redirectUri = this._getRedirectUri()
 434 |       if (redirectUri === null) {
 435 |         throw new Error('Required OAuth redirect URI not found in storage.')
 436 |       }
 437 |       await this._doAccessTokenRequest(
 438 |         {
 439 |           grant_type: 'authorization_code',
 440 |           code,
 441 |           redirect_uri: redirectUri,
 442 |           client_id: this.clientId
 443 |         }
 444 |       )
 445 |     }
 446 | 
 447 |     /**
 448 |      * See: https://tools.ietf.org/html/rfc6749#page-37.
 449 |      *
 450 |      * @private
 451 |      */
 452 |     async _doResourceOwnerPasswordCredentialsGrantAccessTokenRequest (
 453 |       username,
 454 |       password,
 455 |       scope
 456 |     ) {
 457 |       await this._doAccessTokenRequest(
 458 |         {
 459 |           grant_type: 'password',
 460 |           username,
 461 |           password,
 462 |           scope
 463 |         },
 464 |         this._buildBasicAuthHeaders()
 465 |       )
 466 |     }
 467 | 
 468 |     /**
 469 |      * See: https://tools.ietf.org/html/rfc6749#page-41.
 470 |      *
 471 |      * @private
 472 |      */
 473 |     async _doClientCredentialsGrantAccessTokenRequest (scope) {
 474 |       await this._doAccessTokenRequest(
 475 |         {
 476 |           grant_type: 'client_credentials',
 477 |           scope
 478 |         },
 479 |         this._buildBasicAuthHeaders()
 480 |       )
 481 |     }
 482 | 
 483 |     /**
 484 |      * @private
 485 |      * @param {string} refreshToken
 486 |      */
 487 |     async _doRefreshAccessTokenRequest (refreshToken) {
 488 |       await this._doAccessTokenRequest(
 489 |         {
 490 |           grant_type: 'refresh_token',
 491 |           refresh_token: refreshToken
 492 |         },
 493 |         this._buildBasicAuthHeaders()
 494 |       )
 495 |     }
 496 | 
 497 |     /**
 498 |      * @private
 499 |      */
 500 |     _buildBasicAuthHeaders () {
 501 |       const credentials = `${this.clientId}:${this.clientSecret}`
 502 |       return {
 503 |         Authorization: `Basic ${Buffer.from(credentials).toString('base64')}`
 504 |       }
 505 |     }
 506 | 
 507 |     /**
 508 |      * @private
 509 |      * @param {object} body
 510 |      * @param {object} headers
 511 |      */
 512 |     async _doAccessTokenRequest (body = {}, headers = {}) {
 513 |       const res = await fetch(
 514 |         `${API_AUTH_URI}/oauth/v1/token`,
 515 |         {
 516 |           method: 'POST',
 517 |           body: queryString.stringify(body),
 518 |           headers: {
 519 |             ...headers,
 520 |             'Content-Type': 'application/x-www-form-urlencoded'
 521 |           }
 522 |         }
 523 |       )
 524 |       await this._handleAccessTokenResponse(res)
 525 |     }
 526 | 
 527 |     /**
 528 |      * @private
 529 |      * @param {Response} res - Express.js response object.
 530 |      */
 531 |     async _handleAccessTokenResponse (res) {
 532 |       const {
 533 |         error,
 534 |         error_description: errorDescription,
 535 |         access_token: accessToken,
 536 |         token_type: tokenType,
 537 |         expires_in: expiresIn,
 538 |         refresh_token: refreshToken
 539 |       } = await res.json()
 540 |       if (error !== undefined) {
 541 |         throw new AuthError(error, errorDescription)
 542 |       }
 543 |       if (tokenType !== 'Bearer') {
 544 |         throw new Error('Unexpected token type.')
 545 |       }
 546 |       if (accessToken !== undefined) {
 547 |         this.setAccessToken(accessToken)
 548 |       } else {
 549 |         this.clearAccessToken()
 550 |       }
 551 |       if (expiresIn !== undefined) {
 552 |         const expirationTime = moment().add(expiresIn, 'seconds')
 553 |         this.setExpirationTime(expirationTime.format())
 554 |       } else {
 555 |         this.clearExpirationTime()
 556 |       }
 557 |       if (refreshToken !== undefined) {
 558 |         this.setRefreshToken(refreshToken)
 559 |       } else {
 560 |         this.clearRefreshToken()
 561 |       }
 562 |     }
 563 | 
 564 |     /**
 565 |      * Get basic information from the OAuth client with the given identifier.
 566 |      *
 567 |      * @param {string} clientIdentifier
 568 |      * @returns {object}
 569 |      */
 570 |     async getClientByIdentifier (clientIdentifier) {
 571 |       const json = await this._doGetRequest(`/v1/clients/client_identifier=${clientIdentifier}`)
 572 |       return fromResourceObject(fromJsonApiData(json), 'clients')
 573 |     }
 574 | 
 575 |     /**
 576 |      * Retrives the user with the given user id.
 577 |      *
 578 |      * @param {string} userId
 579 |      * @returns {object}
 580 |      */
 581 |     async getUser (userId) {
 582 |       let json = await this._doGetRequest(`/v1/users/${userId}`)
 583 |       return fromResourceObject(fromJsonApiData(json), 'users')
 584 |     }
 585 | 
 586 |     /**
 587 |      * Updates the user with the given user id.
 588 |      *
 589 |      * @param {string} userId
 590 |      * @param {object} attributes
 591 |      * @returns {object}
 592 |      */
 593 |     async updateUser (userId, attributes = {}) {
 594 |       const body = toJsonApiData(toResourceObject(userId, 'users', attributes))
 595 |       const json = await this._doPatchRequest(`/v1/users/${userId}`, body)
 596 |       return fromResourceObject(fromJsonApiData(json), 'users')
 597 |     }
 598 | 
 599 |     /**
 600 |      * Retrives the transaction history of the user with the given user id.
 601 |      *
 602 |      * @param {string} userId
 603 |      * @param {object} query
 604 |      * @returns {object}
 605 |      */
 606 |     async getUserTransactionHistory (userId, query = {}) {
 607 |       const json = await this._doGetRequest(
 608 |         `/v1/users/${userId}/transaction_history`,
 609 |         query
 610 |       )
 611 |       return fromResourceObjectsOfType(
 612 |         fromJsonApiData(json),
 613 |         'transaction_history'
 614 |       )
 615 |     }
 616 | 
 617 |     /**
 618 |      * Retrives the OAuth clients of the user with the given user id.
 619 |      *
 620 |      * @param {string} userId
 621 |      * @param {object} query
 622 |      * @returns {object}
 623 |      */
 624 |     async getUserClients (userId, query = {}) {
 625 |       const json = await this._doGetRequest(
 626 |         `/v1/users/${userId}/clients`,
 627 |         query
 628 |       )
 629 |       return fromResourceObjectsOfType(fromJsonApiData(json), 'clients')
 630 |     }
 631 | 
 632 |     /**
 633 |      * Creates an OAuth client for the user with the given user id.
 634 |      *
 635 |      * @param {string} userId
 636 |      * @param {object} attributes
 637 |      * @returns {object}
 638 |      */
 639 |     async createUserClient (userId, attributes) {
 640 |       let body = toJsonApiData(toNewResourceObject('clients', attributes))
 641 |       const json = await this._doPostRequest(`/v1/users/${userId}/clients`, body)
 642 |       return fromResourceObject(fromJsonApiData(json), 'clients')
 643 |     }
 644 | 
 645 |     /**
 646 |      * Updates an OAuth client for the user with the given user id.
 647 |      *
 648 |      * @param {string} userId
 649 |      * @param {string} clientId
 650 |      * @param {object} attributes
 651 |      * @returns {object}
 652 |      */
 653 |     async updateUserClient (userId, clientId, attributes = {}) {
 654 |       const body = toJsonApiData(
 655 |         toResourceObject(clientId, 'clients', attributes)
 656 |       )
 657 |       await this._doPatchRequest(
 658 |         `/v1/users/${userId}/clients/${clientId}`,
 659 |         body
 660 |       )
 661 |     }
 662 | 
 663 |     /**
 664 |      * Retrives the handles of the user with the given user id.
 665 |      *
 666 |      * @param {string} userId
 667 |      * @param {object} query
 668 |      * @returns {object}
 669 |      */
 670 |     async getUserHandles (userId, query = {}) {
 671 |       const json = await this._doGetRequest(
 672 |         `/v1/users/${userId}/handles`,
 673 |         query
 674 |       )
 675 |       return fromResourceObjectsOfType(fromJsonApiData(json), 'handles')
 676 |     }
 677 | 
 678 |     /**
 679 |      * Creates a handle for the user with the given user id.
 680 |      *
 681 |      * @param {string} userId
 682 |      * @param {object} attributes
 683 |      * @returns {object}
 684 |      */
 685 |     async createUserHandle (userId, attributes) {
 686 |       let body = toJsonApiData(toNewResourceObject('handles', attributes))
 687 |       const json = await this._doPostRequest(`/v1/users/${userId}/handles`, body)
 688 |       return fromResourceObject(fromJsonApiData(json), 'handles')
 689 |     }
 690 | 
 691 |     /**
 692 |      * Retrives the wallet with the given wallet id for the user with
 693 |      * the given user id.
 694 |      *
 695 |      * @param {string} userId
 696 |      * @param {string} walletId
 697 |      * @returns {object}
 698 |      */
 699 |     async getUserWallet (userId, walletId) {
 700 |       let json = await this._doGetRequest(
 701 |         `/v1/users/${userId}/wallets/${walletId}`
 702 |       )
 703 |       return fromResourceObject(fromJsonApiData(json), 'wallets')
 704 |     }
 705 | 
 706 |     /**
 707 |      * Retrives the wallets of the user with the given user id.
 708 |      *
 709 |      * @param {string} userId
 710 |      * @returns {object}
 711 |      */
 712 |     async getUserWallets (userId) {
 713 |       let json = await this._doGetRequest(`/v1/users/${userId}/wallets/`)
 714 |       return fromResourceObjectsOfType(fromJsonApiData(json), 'wallets')
 715 |     }
 716 | 
 717 |     /**
 718 |      * Retrives the max withdrawal amount for the wallet with the given wallet id,
 719 |      * belonging to the user with the given user id.
 720 |      *
 721 |      * @param {string} userId
 722 |      * @param {string} walletId
 723 |      * @returns {object}
 724 |      */
 725 |     async getMaxWithdrawalForWallet (userId, walletId) {
 726 |       let json = await this._doGetRequest(`/v1/users/${userId}/wallets/${walletId}/max_withdrawal`)
 727 |       return fromResourceObject(fromJsonApiData(json), 'amounts')
 728 |     }
 729 | 
 730 |     /**
 731 |      * Creates a wallet for the user with the given user id.
 732 |      *
 733 |      * @param {string} userId
 734 |      * @param {object} attributes
 735 |      * @returns {object}
 736 |      */
 737 |     async createUserWallet (userId, attributes) {
 738 |       let body = toJsonApiData(toNewResourceObject('wallets', attributes))
 739 |       let json = await this._doPostRequest(`/v1/users/${userId}/wallets`, body)
 740 |       return fromResourceObject(fromJsonApiData(json), 'wallets')
 741 |     }
 742 | 
 743 |     /**
 744 |      * Retrieves the balance from the user with given user id.
 745 |      *
 746 |      * @param {string} userId
 747 |      * @returns {object}
 748 |      */
 749 |     async getBalance (userId) {
 750 |       const json = await this._doGetRequest(`/v1/users/${userId}/balance`)
 751 |       return fromResourceObject(fromJsonApiData(json), 'amounts')
 752 |     }
 753 | 
 754 |     /**
 755 |      * Retrives the max withdrawal amount the user with the given user id.
 756 |      *
 757 |      * @param {string} userId
 758 |      * @returns {object}
 759 |      */
 760 |     async getMaxWithdrawal (userId) {
 761 |       let json = await this._doGetRequest(`/v1/users/${userId}/max_withdrawal`)
 762 |       return fromResourceObject(fromJsonApiData(json), 'amounts')
 763 |     }
 764 | 
 765 |     /**
 766 |      * Retrives a recieve address for the user with the given user id.
 767 |      *
 768 |      * @param {string} userId
 769 |      * @param {string} walletId
 770 |      * @returns {object}
 771 |      */
 772 |     async getReceiveAddress (userId, walletId) {
 773 |       let json = await this._doPostRequest(
 774 |         `/v1/users/${userId}/wallets/${walletId}/receive_address`
 775 |       )
 776 |       let { address } = fromResourceObject(fromJsonApiData(json), 'addresses')
 777 |       return address
 778 |     }
 779 | 
 780 |     /**
 781 |      * Converts a (curreny,amount) pair into the given user's default currency.
 782 |      *
 783 |      * @param {string} userId
 784 |      * @param {object} attributes
 785 |      * @returns {object}
 786 |      */
 787 |     async getCurrencyAmount (userId, attributes) {
 788 |       const body = toJsonApiData(toNewResourceObject('currency', attributes))
 789 |       const json = await this._doPostRequest(
 790 |         `/v1/users/${userId}/currency`,
 791 |         body
 792 |       )
 793 |       const { amount, currency } = fromResourceObject(
 794 |         fromJsonApiData(json),
 795 |         'currency'
 796 |       )
 797 |       return { amount, currency }
 798 |     }
 799 | 
 800 |     /**
 801 |      * Retrives the balance for the wallet with the given wallet id,
 802 |      * belonging to the user with the given user id.
 803 |      *
 804 |      * @param {string} userId
 805 |      * @param {string} walletId
 806 |      * @returns {object}
 807 |      */
 808 |     async getWalletBalance (userId, walletId) {
 809 |       const json = await this._doGetRequest(
 810 |         `/v1/users/${userId}/wallets/${walletId}/balance`
 811 |       )
 812 |       return fromResourceObject(fromJsonApiData(json), 'amounts')
 813 |     }
 814 | 
 815 |     /**
 816 |      * Updates the wallet with the given wallet id, belonging to the user
 817 |      * with the given user id.
 818 |      *
 819 |      * @param {string} userId
 820 |      * @param {string} walletId
 821 |      * @param {object} attributes
 822 |      * @returns {object}
 823 |      */
 824 |     async updateWallet (userId, walletId, attributes) {
 825 |       const body = toJsonApiData(
 826 |         toResourceObject(walletId, 'wallets', attributes)
 827 |       )
 828 |       await this._doPatchRequest(
 829 |         `/v1/users/${userId}/wallets/${walletId}`,
 830 |         body
 831 |       )
 832 |     }
 833 | 
 834 |     /**
 835 |      * Retrieves the payments from the user with the given user id.
 836 |      *
 837 |      * @param {string} userId
 838 |      * @param {object} paginate
 839 |      * @returns {object}
 840 |      */
 841 |     async getUserPayments (userId, paginate) {
 842 |       const json = await this._doGetRequest(
 843 |         `/v1/users/${userId}/payments?${this._paginateUri(paginate)}`
 844 |       )
 845 |       return {
 846 |         pages: json.meta['total-pages'],
 847 |         payments: json.data.map(payment =>
 848 |           fromResourceObject(payment, 'payments')
 849 |         )
 850 |       }
 851 |     }
 852 | 
 853 |     /**
 854 |      * @private
 855 |      * @returns {string}
 856 |      */
 857 |     _paginateUri ({ number, size, sort }) {
 858 |       // NOTE: query-string does not support the nesting format used in JsonApi
 859 |       // https://github.com/sindresorhus/query-string#nesting
 860 |       // http://jsonapi.org/examples/#pagination
 861 |       // http://jsonapi.org/format/#fetching-pagination
 862 |       const url = []
 863 |       if (number) url.push(`page[number]=${number}`)
 864 |       if (size) url.push(`page[size]=${size}`)
 865 |       if (sort) url.push(`sort=${sort}`)
 866 |       return url.join('&')
 867 |     }
 868 | 
 869 |     /**
 870 |      * Creates a payment for the user with the given user id to the specified payment
 871 |      * outputs.
 872 |      *
 873 |      * @param {string} userId
 874 |      * @param {object} attributes
 875 |      * @param {array} paymentOutputs
 876 |      * @returns {object}
 877 |      */
 878 |     async createUserPayment (userId, attributes, paymentOutputs) {
 879 |       let body = toJsonApiDataIncluding(
 880 |         toNewResourceObject('payments', attributes),
 881 |         paymentOutputs.map(paymentOutput => {
 882 |           return toNewResourceObject('payment_outputs', paymentOutput)
 883 |         })
 884 |       )
 885 |       let json = await this._doPostRequest(`/v1/users/${userId}/payments`, body)
 886 |       let { data, included } = fromJsonApiDataIncluding(json)
 887 |       let payment = fromResourceObject(data, 'payments')
 888 |       let [bsvTransaction] = fromResourceObjectsOfType(included, 'bsv_transactions')
 889 |       let addressIndexes = fromResourceObjectsOfType(
 890 |         included,
 891 |         'address_indexes'
 892 |       )
 893 |         .sort((a, b) => a.index - b.index)
 894 |         .map(addressIndex => addressIndex.addressIndex)
 895 |       return {
 896 |         payment,
 897 |         paymentOutputs: fromResourceObjectsOfType(included, 'payment_outputs'),
 898 |         bsvTransaction,
 899 |         addressIndexes
 900 |       }
 901 |     }
 902 | 
 903 |     /**
 904 |      * Retrives the payment with the given payment id, belonging to the user with
 905 |      * the given user id.
 906 |      *
 907 |      * @param {string} userId
 908 |      * @param {string} paymentId
 909 |      * @returns {object}
 910 |      */
 911 |     async getUserPayment (userId, paymentId) {
 912 |       let json = await this._doGetRequest(`/v1/users/${userId}/payments/${paymentId}`)
 913 |       let { data, included } = fromJsonApiDataIncluding(json)
 914 |       const payment = fromResourceObject(data, 'payments')
 915 |       payment.outputs = fromResourceObjectsOfType(included, 'payment_outputs')
 916 |       return payment
 917 |     }
 918 | 
 919 |     /**
 920 |      * Updates the payment with the given payment id, belonging to the user with
 921 |      * the given user id.
 922 |      *
 923 |      * @param {string} userId
 924 |      * @param {string} paymentId
 925 |      * @param {object} attributes
 926 |      * @param {bsv.Transaction} bsvTransaction
 927 |      * @returns {object}
 928 |      */
 929 |     async updateUserPaymentWithTransaction (
 930 |       userId,
 931 |       paymentId,
 932 |       attributes,
 933 |       bsvTransaction
 934 |     ) {
 935 |       let body = toJsonApiDataIncluding(
 936 |         toResourceObject(paymentId, 'payments', attributes),
 937 |         [toResourceObject(
 938 |           bsvTransaction.hash,
 939 |           'bsv_transactions',
 940 |           bsvTransaction
 941 |         )]
 942 |       )
 943 |       let json = await this._doPatchRequest(
 944 |         `/v1/users/${userId}/payments/${paymentId}`,
 945 |         body
 946 |       )
 947 |       return fromResourceObject(fromJsonApiData(json), 'payments')
 948 |     }
 949 | 
 950 |     /**
 951 |      * Creates a deposit for the user with the given id.
 952 |      *
 953 |      * @param {string} userId
 954 |      * @param {object} attributes
 955 |      * @returns {object}
 956 |      */
 957 |     async createUserDeposit (userId, attributes) {
 958 |       const body = toJsonApiData(toNewResourceObject('deposits', attributes))
 959 |       const json = await this._doPostRequest(
 960 |         `/v1/users/${userId}/deposits`,
 961 |         body
 962 |       )
 963 |       return fromResourceObject(fromJsonApiData(json), 'deposits')
 964 |     }
 965 | 
 966 |     /**
 967 |      * Retrives the deposit with the given deposit id, belonging to the user with
 968 |      * the given user id.
 969 |      *
 970 |      * @param {string} userId
 971 |      * @param {string} depositId
 972 |      * @returns {object}
 973 |      */
 974 |     async getUserDeposit (userId, depositId) {
 975 |       const json = await this._doGetRequest(
 976 |         `/v1/users/${userId}/deposits/${depositId}`
 977 |       )
 978 |       return fromResourceObject(fromJsonApiData(json), 'deposits')
 979 |     }
 980 | 
 981 |     /**
 982 |      *
 983 |      * @param {string} userId
 984 |      * @param {object} attributes
 985 |      * @returns {object}
 986 |      */
 987 |     async createUserWithdrawal (userId, attributes) {
 988 |       let body = toJsonApiData(toNewResourceObject('withdrawals', attributes))
 989 |       let json = await this._doPostRequest(
 990 |         `/v1/users/${userId}/withdrawals`,
 991 |         body
 992 |       )
 993 |       let { data, included } = fromJsonApiDataIncluding(json)
 994 |       let withdrawal = fromResourceObject(data, 'withdrawals')
 995 |       let [bsvTransaction] = fromResourceObjectsOfType(included, 'bsv_transactions')
 996 |       let addressIndexes = fromResourceObjectsOfType(
 997 |         included,
 998 |         'address_indexes'
 999 |       )
1000 |         .sort((a, b) => a.index - b.index)
1001 |         .map(addressIndex => addressIndex.addressIndex)
1002 |       return {
1003 |         withdrawal,
1004 |         bsvTransaction,
1005 |         addressIndexes
1006 |       }
1007 |     }
1008 | 
1009 |     /**
1010 |      * Retrives the withdrawal with the given withdrawal id, belonging to the user with
1011 |      * the given user id.
1012 |      *
1013 |      * @param {string} userId
1014 |      * @param {string} withdrawalId
1015 |      * @returns {object}
1016 |      */
1017 |     async getUserWithdrawal (userId, withdrawalId) {
1018 |       const json = await this._doGetRequest(
1019 |         `/v1/users/${userId}/withdrawals/${withdrawalId}`
1020 |       )
1021 |       return fromResourceObject(fromJsonApiData(json), 'withdrawals')
1022 |     }
1023 | 
1024 |     /**
1025 |      * Updates the withdrawal with the given withdrawal id, belonging to the user with
1026 |      * the given user id.
1027 |      *
1028 |      * @param {string} userId
1029 |      * @param {string} withdrawalId
1030 |      * @param {object} attributes
1031 |      * @param {object} transaction
1032 |      * @returns {object}
1033 |      */
1034 |     async updateUserWithdrawalWithTransaction (
1035 |       userId,
1036 |       withdrawalId,
1037 |       attributes,
1038 |       transaction
1039 |     ) {
1040 |       let body = toJsonApiDataIncluding(
1041 |         toResourceObject(withdrawalId, 'withdrawals', attributes),
1042 |         [toResourceObject(uuid.v1(), 'transactions', transaction)]
1043 |       )
1044 |       let json = await this._doPatchRequest(
1045 |         `/v1/users/${userId}/withdrawals/${withdrawalId}`,
1046 |         body
1047 |       )
1048 |       return fromResourceObject(fromJsonApiData(json), 'withdrawals')
1049 |     }
1050 | 
1051 |     /**
1052 |      * Broadcasts the given bsv transaction. The transaction must be fully signed.
1053 |      *
1054 |      * @param {bsv.Transaction} bsvTransaction
1055 |      * @returns {object}
1056 |      */
1057 |     async broadcastTransaction (bsvTransaction) {
1058 |       const body = toJsonApiData(toResourceObject(
1059 |         bsvTransaction.hash,
1060 |         'bsv_transactions',
1061 |         bsvTransaction
1062 |       ))
1063 |       const json = await this._doPostRequest(
1064 |         '/v1/transactions/broadcast',
1065 |         body
1066 |       )
1067 |       return fromResourceObject(fromJsonApiData(json), 'txids')
1068 |     }
1069 | 
1070 |     /**
1071 |      * Retrieves the list of supported cryptocurrencies.
1072 |      *
1073 |      * @param {object} query
1074 |      * @returns {array}
1075 |      */
1076 |     async getSupportedCryptocurrencies (query = {}) {
1077 |       const json = await this._doGetRequest('/v1/currencies/crypto', query)
1078 |       return fromResourceObjectsOfType(fromJsonApiData(json), 'currencies')
1079 |     }
1080 | 
1081 |     /**
1082 |      * Retrieves the list of supported fiat currencies.
1083 |      *
1084 |      * @param {object} query
1085 |      * @returns {array}
1086 |      */
1087 |     async getSupportedFiatCurrencies (query = {}) {
1088 |       const json = await this._doGetRequest('/v1/currencies/fiat', query)
1089 |       return fromResourceObjectsOfType(fromJsonApiData(json), 'currencies')
1090 |     }
1091 | 
1092 |     /**
1093 |      * @private
1094 |      * @param {string} endpoint - REST API relative endpoint.
1095 |      * @param {object} query - URL query parameters.
1096 |      * @param {string} accessToken - auth API access token
1097 |      * @returns {object}
1098 |      */
1099 |     async _doGetRequest (endpoint, query = {}, accessToken = null) {
1100 |       let opts = {
1101 |         method: 'GET'
1102 |       }
1103 |       return this._doRequest(endpoint, opts, query, accessToken)
1104 |     }
1105 | 
1106 |     /**
1107 |      * @private
1108 |      * @param {string} endpoint - REST API relative endpoint.
1109 |      * @param {object} body - fetch request's body.
1110 |      * @param {object} query - URL query parameters.
1111 |      * @param {string} accessToken - auth API access token
1112 |      * @returns {object}
1113 |      */
1114 |     async _doPostRequest (endpoint, body = {}, query = {}, accessToken = null) {
1115 |       let opts = {
1116 |         method: 'POST',
1117 |         body: JSON.stringify(body)
1118 |       }
1119 |       return this._doRequest(endpoint, opts, query, accessToken)
1120 |     }
1121 | 
1122 |     /**
1123 |      * @private
1124 |      * @param {string} endpoint - REST API relative endpoint.
1125 |      * @param {object} body - fetch request's body.
1126 |      * @param {string} accessToken - auth API access token
1127 |      * @returns {object}
1128 |      */
1129 |     async _doPatchRequest (endpoint, body = {}, accessToken = null) {
1130 |       let opts = {
1131 |         method: 'PATCH',
1132 |         body: JSON.stringify(body)
1133 |       }
1134 |       return this._doRequest(endpoint, opts, {}, accessToken)
1135 |     }
1136 | 
1137 |     /**
1138 |      * @private
1139 |      * @param {string} endpoint - REST API relative endpoint.
1140 |      * @param {object} body - fetch request's body.
1141 |      * @param {string} accessToken - auth API access token
1142 |      * @returns {object}
1143 |      */
1144 |     async _doPutRequest (endpoint, body = {}, accessToken = null) {
1145 |       let opts = {
1146 |         method: 'PUT',
1147 |         body: JSON.stringify(body)
1148 |       }
1149 |       return this._doRequest(endpoint, opts, {}, accessToken)
1150 |     }
1151 | 
1152 |     /**
1153 |      *
1154 |      * @param {string} endpoint - REST API relative endpoint.
1155 |      * @param {object} opts - fetch request options.
1156 |      * @param {object} query - URL query parameters.
1157 |      * @param {string} accessToken - auth API access token
1158 |      * @returns {object}
1159 |      */
1160 |     async _doRequest (endpoint, opts = {}, query = {}, accessToken = null) {
1161 |       const url = this._appendQuery(`${API_REST_URI}/api${endpoint}`, query)
1162 |       let headers = {
1163 |         'Content-Type': 'application/vnd.api+json',
1164 |         Accept: 'application/vnd.api+json'
1165 |       }
1166 |       accessToken = accessToken === null
1167 |         ? await this.getValidAccessToken()
1168 |         : accessToken
1169 |       if (accessToken !== null) {
1170 |         headers['Authorization'] = `Bearer ${accessToken}`
1171 |       }
1172 |       const res = await fetch(url, { ...opts, headers })
1173 |       let json = await res.json()
1174 |       let { errors } = json
1175 |       if (errors instanceof Array) {
1176 |         let error = errors[0]
1177 |         if (error.status) {
1178 |           let { status, title, detail } = error
1179 |           throw new RestError(status, title, detail)
1180 |         }
1181 |         throw new Error(error.title)
1182 |       }
1183 |       return json
1184 |     }
1185 | 
1186 |     /**
1187 |      * @private
1188 |      * @param {string} url - base URL where query will be appended.
1189 |      * @param {object} query - URL query parameters.
1190 |      * @returns {string}
1191 |      */
1192 |     _appendQuery (url, query = {}) {
1193 |       if (Object.keys(query).length === 0) {
1194 |         return url
1195 |       }
1196 |       const { page, ...queryWithoutPage } = query
1197 |       if (page !== undefined) {
1198 |         for (const key in page) {
1199 |           queryWithoutPage[`page[${key}]`] = page[key]
1200 |         }
1201 |       }
1202 |       return `${url}?${queryString.stringify(queryWithoutPage)}`
1203 |     }
1204 | 
1205 |     /**
1206 |     *
1207 |     * Web location utilities.
1208 |     *
1209 |     */
1210 | 
1211 |     /**
1212 |      *
1213 |      */
1214 |     _getUrlQuery () {
1215 |       return queryString.parse(webLocation.search)
1216 |     }
1217 | 
1218 |     /**
1219 |      *
1220 |      * @param {string} uri - URI where the browser will be redirected to.
1221 |      */
1222 |     _redirectToUri (uri) {
1223 |       webLocation.href = uri
1224 |     }
1225 | 
1226 |     /**
1227 |     *
1228 |     * Web storage utilities.
1229 |     *
1230 |     */
1231 | 
1232 |     /**
1233 |      * @private
1234 |      * @returns {string}
1235 |      */
1236 |     _getRedirectUri () {
1237 |       return webStorage.getItem(OAUTH_REDIRECT_URI_KEY)
1238 |     }
1239 | 
1240 |     /**
1241 |      * @private
1242 |      * @param {string} redirectUri - OAuth redirect URI from authorization grant flow.
1243 |      * @returns {undefined}
1244 |      */
1245 |     _setRedirectUri (redirectUri) {
1246 |       webStorage.setItem(OAUTH_REDIRECT_URI_KEY, redirectUri)
1247 |     }
1248 | 
1249 |     /**
1250 |      * @private
1251 |      * @returns {undefined}
1252 |      */
1253 |     _clearRedirectUri () {
1254 |       webStorage.removeItem(OAUTH_REDIRECT_URI_KEY)
1255 |     }
1256 | 
1257 |     /**
1258 |      * @private
1259 |      * @returns {undefined}
1260 |      */
1261 |     _getState () {
1262 |       return webStorage.getItem(OAUTH_STATE_KEY)
1263 |     }
1264 | 
1265 |     /**
1266 |      * @private
1267 |      * @param {string} state - OAuth state from authorization grant flow.
1268 |      * @returns {undefined}
1269 |      */
1270 |     _setState (state) {
1271 |       webStorage.setItem(OAUTH_STATE_KEY, state)
1272 |     }
1273 | 
1274 |     /**
1275 |      * @private
1276 |      * @returns {undefined}
1277 |      */
1278 |     _clearState () {
1279 |       webStorage.removeItem(OAUTH_STATE_KEY)
1280 |     }
1281 | 
1282 |     /**
1283 |      * Retrieves the currently-set access token.
1284 |      *
1285 |      * @returns {string}
1286 |      */
1287 |     getAccessToken () {
1288 |       return webStorage.getItem(OAUTH_ACCESS_TOKEN_KEY)
1289 |     }
1290 | 
1291 |     /**
1292 |      * Sets the given access token.
1293 |      *
1294 |      * @param {string} accessToken - auth API access token
1295 |      * @returns {undefined}
1296 |      */
1297 |     setAccessToken (accessToken) {
1298 |       webStorage.setItem(OAUTH_ACCESS_TOKEN_KEY, accessToken)
1299 |     }
1300 | 
1301 |     /**
1302 |      * Clears the currently-set access token.
1303 |      *
1304 |      * @returns {undefined}
1305 |      */
1306 |     clearAccessToken () {
1307 |       webStorage.removeItem(OAUTH_ACCESS_TOKEN_KEY)
1308 |     }
1309 | 
1310 |     /**
1311 |      * Returns the currently-set token's expiration time in the following
1312 |      * format: 'YYYY-MM-DDTHH:mm:ssZ'.
1313 |      * For example, '2018-10-25T13:08:58-03:00'.
1314 |      *
1315 |      * @returns {string}
1316 |      */
1317 |     getExpirationTime () {
1318 |       return webStorage.getItem(OAUTH_EXPIRATION_TIME_KEY)
1319 |     }
1320 | 
1321 |     /**
1322 |      * Sets the currently-set token's expiration time. The argument must be
1323 |      * in the following format: 'YYYY-MM-DDTHH:mm:ssZ'.
1324 |      * For example, '2018-10-25T13:08:58-03:00'.
1325 |      *
1326 |      * @param {string} expirationTime
1327 |      * @returns {undefined}
1328 |      */
1329 |     setExpirationTime (expirationTime) {
1330 |       webStorage.setItem(OAUTH_EXPIRATION_TIME_KEY, expirationTime)
1331 |     }
1332 | 
1333 |     /**
1334 |      * Clears the currently-set access token's expiration time.
1335 |      *
1336 |      * @returns {undefined}
1337 |      */
1338 |     clearExpirationTime () {
1339 |       webStorage.removeItem(OAUTH_EXPIRATION_TIME_KEY)
1340 |     }
1341 | 
1342 |     /**
1343 |      * Retrieves the currently-set refresh token.
1344 |      *
1345 |      * @returns {string}
1346 |      */
1347 |     getRefreshToken () {
1348 |       return webStorage.getItem(OAUTH_REFRESH_TOKEN_KEY)
1349 |     }
1350 | 
1351 |     /**
1352 |      * Sets the given refresh token.
1353 |      *
1354 |      * @param {string} refreshToken - auth API refresh token
1355 |      * @returns {undefined}
1356 |      */
1357 |     setRefreshToken (refreshToken) {
1358 |       webStorage.setItem(OAUTH_REFRESH_TOKEN_KEY, refreshToken)
1359 |     }
1360 | 
1361 |     /**
1362 |      * Clears the currently-set refresh token.
1363 |      * @returns {undefined}
1364 |      */
1365 |     clearRefreshToken () {
1366 |       webStorage.removeItem(OAUTH_REFRESH_TOKEN_KEY)
1367 |     }
1368 | 
1369 |     /**
1370 |     *
1371 |     * Web crypto utilities.
1372 |     *
1373 |     */
1374 | 
1375 |     /**
1376 |      * @private
1377 |      * @param {string} key - HMAC key.
1378 |      * @param {string} message- HMAC message.
1379 |      * @returns {string}
1380 |      */
1381 |     static async _computeHmac256 (key, message) {
1382 |       let cryptoKey = await webCrypto.subtle.importKey(
1383 |         'raw',
1384 |         Buffer.from(key),
1385 |         {
1386 |           name: 'HMAC',
1387 |           hash: { name: 'SHA-256' }
1388 |         },
1389 |         false,
1390 |         ['sign', 'verify']
1391 |       )
1392 |       let signature = await webCrypto.subtle.sign(
1393 |         'HMAC',
1394 |         cryptoKey,
1395 |         Buffer.from(message)
1396 |       )
1397 |       return Buffer.from(new Uint8Array(signature)).toString('hex')
1398 |     }
1399 |   }
1400 | 
1401 |   return MoneyButtonClient
1402 | }
1403 | 
1404 |
1405 |
1406 | 1407 | 1408 | 1409 | 1410 |
1411 | 1412 | 1415 | 1416 |
1417 | 1418 | 1421 | 1422 | 1423 | 1424 | 1425 | 1426 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Home 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Home

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
51 | 52 | 55 | 56 |
57 | 58 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /docs/index.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Source: index.js 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Source: index.js

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
import {
  30 |   toResourceObject,
  31 |   toNewResourceObject,
  32 |   fromResourceObject,
  33 |   fromResourceObjectsOfType,
  34 |   toJsonApiData,
  35 |   toJsonApiDataIncluding,
  36 |   fromJsonApiData,
  37 |   fromJsonApiDataIncluding
  38 | } from '@moneybutton/json-api'
  39 | import uuid from 'uuid'
  40 | import fetch from 'isomorphic-fetch'
  41 | import moment from 'moment'
  42 | import queryString from 'query-string'
  43 | 
  44 | import { AuthError, RestError } from './errors'
  45 | import config from './config'
  46 | 
  47 | const API_REST_URI = config.get('MONEY_BUTTON_API_REST_URI')
  48 | const API_AUTH_URI = config.get('MONEY_BUTTON_API_AUTH_URI')
  49 | 
  50 | const LOGIN_PASSWORD_HMAC_KEY = 'yours login password'
  51 | 
  52 | const STORAGE_NAMESPACE = 'mb_js_client'
  53 | const OAUTH_REDIRECT_URI_KEY = [STORAGE_NAMESPACE, 'oauth_redirect_uri'].join(':')
  54 | const OAUTH_STATE_KEY = [STORAGE_NAMESPACE, 'oauth_state'].join(':')
  55 | const OAUTH_ACCESS_TOKEN_KEY = [STORAGE_NAMESPACE, 'oauth_access_token'].join(':')
  56 | const OAUTH_EXPIRATION_TIME_KEY = [STORAGE_NAMESPACE, 'oauth_expiration_time'].join(':')
  57 | const OAUTH_REFRESH_TOKEN_KEY = [STORAGE_NAMESPACE, 'oauth_refresh_token'].join(':')
  58 | 
  59 | /**
  60 |  * @param {Location} webLocation - Object conforming to the Location Web API.
  61 |  * @param {Storage} webStorage - Object conforming to the Storage Web API.
  62 |  * @param {Crypto} webCrypto - Object conforming to the Crypto Web API.
  63 |  */
  64 | export default function (webLocation, webStorage, webCrypto) {
  65 |   /**
  66 |    *
  67 |    */
  68 |   class MoneyButtonClient {
  69 |     /**
  70 |      * Creates an instance of Money Button for the given OAuth client.
  71 |      *
  72 |      * @param {string} clientId - OAuth client's identifier.
  73 |      * @param {string} clientSecret - OAuth client's secret.
  74 |      */
  75 |     constructor (clientId, clientSecret = null) {
  76 |       this.clientId = clientId
  77 |       this.clientSecret = clientSecret
  78 |     }
  79 | 
  80 |     /**
  81 |      * Logs in the user with the given email and password.
  82 |      *
  83 |      * @param {string} email
  84 |      * @param {string} password
  85 |      * @returns {undefined}
  86 |      */
  87 |     async logIn (email, password) {
  88 |       if (await this.isLoggedIn()) {
  89 |         this.logOut()
  90 |       }
  91 |       const loginPassword = await MoneyButtonClient._computeHmac256(
  92 |         LOGIN_PASSWORD_HMAC_KEY,
  93 |         password
  94 |       )
  95 |       await this._doResourceOwnerPasswordCredentialsGrantAccessTokenRequest(
  96 |         email,
  97 |         loginPassword,
  98 |         'general_access:write'
  99 |       )
 100 |     }
 101 | 
 102 |     /**
 103 |      * Determines whether a user is currently logged-in.
 104 |      *
 105 |      * @returns {boolean}
 106 |      */
 107 |     async isLoggedIn () {
 108 |       const accessToken = await this.getValidAccessToken()
 109 |       return accessToken !== null
 110 |     }
 111 | 
 112 |     /**
 113 |      * Retrieves a valid access token for the currently logged-in user.
 114 |      * Returns null if no user is currently logged-in.
 115 |      *
 116 |      * @returns {string|null}
 117 |      */
 118 |     async getValidAccessToken () {
 119 |       let accessToken = this.getAccessToken()
 120 |       if (
 121 |         accessToken !== null &&
 122 |         moment().isBefore(moment(this.getExpirationTime()))
 123 |       ) {
 124 |         return accessToken
 125 |       }
 126 |       const refreshToken = this.getRefreshToken()
 127 |       if (refreshToken === null) {
 128 |         return null
 129 |       }
 130 |       accessToken = null
 131 |       try {
 132 |         await this._doRefreshAccessTokenRequest(refreshToken)
 133 |         accessToken = this.getAccessToken()
 134 |       } catch (err) {
 135 |         if (!(err instanceof AuthError)) {
 136 |           throw err
 137 |         }
 138 |       }
 139 |       return accessToken
 140 |     }
 141 | 
 142 |     /**
 143 |      * Logs out the current logged-in user, if any.
 144 |      */
 145 |     logOut () {
 146 |       this.clearAccessToken()
 147 |       this.clearExpirationTime()
 148 |       this.clearRefreshToken()
 149 |     }
 150 | 
 151 |     /**
 152 |      * Finishes the email verification process with the access token generated
 153 |      * during signup.
 154 |      *
 155 |      * @param {string} accessToken - auth API access token
 156 |      * @returns {object}
 157 |      */
 158 |     async verifyEmail (accessToken) {
 159 |       const json = await this._doPostRequest(
 160 |         '/v1/auth/email_verification',
 161 |         {},
 162 |         {},
 163 |         accessToken
 164 |       )
 165 |       return fromResourceObject(fromJsonApiData(json), 'email_verifications')
 166 |     }
 167 | 
 168 |     /**
 169 |      * Retrieves the currently logged user's identity.
 170 |      *
 171 |      * @returns {object}
 172 |      */
 173 |     async getIdentity () {
 174 |       const json = await this._doGetRequest('/v1/auth/user_identity')
 175 |       return fromResourceObject(fromJsonApiData(json), 'user_identities')
 176 |     }
 177 | 
 178 |     /**
 179 |      * Returns an object with two keys:
 180 |      * - loggedIn: a boolean indicating whether there is a currently logged-in user.
 181 |      * - user: if loggedIn is true, this is an object with the user's attributes.
 182 |      *
 183 |      * @returns {object}
 184 |      */
 185 |     async whoAmI () {
 186 |       const loggedIn = await this.isLoggedIn()
 187 |       if (!loggedIn) {
 188 |         return { loggedIn }
 189 |       }
 190 |       const { id } = await this.getIdentity()
 191 |       const user = await this.getUser(id)
 192 |       return { loggedIn, user }
 193 |     }
 194 | 
 195 |     /**
 196 |      * Changes the currently logged-in user's password.
 197 |      *
 198 |      * @param {string} password
 199 |      * @param {string} encryptedMnemonic
 200 |      * @param {string} xpub
 201 |      * @param {string} language
 202 |      * @returns {object}
 203 |      */
 204 |     async changePassword (
 205 |       password,
 206 |       encryptedMnemonic,
 207 |       xpub,
 208 |       language
 209 |     ) {
 210 |       const loginPassword = await MoneyButtonClient._computeHmac256(
 211 |         LOGIN_PASSWORD_HMAC_KEY,
 212 |         password
 213 |       )
 214 |       const body = toJsonApiDataIncluding(
 215 |         toNewResourceObject('users', {
 216 |           password: loginPassword
 217 |         }),
 218 |         [
 219 |           toNewResourceObject('wallets', {
 220 |             encryptedMnemonic,
 221 |             xpub,
 222 |             language
 223 |           })
 224 |         ]
 225 |       )
 226 |       const json = await this._doPostRequest('/v1/auth/password_change', body)
 227 |       return fromResourceObject(fromJsonApiData(json), 'password_changes')
 228 |     }
 229 | 
 230 |     /**
 231 |      * Resets the currently logged-in user's password by using the access token
 232 |      * generated during the "I forgot my password" flow.
 233 |      *
 234 |      * @param {string} accessToken - auth API access token
 235 |      * @param {string} password
 236 |      * @param {string} encryptedMnemonic
 237 |      * @param {string} xpub
 238 |      * @param {boolean} forceCreate
 239 |      * @param {string} walletLanguage
 240 |      * @returns {object}
 241 |      */
 242 |     async resetPassword (
 243 |       accessToken,
 244 |       password,
 245 |       encryptedMnemonic,
 246 |       xpub,
 247 |       forceCreate,
 248 |       walletLanguage
 249 |     ) {
 250 |       const loginPassword = await MoneyButtonClient._computeHmac256(
 251 |         LOGIN_PASSWORD_HMAC_KEY,
 252 |         password
 253 |       )
 254 |       const body = toJsonApiDataIncluding(
 255 |         toNewResourceObject('users', {
 256 |           password: loginPassword
 257 |         }),
 258 |         [
 259 |           toNewResourceObject('wallets', {
 260 |             encryptedMnemonic,
 261 |             xpub,
 262 |             language: walletLanguage
 263 |           })
 264 |         ]
 265 |       )
 266 |       const query = forceCreate ? { forceCreate: 'true' } : {}
 267 |       const json = await this._doPostRequest(
 268 |         '/v1/auth/password_reset',
 269 |         body,
 270 |         query,
 271 |         accessToken
 272 |       )
 273 |       return fromResourceObject(fromJsonApiData(json), 'password_resets')
 274 |     }
 275 | 
 276 |     /**
 277 |      * Sends a password reset email to begin the "I forgot my password" flow.
 278 |      *
 279 |      * @param {string} email
 280 |      * @returns {object}
 281 |      */
 282 |     async sendPasswordReset (email) {
 283 |       if (await this.isLoggedIn()) {
 284 |         this.logOut()
 285 |       }
 286 |       await this._doClientCredentialsGrantAccessTokenRequest(
 287 |         'auth.password_reset_email:write'
 288 |       )
 289 |       const attributes = { email }
 290 |       const body = toJsonApiData(toNewResourceObject('users', attributes))
 291 |       const json = await this._doPostRequest('/v1/auth/password_reset_email', body)
 292 |       this.logOut()
 293 |       return fromResourceObject(fromJsonApiData(json), 'password_reset_emails')
 294 |     }
 295 | 
 296 |     /**
 297 |      * Creates a new user account with the given email and password.
 298 |      *
 299 |      * @param {string} email
 300 |      * @param {string} password
 301 |      * @returns {object}
 302 |      */
 303 |     async signUp (email, password) {
 304 |       if (await this.isLoggedIn()) {
 305 |         this.logOut()
 306 |       }
 307 |       await this._doClientCredentialsGrantAccessTokenRequest(
 308 |         'auth.signup:write'
 309 |       )
 310 |       const loginPassword = await MoneyButtonClient._computeHmac256(
 311 |         LOGIN_PASSWORD_HMAC_KEY,
 312 |         password
 313 |       )
 314 |       const attributes = {
 315 |         email,
 316 |         password: loginPassword
 317 |       }
 318 |       const body = toJsonApiData(toNewResourceObject('users', attributes))
 319 |       const json = await this._doPostRequest('/v1/auth/signup', body)
 320 |       await this.logIn(email, password)
 321 |       return fromResourceObject(fromJsonApiData(json), 'signups')
 322 |     }
 323 | 
 324 |     /**
 325 |      * [Browser only] Starts the authorization flow which allows third-party applications
 326 |      * to request access to user resources on their behalf. This function will
 327 |      * redirect the user's window to the Money Button's authorization flow page.
 328 |      *
 329 |      * @param {string} scope - scope to be requested to the user.
 330 |      * @param {string} redirectUri - URI where the authorization response will be handled.
 331 |      * @returns {undefined}
 332 |      */
 333 |     requestAuthorization (
 334 |       scope,
 335 |       redirectUri
 336 |     ) {
 337 |       if (typeof scope !== 'string' || scope.length === 0) {
 338 |         throw new Error(`Invalid scope requested: ${scope}.`)
 339 |       }
 340 |       if (typeof redirectUri !== 'string' || redirectUri.length === 0) {
 341 |         throw new Error(`Invalid return URI: ${redirectUri}.`)
 342 |       }
 343 |       this._doAuthorizationCodeGrantAuthorizationRequest(redirectUri, scope)
 344 |     }
 345 | 
 346 |     /**
 347 |      * [Browser only] Finishes the authorization flow started by {@link requestAuthorization}.
 348 |      * If successful, after calling this function, the client will be able to perform requests
 349 |      * on behalf of the user as long as they are within the scope requested when starting the
 350 |      * authorization flow.
 351 |      *
 352 |      * @returns {undefined}
 353 |      */
 354 |     async handleAuthorizationResponse () {
 355 |       const { error, code, state } = this._getUrlQuery()
 356 |       await this._handleAuthorizationCodeGrantAuthorizationResponse(error, code, state)
 357 |     }
 358 | 
 359 |     /**
 360 |      * See: https://tools.ietf.org/html/rfc6749#page-24.
 361 |      *
 362 |      * @private
 363 |      * @param {string} redirectUri
 364 |      * @param {string} scope
 365 |      */
 366 |     _doAuthorizationCodeGrantAuthorizationRequest (
 367 |       redirectUri,
 368 |       scope
 369 |     ) {
 370 |       if (this.clientSecret !== null) {
 371 |         throw new Error([
 372 |           'Grant `authentication_code` can only be performed by ',
 373 |           'a public client (that is, a client with no client secret).'
 374 |         ].join(''))
 375 |       }
 376 |       const state = uuid.v4()
 377 |       this._setRedirectUri(redirectUri)
 378 |       this._setState(state)
 379 |       const authorizationUri = [
 380 |         `${API_AUTH_URI}/oauth/v1/authorize`,
 381 |         queryString.stringify({
 382 |           response_type: 'code',
 383 |           client_id: this.clientId,
 384 |           redirect_uri: redirectUri,
 385 |           scope,
 386 |           state
 387 |         })
 388 |       ].join('?')
 389 |       this._redirectToUri(authorizationUri)
 390 |     }
 391 | 
 392 |     /**
 393 |      * See: https://tools.ietf.org/html/rfc6749#page-26.
 394 |      *
 395 |      * @private
 396 |      */
 397 |     async _handleAuthorizationCodeGrantAuthorizationResponse (
 398 |       error,
 399 |       code,
 400 |       state
 401 |     ) {
 402 |       const expectedState = this._getState()
 403 |       if (expectedState === null || state !== expectedState) {
 404 |         throw new Error('Invalid OAuth state.')
 405 |       }
 406 |       if (error !== undefined) {
 407 |         throw new AuthError('Authorization failed.', error)
 408 |       }
 409 |       if (code === undefined) {
 410 |         throw new Error('Missing OAuth authorization code.')
 411 |       }
 412 |       await this._doAuthorizationCodeGrantAccessTokenRequest(code)
 413 |     }
 414 | 
 415 |     /**
 416 |      * See: https://tools.ietf.org/html/rfc6749#page-29.
 417 |      *
 418 |      * @private
 419 |      */
 420 |     async _doAuthorizationCodeGrantAccessTokenRequest (
 421 |       code
 422 |     ) {
 423 |       const redirectUri = this._getRedirectUri()
 424 |       if (redirectUri === null) {
 425 |         throw new Error('Required OAuth redirect URI not found in storage.')
 426 |       }
 427 |       await this._doAccessTokenRequest(
 428 |         {
 429 |           grant_type: 'authorization_code',
 430 |           code,
 431 |           redirect_uri: redirectUri,
 432 |           client_id: this.clientId
 433 |         }
 434 |       )
 435 |     }
 436 | 
 437 |     /**
 438 |      * See: https://tools.ietf.org/html/rfc6749#page-37.
 439 |      *
 440 |      * @private
 441 |      */
 442 |     async _doResourceOwnerPasswordCredentialsGrantAccessTokenRequest (
 443 |       username,
 444 |       password,
 445 |       scope
 446 |     ) {
 447 |       await this._doAccessTokenRequest(
 448 |         {
 449 |           grant_type: 'password',
 450 |           username,
 451 |           password,
 452 |           scope
 453 |         },
 454 |         this._buildBasicAuthHeaders()
 455 |       )
 456 |     }
 457 | 
 458 |     /**
 459 |      * See: https://tools.ietf.org/html/rfc6749#page-41.
 460 |      *
 461 |      * @private
 462 |      */
 463 |     async _doClientCredentialsGrantAccessTokenRequest (scope) {
 464 |       await this._doAccessTokenRequest(
 465 |         {
 466 |           grant_type: 'client_credentials',
 467 |           scope
 468 |         },
 469 |         this._buildBasicAuthHeaders()
 470 |       )
 471 |     }
 472 | 
 473 |     /**
 474 |      * @private
 475 |      * @param {string} refreshToken
 476 |      */
 477 |     async _doRefreshAccessTokenRequest (refreshToken) {
 478 |       await this._doAccessTokenRequest(
 479 |         {
 480 |           grant_type: 'refresh_token',
 481 |           refresh_token: refreshToken
 482 |         },
 483 |         this._buildBasicAuthHeaders()
 484 |       )
 485 |     }
 486 | 
 487 |     /**
 488 |      * @private
 489 |      */
 490 |     _buildBasicAuthHeaders () {
 491 |       const credentials = `${this.clientId}:${this.clientSecret}`
 492 |       return {
 493 |         Authorization: `Basic ${Buffer.from(credentials).toString('base64')}`
 494 |       }
 495 |     }
 496 | 
 497 |     /**
 498 |      * @private
 499 |      * @param {object} body
 500 |      * @param {object} headers
 501 |      */
 502 |     async _doAccessTokenRequest (body = {}, headers = {}) {
 503 |       const res = await fetch(
 504 |         `${API_AUTH_URI}/oauth/v1/token`,
 505 |         {
 506 |           method: 'POST',
 507 |           body: queryString.stringify(body),
 508 |           headers: {
 509 |             ...headers,
 510 |             'Content-Type': 'application/x-www-form-urlencoded'
 511 |           }
 512 |         }
 513 |       )
 514 |       await this._handleAccessTokenResponse(res)
 515 |     }
 516 | 
 517 |     /**
 518 |      * @private
 519 |      * @param {Response} res - Express.js response object.
 520 |      */
 521 |     async _handleAccessTokenResponse (res) {
 522 |       const {
 523 |         error,
 524 |         error_description: errorDescription,
 525 |         access_token: accessToken,
 526 |         token_type: tokenType,
 527 |         expires_in: expiresIn,
 528 |         refresh_token: refreshToken
 529 |       } = await res.json()
 530 |       if (error !== undefined) {
 531 |         throw new AuthError(error, errorDescription)
 532 |       }
 533 |       if (tokenType !== 'Bearer') {
 534 |         throw new Error('Unexpected token type.')
 535 |       }
 536 |       if (accessToken !== undefined) {
 537 |         this.setAccessToken(accessToken)
 538 |       } else {
 539 |         this.clearAccessToken()
 540 |       }
 541 |       if (expiresIn !== undefined) {
 542 |         const expirationTime = moment().add(expiresIn, 'seconds')
 543 |         this.setExpirationTime(expirationTime.format())
 544 |       } else {
 545 |         this.clearExpirationTime()
 546 |       }
 547 |       if (refreshToken !== undefined) {
 548 |         this.setRefreshToken(refreshToken)
 549 |       } else {
 550 |         this.clearRefreshToken()
 551 |       }
 552 |     }
 553 | 
 554 |     /**
 555 |      * Get basic information from the OAuth client with the given identifier.
 556 |      *
 557 |      * @param {string} clientIdentifier
 558 |      * @returns {object}
 559 |      */
 560 |     async getClientByIdentifier (clientIdentifier) {
 561 |       const json = await this._doGetRequest(`/v1/clients/client_identifier=${clientIdentifier}`)
 562 |       return fromResourceObject(fromJsonApiData(json), 'clients')
 563 |     }
 564 | 
 565 |     /**
 566 |      * Retrives the user with the given user id.
 567 |      *
 568 |      * @param {string} userId
 569 |      * @returns {object}
 570 |      */
 571 |     async getUser (userId) {
 572 |       let json = await this._doGetRequest(`/v1/users/${userId}`)
 573 |       return fromResourceObject(fromJsonApiData(json), 'users')
 574 |     }
 575 | 
 576 |     /**
 577 |      * Updates the user with the given user id.
 578 |      *
 579 |      * @param {string} userId
 580 |      * @param {object} attributes
 581 |      * @returns {object}
 582 |      */
 583 |     async updateUser (userId, attributes = {}) {
 584 |       const body = toJsonApiData(toResourceObject(userId, 'users', attributes))
 585 |       const json = await this._doPatchRequest(`/v1/users/${userId}`, body)
 586 |       return fromResourceObject(fromJsonApiData(json), 'users')
 587 |     }
 588 | 
 589 |     /**
 590 |      * Retrives the transaction history of the user with the given user id.
 591 |      *
 592 |      * @param {string} userId
 593 |      * @param {object} query
 594 |      * @returns {object}
 595 |      */
 596 |     async getUserTransactionHistory (userId, query = {}) {
 597 |       const json = await this._doGetRequest(
 598 |         `/v1/users/${userId}/transaction_history`,
 599 |         query
 600 |       )
 601 |       return fromResourceObjectsOfType(
 602 |         fromJsonApiData(json),
 603 |         'transaction_history'
 604 |       )
 605 |     }
 606 | 
 607 |     /**
 608 |      * Retrives the OAuth clients of the user with the given user id.
 609 |      *
 610 |      * @param {string} userId
 611 |      * @param {object} query
 612 |      * @returns {object}
 613 |      */
 614 |     async getUserClients (userId, query = {}) {
 615 |       const json = await this._doGetRequest(
 616 |         `/v1/users/${userId}/clients`,
 617 |         query
 618 |       )
 619 |       return fromResourceObjectsOfType(fromJsonApiData(json), 'clients')
 620 |     }
 621 | 
 622 |     /**
 623 |      * Creates an OAuth client for the user with the given user id.
 624 |      *
 625 |      * @param {string} userId
 626 |      * @param {object} attributes
 627 |      * @returns {object}
 628 |      */
 629 |     async createUserClient (userId, attributes) {
 630 |       let body = toJsonApiData(toNewResourceObject('clients', attributes))
 631 |       const json = await this._doPostRequest(`/v1/users/${userId}/clients`, body)
 632 |       return fromResourceObject(fromJsonApiData(json), 'clients')
 633 |     }
 634 | 
 635 |     /**
 636 |      * Updates an OAuth client for the user with the given user id.
 637 |      *
 638 |      * @param {string} userId
 639 |      * @param {string} clientId
 640 |      * @param {object} attributes
 641 |      * @returns {object}
 642 |      */
 643 |     async updateUserClient (userId, clientId, attributes = {}) {
 644 |       const body = toJsonApiData(
 645 |         toResourceObject(clientId, 'clients', attributes)
 646 |       )
 647 |       await this._doPatchRequest(
 648 |         `/v1/users/${userId}/clients/${clientId}`,
 649 |         body
 650 |       )
 651 |     }
 652 | 
 653 |     /**
 654 |      * Retrives the wallet with the given wallet id for the user with
 655 |      * the given user id.
 656 |      *
 657 |      * @param {string} userId
 658 |      * @param {string} walletId
 659 |      * @returns {object}
 660 |      */
 661 |     async getUserWallet (userId, walletId) {
 662 |       let json = await this._doGetRequest(
 663 |         `/v1/users/${userId}/wallets/${walletId}`
 664 |       )
 665 |       return fromResourceObject(fromJsonApiData(json), 'wallets')
 666 |     }
 667 | 
 668 |     /**
 669 |      * Retrives the wallets of the user with the given user id.
 670 |      *
 671 |      * @param {string} userId
 672 |      * @returns {object}
 673 |      */
 674 |     async getUserWallets (userId) {
 675 |       let json = await this._doGetRequest(`/v1/users/${userId}/wallets/`)
 676 |       return fromResourceObjectsOfType(fromJsonApiData(json), 'wallets')
 677 |     }
 678 | 
 679 |     /**
 680 |      * Retrives the max withdrawal amount for the wallet with the given wallet id,
 681 |      * belonging to the user with the given user id.
 682 |      *
 683 |      * @param {string} userId
 684 |      * @param {string} walletId
 685 |      * @returns {object}
 686 |      */
 687 |     async getMaxWithdrawalForWallet (userId, walletId) {
 688 |       let json = await this._doGetRequest(`/v1/users/${userId}/wallets/${walletId}/max_withdrawal`)
 689 |       return fromResourceObject(fromJsonApiData(json), 'amounts')
 690 |     }
 691 | 
 692 |     /**
 693 |      * Creates a wallet for the user with the given user id.
 694 |      *
 695 |      * @param {string} userId
 696 |      * @param {object} attributes
 697 |      * @returns {object}
 698 |      */
 699 |     async createUserWallet (userId, attributes) {
 700 |       let body = toJsonApiData(toNewResourceObject('wallets', attributes))
 701 |       let json = await this._doPostRequest(`/v1/users/${userId}/wallets`, body)
 702 |       return fromResourceObject(fromJsonApiData(json), 'wallets')
 703 |     }
 704 | 
 705 |     /**
 706 |      * Retrieves the balance from the user with given user id.
 707 |      *
 708 |      * @param {string} userId
 709 |      * @returns {object}
 710 |      */
 711 |     async getBalance (userId) {
 712 |       const json = await this._doGetRequest(`/v1/users/${userId}/balance`)
 713 |       return fromResourceObject(fromJsonApiData(json), 'amounts')
 714 |     }
 715 | 
 716 |     /**
 717 |      * Retrives the max withdrawal amount the user with the given user id.
 718 |      *
 719 |      * @param {string} userId
 720 |      * @returns {object}
 721 |      */
 722 |     async getMaxWithdrawal (userId) {
 723 |       let json = await this._doGetRequest(`/v1/users/${userId}/max_withdrawal`)
 724 |       return fromResourceObject(fromJsonApiData(json), 'amounts')
 725 |     }
 726 | 
 727 |     /**
 728 |      * Retrives a recieve address for the user with the given user id.
 729 |      *
 730 |      * @param {string} userId
 731 |      * @param {string} walletId
 732 |      * @returns {object}
 733 |      */
 734 |     async getReceiveAddress (userId, walletId) {
 735 |       let json = await this._doPostRequest(
 736 |         `/v1/users/${userId}/wallets/${walletId}/receive_address`
 737 |       )
 738 |       let { address } = fromResourceObject(fromJsonApiData(json), 'addresses')
 739 |       return address
 740 |     }
 741 | 
 742 |     /**
 743 |      * Converts a (curreny,amount) pair into the given user's default currency.
 744 |      *
 745 |      * @param {string} userId
 746 |      * @param {object} attributes
 747 |      * @returns {object}
 748 |      */
 749 |     async getCurrencyAmount (userId, attributes) {
 750 |       const body = toJsonApiData(toNewResourceObject('currency', attributes))
 751 |       const json = await this._doPostRequest(
 752 |         `/v1/users/${userId}/currency`,
 753 |         body
 754 |       )
 755 |       const { amount, currency } = fromResourceObject(
 756 |         fromJsonApiData(json),
 757 |         'currency'
 758 |       )
 759 |       return { amount, currency }
 760 |     }
 761 | 
 762 |     /**
 763 |      * Retrives the balance for the wallet with the given wallet id,
 764 |      * belonging to the user with the given user id.
 765 |      *
 766 |      * @param {string} userId
 767 |      * @param {string} walletId
 768 |      * @returns {object}
 769 |      */
 770 |     async getWalletBalance (userId, walletId) {
 771 |       const json = await this._doGetRequest(
 772 |         `/v1/users/${userId}/wallets/${walletId}/balance`
 773 |       )
 774 |       return fromResourceObject(fromJsonApiData(json), 'amounts')
 775 |     }
 776 | 
 777 |     /**
 778 |      * Updates the wallet with the given wallet id, belonging to the user
 779 |      * with the given user id.
 780 |      *
 781 |      * @param {string} userId
 782 |      * @param {string} walletId
 783 |      * @param {object} attributes
 784 |      * @returns {object}
 785 |      */
 786 |     async updateWallet (userId, walletId, attributes) {
 787 |       const body = toJsonApiData(
 788 |         toResourceObject(walletId, 'wallets', attributes)
 789 |       )
 790 |       await this._doPatchRequest(
 791 |         `/v1/users/${userId}/wallets/${walletId}`,
 792 |         body
 793 |       )
 794 |     }
 795 | 
 796 |     /**
 797 |      * Retrieves the payments from the user with the given user id.
 798 |      *
 799 |      * @param {string} userId
 800 |      * @param {object} paginate
 801 |      * @returns {object}
 802 |      */
 803 |     async getUserPayments (userId, paginate) {
 804 |       const json = await this._doGetRequest(
 805 |         `/v1/users/${userId}/payments?${this._paginateUri(paginate)}`
 806 |       )
 807 |       return {
 808 |         pages: json.meta['total-pages'],
 809 |         payments: json.data.map(payment =>
 810 |           fromResourceObject(payment, 'payments')
 811 |         )
 812 |       }
 813 |     }
 814 | 
 815 |     /**
 816 |      * @private
 817 |      * @returns {string}
 818 |      */
 819 |     _paginateUri ({ number, size, sort }) {
 820 |       // NOTE: query-string does not support the nesting format used in JsonApi
 821 |       // https://github.com/sindresorhus/query-string#nesting
 822 |       // http://jsonapi.org/examples/#pagination
 823 |       // http://jsonapi.org/format/#fetching-pagination
 824 |       const url = []
 825 |       if (number) url.push(`page[number]=${number}`)
 826 |       if (size) url.push(`page[size]=${size}`)
 827 |       if (sort) url.push(`sort=${sort}`)
 828 |       return url.join('&')
 829 |     }
 830 | 
 831 |     /**
 832 |      * Creates a payment for the user with the given user id to the specified payment
 833 |      * outputs.
 834 |      *
 835 |      * @param {string} userId
 836 |      * @param {object} attributes
 837 |      * @param {array} paymentOutputs
 838 |      * @returns {object}
 839 |      */
 840 |     async createUserPayment (userId, attributes, paymentOutputs) {
 841 |       let body = toJsonApiDataIncluding(
 842 |         toNewResourceObject('payments', attributes),
 843 |         paymentOutputs.map(paymentOutput => {
 844 |           return toNewResourceObject('payment_outputs', paymentOutput)
 845 |         })
 846 |       )
 847 |       let json = await this._doPostRequest(`/v1/users/${userId}/payments`, body)
 848 |       let { data, included } = fromJsonApiDataIncluding(json)
 849 |       let payment = fromResourceObject(data, 'payments')
 850 |       let [bsvTransaction] = fromResourceObjectsOfType(included, 'bsv_transactions')
 851 |       let addressIndexes = fromResourceObjectsOfType(
 852 |         included,
 853 |         'address_indexes'
 854 |       )
 855 |         .sort((a, b) => a.index - b.index)
 856 |         .map(addressIndex => addressIndex.addressIndex)
 857 |       return {
 858 |         payment,
 859 |         paymentOutputs: fromResourceObjectsOfType(included, 'payment_outputs'),
 860 |         bsvTransaction,
 861 |         addressIndexes
 862 |       }
 863 |     }
 864 | 
 865 |     /**
 866 |      * Retrives the payment with the given payment id, belonging to the user with
 867 |      * the given user id.
 868 |      *
 869 |      * @param {string} userId
 870 |      * @param {string} paymentId
 871 |      * @returns {object}
 872 |      */
 873 |     async getUserPayment (userId, paymentId) {
 874 |       let json = await this._doGetRequest(`/v1/users/${userId}/payments/${paymentId}`)
 875 |       let { data, included } = fromJsonApiDataIncluding(json)
 876 |       const payment = fromResourceObject(data, 'payments')
 877 |       payment.outputs = fromResourceObjectsOfType(included, 'payment_outputs')
 878 |       return payment
 879 |     }
 880 | 
 881 |     /**
 882 |      * Updates the payment with the given payment id, belonging to the user with
 883 |      * the given user id.
 884 |      *
 885 |      * @param {string} userId
 886 |      * @param {string} paymentId
 887 |      * @param {object} attributes
 888 |      * @param {bsv.Transaction} bsvTransaction
 889 |      * @returns {object}
 890 |      */
 891 |     async updateUserPaymentWithTransaction (
 892 |       userId,
 893 |       paymentId,
 894 |       attributes,
 895 |       bsvTransaction
 896 |     ) {
 897 |       let body = toJsonApiDataIncluding(
 898 |         toResourceObject(paymentId, 'payments', attributes),
 899 |         [toResourceObject(
 900 |           bsvTransaction.hash,
 901 |           'bsv_transactions',
 902 |           bsvTransaction
 903 |         )]
 904 |       )
 905 |       let json = await this._doPatchRequest(
 906 |         `/v1/users/${userId}/payments/${paymentId}`,
 907 |         body
 908 |       )
 909 |       return fromResourceObject(fromJsonApiData(json), 'payments')
 910 |     }
 911 | 
 912 |     /**
 913 |      * Creates a deposit for the user with the given id.
 914 |      *
 915 |      * @param {string} userId
 916 |      * @param {object} attributes
 917 |      * @returns {object}
 918 |      */
 919 |     async createUserDeposit (userId, attributes) {
 920 |       const body = toJsonApiData(toNewResourceObject('deposits', attributes))
 921 |       const json = await this._doPostRequest(
 922 |         `/v1/users/${userId}/deposits`,
 923 |         body
 924 |       )
 925 |       return fromResourceObject(fromJsonApiData(json), 'deposits')
 926 |     }
 927 | 
 928 |     /**
 929 |      * Retrives the deposit with the given deposit id, belonging to the user with
 930 |      * the given user id.
 931 |      *
 932 |      * @param {string} userId
 933 |      * @param {string} depositId
 934 |      * @returns {object}
 935 |      */
 936 |     async getUserDeposit (userId, depositId) {
 937 |       const json = await this._doGetRequest(
 938 |         `/v1/users/${userId}/deposits/${depositId}`
 939 |       )
 940 |       return fromResourceObject(fromJsonApiData(json), 'deposits')
 941 |     }
 942 | 
 943 |     /**
 944 |      *
 945 |      * @param {string} userId
 946 |      * @param {object} attributes
 947 |      * @returns {object}
 948 |      */
 949 |     async createUserWithdrawal (userId, attributes) {
 950 |       let body = toJsonApiData(toNewResourceObject('withdrawals', attributes))
 951 |       let json = await this._doPostRequest(
 952 |         `/v1/users/${userId}/withdrawals`,
 953 |         body
 954 |       )
 955 |       let { data, included } = fromJsonApiDataIncluding(json)
 956 |       let withdrawal = fromResourceObject(data, 'withdrawals')
 957 |       let [bsvTransaction] = fromResourceObjectsOfType(included, 'bsv_transactions')
 958 |       let addressIndexes = fromResourceObjectsOfType(
 959 |         included,
 960 |         'address_indexes'
 961 |       )
 962 |         .sort((a, b) => a.index - b.index)
 963 |         .map(addressIndex => addressIndex.addressIndex)
 964 |       return {
 965 |         withdrawal,
 966 |         bsvTransaction,
 967 |         addressIndexes
 968 |       }
 969 |     }
 970 | 
 971 |     /**
 972 |      * Retrives the withdrawal with the given withdrawal id, belonging to the user with
 973 |      * the given user id.
 974 |      *
 975 |      * @param {string} userId
 976 |      * @param {string} withdrawalId
 977 |      * @returns {object}
 978 |      */
 979 |     async getUserWithdrawal (userId, withdrawalId) {
 980 |       const json = await this._doGetRequest(
 981 |         `/v1/users/${userId}/withdrawals/${withdrawalId}`
 982 |       )
 983 |       return fromResourceObject(fromJsonApiData(json), 'withdrawals')
 984 |     }
 985 | 
 986 |     /**
 987 |      * Updates the withdrawal with the given withdrawal id, belonging to the user with
 988 |      * the given user id.
 989 |      *
 990 |      * @param {string} userId
 991 |      * @param {string} withdrawalId
 992 |      * @param {object} attributes
 993 |      * @param {object} transaction
 994 |      * @returns {object}
 995 |      */
 996 |     async updateUserWithdrawalWithTransaction (
 997 |       userId,
 998 |       withdrawalId,
 999 |       attributes,
1000 |       transaction
1001 |     ) {
1002 |       let body = toJsonApiDataIncluding(
1003 |         toResourceObject(withdrawalId, 'withdrawals', attributes),
1004 |         [toResourceObject(uuid.v1(), 'transactions', transaction)]
1005 |       )
1006 |       let json = await this._doPatchRequest(
1007 |         `/v1/users/${userId}/withdrawals/${withdrawalId}`,
1008 |         body
1009 |       )
1010 |       return fromResourceObject(fromJsonApiData(json), 'withdrawals')
1011 |     }
1012 | 
1013 |     /**
1014 |      * Broadcasts the given bsv transaction. The transaction must be fully signed.
1015 |      *
1016 |      * @param {bsv.Transaction} bsvTransaction
1017 |      * @returns {object}
1018 |      */
1019 |     async broadcastTransaction (bsvTransaction) {
1020 |       const body = toJsonApiData(toResourceObject(
1021 |         bsvTransaction.hash,
1022 |         'bsv_transactions',
1023 |         bsvTransaction
1024 |       ))
1025 |       const json = await this._doPostRequest(
1026 |         '/v1/transactions/broadcast',
1027 |         body
1028 |       )
1029 |       return fromResourceObject(fromJsonApiData(json), 'txids')
1030 |     }
1031 | 
1032 |     /**
1033 |      * Retrieves the list of supported cryptocurrencies.
1034 |      *
1035 |      * @param {object} query
1036 |      * @returns {array}
1037 |      */
1038 |     async getSupportedCryptocurrencies (query = {}) {
1039 |       const json = await this._doGetRequest('/v1/currencies/crypto', query)
1040 |       return fromResourceObjectsOfType(fromJsonApiData(json), 'currencies')
1041 |     }
1042 | 
1043 |     /**
1044 |      * Retrieves the list of supported fiat currencies.
1045 |      *
1046 |      * @param {object} query
1047 |      * @returns {array}
1048 |      */
1049 |     async getSupportedFiatCurrencies (query = {}) {
1050 |       const json = await this._doGetRequest('/v1/currencies/fiat', query)
1051 |       return fromResourceObjectsOfType(fromJsonApiData(json), 'currencies')
1052 |     }
1053 | 
1054 |     /**
1055 |      * @private
1056 |      * @param {string} endpoint - REST API relative endpoint.
1057 |      * @param {object} query - URL query parameters.
1058 |      * @param {string} accessToken - auth API access token
1059 |      * @returns {object}
1060 |      */
1061 |     async _doGetRequest (endpoint, query = {}, accessToken = null) {
1062 |       let opts = {
1063 |         method: 'GET'
1064 |       }
1065 |       return this._doRequest(endpoint, opts, query, accessToken)
1066 |     }
1067 | 
1068 |     /**
1069 |      * @private
1070 |      * @param {string} endpoint - REST API relative endpoint.
1071 |      * @param {object} body - fetch request's body.
1072 |      * @param {object} query - URL query parameters.
1073 |      * @param {string} accessToken - auth API access token
1074 |      * @returns {object}
1075 |      */
1076 |     async _doPostRequest (endpoint, body = {}, query = {}, accessToken = null) {
1077 |       let opts = {
1078 |         method: 'POST',
1079 |         body: JSON.stringify(body)
1080 |       }
1081 |       return this._doRequest(endpoint, opts, query, accessToken)
1082 |     }
1083 | 
1084 |     /**
1085 |      * @private
1086 |      * @param {string} endpoint - REST API relative endpoint.
1087 |      * @param {object} body - fetch request's body.
1088 |      * @param {string} accessToken - auth API access token
1089 |      * @returns {object}
1090 |      */
1091 |     async _doPatchRequest (endpoint, body = {}, accessToken = null) {
1092 |       let opts = {
1093 |         method: 'PATCH',
1094 |         body: JSON.stringify(body)
1095 |       }
1096 |       return this._doRequest(endpoint, opts, {}, accessToken)
1097 |     }
1098 | 
1099 |     /**
1100 |      * @private
1101 |      * @param {string} endpoint - REST API relative endpoint.
1102 |      * @param {object} body - fetch request's body.
1103 |      * @param {string} accessToken - auth API access token
1104 |      * @returns {object}
1105 |      */
1106 |     async _doPutRequest (endpoint, body = {}, accessToken = null) {
1107 |       let opts = {
1108 |         method: 'PUT',
1109 |         body: JSON.stringify(body)
1110 |       }
1111 |       return this._doRequest(endpoint, opts, {}, accessToken)
1112 |     }
1113 | 
1114 |     /**
1115 |      *
1116 |      * @param {string} endpoint - REST API relative endpoint.
1117 |      * @param {object} opts - fetch request options.
1118 |      * @param {object} query - URL query parameters.
1119 |      * @param {string} accessToken - auth API access token
1120 |      * @returns {object}
1121 |      */
1122 |     async _doRequest (endpoint, opts = {}, query = {}, accessToken = null) {
1123 |       const url = this._appendQuery(`${API_REST_URI}/api${endpoint}`, query)
1124 |       let headers = {
1125 |         'Content-Type': 'application/vnd.api+json',
1126 |         Accept: 'application/vnd.api+json'
1127 |       }
1128 |       accessToken = accessToken === null
1129 |         ? await this.getValidAccessToken()
1130 |         : accessToken
1131 |       if (accessToken !== null) {
1132 |         headers['Authorization'] = `Bearer ${accessToken}`
1133 |       }
1134 |       const res = await fetch(url, { ...opts, headers })
1135 |       let json = await res.json()
1136 |       let { errors } = json
1137 |       if (errors instanceof Array) {
1138 |         let error = errors[0]
1139 |         if (error.status) {
1140 |           let { status, title, detail } = error
1141 |           throw new RestError(status, title, detail)
1142 |         }
1143 |         throw new Error(error.title)
1144 |       }
1145 |       return json
1146 |     }
1147 | 
1148 |     /**
1149 |      * @private
1150 |      * @param {string} url - base URL where query will be appended.
1151 |      * @param {object} query - URL query parameters.
1152 |      * @returns {string}
1153 |      */
1154 |     _appendQuery (url, query = {}) {
1155 |       if (Object.keys(query).length === 0) {
1156 |         return url
1157 |       }
1158 |       const { page, ...queryWithoutPage } = query
1159 |       if (page !== undefined) {
1160 |         for (const key in page) {
1161 |           queryWithoutPage[`page[${key}]`] = page[key]
1162 |         }
1163 |       }
1164 |       return `${url}?${queryString.stringify(queryWithoutPage)}`
1165 |     }
1166 | 
1167 |     /**
1168 |     *
1169 |     * Web location utilities.
1170 |     *
1171 |     */
1172 | 
1173 |     /**
1174 |      *
1175 |      */
1176 |     _getUrlQuery () {
1177 |       return queryString.parse(webLocation.search)
1178 |     }
1179 | 
1180 |     /**
1181 |      *
1182 |      * @param {string} uri - URI where the browser will be redirected to.
1183 |      */
1184 |     _redirectToUri (uri) {
1185 |       webLocation.href = uri
1186 |     }
1187 | 
1188 |     /**
1189 |     *
1190 |     * Web storage utilities.
1191 |     *
1192 |     */
1193 | 
1194 |     /**
1195 |      * @private
1196 |      * @returns {string}
1197 |      */
1198 |     _getRedirectUri () {
1199 |       return webStorage.getItem(OAUTH_REDIRECT_URI_KEY)
1200 |     }
1201 | 
1202 |     /**
1203 |      * @private
1204 |      * @param {string} redirectUri - OAuth redirect URI from authorization grant flow.
1205 |      * @returns {undefined}
1206 |      */
1207 |     _setRedirectUri (redirectUri) {
1208 |       webStorage.setItem(OAUTH_REDIRECT_URI_KEY, redirectUri)
1209 |     }
1210 | 
1211 |     /**
1212 |      * @private
1213 |      * @returns {undefined}
1214 |      */
1215 |     _clearRedirectUri () {
1216 |       webStorage.removeItem(OAUTH_REDIRECT_URI_KEY)
1217 |     }
1218 | 
1219 |     /**
1220 |      * @private
1221 |      * @returns {undefined}
1222 |      */
1223 |     _getState () {
1224 |       return webStorage.getItem(OAUTH_STATE_KEY)
1225 |     }
1226 | 
1227 |     /**
1228 |      * @private
1229 |      * @param {string} state - OAuth state from authorization grant flow.
1230 |      * @returns {undefined}
1231 |      */
1232 |     _setState (state) {
1233 |       webStorage.setItem(OAUTH_STATE_KEY, state)
1234 |     }
1235 | 
1236 |     /**
1237 |      * @private
1238 |      * @returns {undefined}
1239 |      */
1240 |     _clearState () {
1241 |       webStorage.removeItem(OAUTH_STATE_KEY)
1242 |     }
1243 | 
1244 |     /**
1245 |      * Retrieves the currently-set access token.
1246 |      *
1247 |      * @returns {string}
1248 |      */
1249 |     getAccessToken () {
1250 |       return webStorage.getItem(OAUTH_ACCESS_TOKEN_KEY)
1251 |     }
1252 | 
1253 |     /**
1254 |      * Sets the given access token.
1255 |      *
1256 |      * @param {string} accessToken - auth API access token
1257 |      * @returns {undefined}
1258 |      */
1259 |     setAccessToken (accessToken) {
1260 |       webStorage.setItem(OAUTH_ACCESS_TOKEN_KEY, accessToken)
1261 |     }
1262 | 
1263 |     /**
1264 |      * Clears the currently-set access token.
1265 |      *
1266 |      * @returns {undefined}
1267 |      */
1268 |     clearAccessToken () {
1269 |       webStorage.removeItem(OAUTH_ACCESS_TOKEN_KEY)
1270 |     }
1271 | 
1272 |     /**
1273 |      * Returns the currently-set token's expiration time in the following
1274 |      * format: 'YYYY-MM-DDTHH:mm:ssZ'.
1275 |      * For example, '2018-10-25T13:08:58-03:00'.
1276 |      *
1277 |      * @returns {string}
1278 |      */
1279 |     getExpirationTime () {
1280 |       return webStorage.getItem(OAUTH_EXPIRATION_TIME_KEY)
1281 |     }
1282 | 
1283 |     /**
1284 |      * Sets the currently-set token's expiration time. The argument must be
1285 |      * in the following format: 'YYYY-MM-DDTHH:mm:ssZ'.
1286 |      * For example, '2018-10-25T13:08:58-03:00'.
1287 |      *
1288 |      * @param {string} expirationTime
1289 |      * @returns {undefined}
1290 |      */
1291 |     setExpirationTime (expirationTime) {
1292 |       webStorage.setItem(OAUTH_EXPIRATION_TIME_KEY, expirationTime)
1293 |     }
1294 | 
1295 |     /**
1296 |      * Clears the currently-set access token's expiration time.
1297 |      *
1298 |      * @returns {undefined}
1299 |      */
1300 |     clearExpirationTime () {
1301 |       webStorage.removeItem(OAUTH_EXPIRATION_TIME_KEY)
1302 |     }
1303 | 
1304 |     /**
1305 |      * Retrieves the currently-set refresh token.
1306 |      *
1307 |      * @returns {string}
1308 |      */
1309 |     getRefreshToken () {
1310 |       return webStorage.getItem(OAUTH_REFRESH_TOKEN_KEY)
1311 |     }
1312 | 
1313 |     /**
1314 |      * Sets the given refresh token.
1315 |      *
1316 |      * @param {string} refreshToken - auth API refresh token
1317 |      * @returns {undefined}
1318 |      */
1319 |     setRefreshToken (refreshToken) {
1320 |       webStorage.setItem(OAUTH_REFRESH_TOKEN_KEY, refreshToken)
1321 |     }
1322 | 
1323 |     /**
1324 |      * Clears the currently-set refresh token.
1325 |      * @returns {undefined}
1326 |      */
1327 |     clearRefreshToken () {
1328 |       webStorage.removeItem(OAUTH_REFRESH_TOKEN_KEY)
1329 |     }
1330 | 
1331 |     /**
1332 |     *
1333 |     * Web crypto utilities.
1334 |     *
1335 |     */
1336 | 
1337 |     /**
1338 |      * @private
1339 |      * @param {string} key - HMAC key.
1340 |      * @param {string} message- HMAC message.
1341 |      * @returns {string}
1342 |      */
1343 |     static async _computeHmac256 (key, message) {
1344 |       let cryptoKey = await webCrypto.subtle.importKey(
1345 |         'raw',
1346 |         Buffer.from(key),
1347 |         {
1348 |           name: 'HMAC',
1349 |           hash: { name: 'SHA-256' }
1350 |         },
1351 |         false,
1352 |         ['sign', 'verify']
1353 |       )
1354 |       let signature = await webCrypto.subtle.sign(
1355 |         'HMAC',
1356 |         cryptoKey,
1357 |         Buffer.from(message)
1358 |       )
1359 |       return Buffer.from(new Uint8Array(signature)).toString('hex')
1360 |     }
1361 |   }
1362 | 
1363 |   return MoneyButtonClient
1364 | }
1365 | 
1366 | // TODO(ealmansi): fix warning "(!) Mixing named and default exports."
1367 | export { AuthError, RestError }
1368 | 
1369 |
1370 |
1371 | 1372 | 1373 | 1374 | 1375 |
1376 | 1377 | 1380 | 1381 |
1382 | 1383 | 1386 | 1387 | 1388 | 1389 | 1390 | 1391 | -------------------------------------------------------------------------------- /docs/module.html#.exports: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Class: exports 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Class: exports

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 |
30 | 31 |

exports(title, detail)

32 | 33 |
Authentication API error.
34 | 35 | 36 |
37 | 38 |
39 |
40 | 41 | 42 | 43 | 44 |

Constructor

45 | 46 | 47 | 48 |

new exports(title, detail)

49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
Parameters:
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 |
NameTypeDescription
title 92 | 93 | 94 | string 95 | 96 | 97 | 98 | Error title.
detail 115 | 116 | 117 | string 118 | 119 | 120 | 121 | Error detail.
133 | 134 | 135 | 136 | 137 | 138 | 139 |
140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 |
Source:
167 |
170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 |
178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 |
198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 |
219 | 220 |
221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 |
229 | 230 |
231 | 232 |

exports(status, title, detail)

233 | 234 |
REST API error.
235 | 236 | 237 |
238 | 239 |
240 |
241 | 242 | 243 | 244 | 245 |

Constructor

246 | 247 | 248 | 249 |

new exports(status, title, detail)

250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 |
Parameters:
265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 |
NameTypeDescription
status 293 | 294 | 295 | number 296 | 297 | 298 | 299 | HTTP status code.
title 316 | 317 | 318 | string 319 | 320 | 321 | 322 | Error title.
detail 339 | 340 | 341 | string 342 | 343 | 344 | 345 | Error detail.
357 | 358 | 359 | 360 | 361 | 362 | 363 |
364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 |
Source:
391 |
394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 |
402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 |
422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 |
443 | 444 |
445 | 446 | 447 | 448 | 449 |
450 | 451 | 454 | 455 |
456 | 457 | 460 | 461 | 462 | 463 | 464 | -------------------------------------------------------------------------------- /docs/rest-error.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Source: rest-error.js 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Source: rest-error.js

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
/**
30 |  * REST API error.
31 |  */
32 | export default class RestError {
33 |   /**
34 |    *
35 |    * @param {number} status - HTTP status code.
36 |    * @param {string} title - Error title.
37 |    * @param {string} detail - Error detail.
38 |    */
39 |   constructor (status, title, detail) {
40 |     this.status = status
41 |     this.title = title
42 |     this.detail = detail
43 |     this.message = detail !== undefined ? detail : title
44 |   }
45 | }
46 | 
47 |
48 |
49 | 50 | 51 | 52 | 53 |
54 | 55 | 58 | 59 |
60 | 61 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /docs/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /* global document */ 2 | (function () { 3 | var source = document.getElementsByClassName('prettyprint source linenums') 4 | var i = 0 5 | var lineNumber = 0 6 | var lineId 7 | var lines 8 | var totalLines 9 | var anchorHash 10 | 11 | if (source && source[0]) { 12 | anchorHash = document.location.hash.substring(1) 13 | lines = source[0].getElementsByTagName('li') 14 | totalLines = lines.length 15 | 16 | for (; i < totalLines; i++) { 17 | lineNumber++ 18 | lineId = 'line' + lineNumber 19 | lines[i].id = lineId 20 | if (lineId === anchorHash) { 21 | lines[i].className += ' selected' 22 | } 23 | } 24 | } 25 | })() 26 | -------------------------------------------------------------------------------- /docs/scripts/prettify/Apache-License-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /docs/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([['pln', /^[\t\n\f\r ]+/, null, ' \t\r\n ']], [['str', /^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/, null], ['str', /^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/, null], ['lang-css-str', /^url\(([^"')]*)\)/i], ['kwd', /^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i, null], ['lang-css-kw', /^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i], ['com', /^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//], ['com', 2 | /^(?:<\!--|--\>)/], ['lit', /^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i], ['lit', /^#[\da-f]{3,6}/i], ['pln', /^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i], ['pun', /^[^\s\w"']+/]]), ['css']); PR.registerLangHandler(PR.createSimpleLexer([], [['kwd', /^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]), ['css-kw']); PR.registerLangHandler(PR.createSimpleLexer([], [['str', /^[^"')]+/]]), ['css-str']) 3 | -------------------------------------------------------------------------------- /docs/scripts/prettify/prettify.js: -------------------------------------------------------------------------------- 1 | var q = null; window.PR_SHOULD_USE_CONTINUATION = !0; 2 | (function () { 3 | function L (a) { 4 | function m (a) { var f = a.charCodeAt(0); if (f !== 92) return f; var b = a.charAt(1); return (f = r[b]) ? f : b >= '0' && b <= '7' ? parseInt(a.substring(1), 8) : b === 'u' || b === 'x' ? parseInt(a.substring(2), 16) : a.charCodeAt(1) } function e (a) { if (a < 32) return (a < 16 ? '\\x0' : '\\x') + a.toString(16); a = String.fromCharCode(a); if (a === '\\' || a === '-' || a === '[' || a === ']')a = '\\' + a; return a } function h (a) { 5 | for (var f = a.substring(1, a.length - 1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g), a = 6 | [], b = [], o = f[0] === '^', c = o ? 1 : 0, i = f.length; c < i; ++c) { var j = f[c]; if (/\\[bdsw]/i.test(j))a.push(j); else { var j = m(j); var d; c + 2 < i && f[c + 1] === '-' ? (d = m(f[c + 2]), c += 2) : d = j; b.push([j, d]); d < 65 || j > 122 || (d < 65 || j > 90 || b.push([Math.max(65, j) | 32, Math.min(d, 90) | 32]), d < 97 || j > 122 || b.push([Math.max(97, j) & -33, Math.min(d, 122) & -33])) } }b.sort(function (a, f) { return a[0] - f[0] || f[1] - a[1] }); f = []; j = [NaN, NaN]; for (c = 0; c < b.length; ++c)i = b[c], i[0] <= j[1] + 1 ? j[1] = Math.max(j[1], i[1]) : f.push(j = i); b = ['[']; o && b.push('^'); b.push.apply(b, a); for (c = 0; c < 7 | f.length; ++c)i = f[c], b.push(e(i[0])), i[1] > i[0] && (i[1] + 1 > i[0] && b.push('-'), b.push(e(i[1]))); b.push(']'); return b.join('') 8 | } function y (a) { 9 | for (var f = a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g), b = f.length, d = [], c = 0, i = 0; c < b; ++c) { var j = f[c]; j === '(' ? ++i : j.charAt(0) === '\\' && (j = +j.substring(1)) && j <= i && (d[j] = -1) } for (c = 1; c < d.length; ++c)d[c] === -1 && (d[c] = ++t); for (i = c = 0; c < b; ++c) { 10 | j = f[c], j === '(' ? (++i, d[i] === void 0 && (f[c] = '(?:')) : j.charAt(0) === '\\' && 11 | (j = +j.substring(1)) && j <= i && (f[c] = '\\' + d[i]) 12 | } for (i = c = 0; c < b; ++c)f[c] === '^' && f[c + 1] !== '^' && (f[c] = ''); if (a.ignoreCase && s) for (c = 0; c < b; ++c)j = f[c], a = j.charAt(0), j.length >= 2 && a === '[' ? f[c] = h(j) : a !== '\\' && (f[c] = j.replace(/[A-Za-z]/g, function (a) { a = a.charCodeAt(0); return '[' + String.fromCharCode(a & -33, a | 32) + ']' })); return f.join('') 13 | } for (var t = 0, s = !1, l = !1, p = 0, d = a.length; p < d; ++p) { var g = a[p]; if (g.ignoreCase)l = !0; else if (/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi, ''))) { s = !0; l = !1; break } } for (var r = 14 | { b: 8, t: 9, n: 10, v: 11, f: 12, r: 13 }, n = [], p = 0, d = a.length; p < d; ++p) { g = a[p]; if (g.global || g.multiline) throw Error('' + g); n.push('(?:' + y(g) + ')') } return RegExp(n.join('|'), l ? 'gi' : 'g') 15 | } function M (a) { 16 | function m (a) { 17 | switch (a.nodeType) { 18 | case 1:if (e.test(a.className)) break; for (var g = a.firstChild; g; g = g.nextSibling)m(g); g = a.nodeName; if (g === 'BR' || g === 'LI')h[s] = '\n', t[s << 1] = y++, t[s++ << 1 | 1] = a; break; case 3:case 4:g = a.nodeValue, g.length && (g = p ? g.replace(/\r\n?/g, '\n') : g.replace(/[\t\n\r ]+/g, ' '), h[s] = g, t[s << 1] = y, y += g.length, 19 | t[s++ << 1 | 1] = a) 20 | } 21 | } var e = /(?:^|\s)nocode(?:\s|$)/; var h = []; var y = 0; var t = []; var s = 0; var l; a.currentStyle ? l = a.currentStyle.whiteSpace : window.getComputedStyle && (l = document.defaultView.getComputedStyle(a, q).getPropertyValue('white-space')); var p = l && l.substring(0, 3) === 'pre'; m(a); return { a: h.join('').replace(/\n$/, ''), c: t } 22 | } function B (a, m, e, h) { m && (a = { a: m, d: a }, e(a), h.push.apply(h, a.e)) } function x (a, m) { 23 | function e (a) { 24 | for (var l = a.d, p = [l, 'pln'], d = 0, g = a.a.match(y) || [], r = {}, n = 0, z = g.length; n < z; ++n) { 25 | var f = g[n]; var b = r[f]; var o = void 0; var c; if (typeof b === 26 | 'string')c = !1; else { var i = h[f.charAt(0)]; if (i)o = f.match(i[1]), b = i[0]; else { for (c = 0; c < t; ++c) if (i = m[c], o = f.match(i[1])) { b = i[0]; break }o || (b = 'pln') } if ((c = b.length >= 5 && b.substring(0, 5) === 'lang-') && !(o && typeof o[1] === 'string'))c = !1, b = 'src'; c || (r[f] = b) }i = d; d += f.length; if (c) { c = o[1]; var j = f.indexOf(c); var k = j + c.length; o[2] && (k = f.length - o[2].length, j = k - c.length); b = b.substring(5); B(l + i, f.substring(0, j), e, p); B(l + i + j, c, C(b, c), p); B(l + i + k, f.substring(k), e, p) } else p.push(l + i, b) 27 | }a.e = p 28 | } var h = {}; var y; (function () { 29 | for (var e = a.concat(m), 30 | l = [], p = {}, d = 0, g = e.length; d < g; ++d) { var r = e[d]; var n = r[3]; if (n) for (var k = n.length; --k >= 0;)h[n.charAt(k)] = r; r = r[1]; n = '' + r; p.hasOwnProperty(n) || (l.push(r), p[n] = q) }l.push(/[\S\s]/); y = L(l) 31 | })(); var t = m.length; return e 32 | } function u (a) { 33 | var m = []; var e = []; a.tripleQuotedStrings ? m.push(['str', /^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/, q, "'\""]) : a.multiLineStrings ? m.push(['str', /^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 34 | q, "'\"`"]) : m.push(['str', /^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/, q, "\"'"]); a.verbatimStrings && e.push(['str', /^@"(?:[^"]|"")*(?:"|$)/, q]); var h = a.hashComments; h && (a.cStyleComments ? (h > 1 ? m.push(['com', /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, q, '#']) : m.push(['com', /^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/, q, '#']), e.push(['str', /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/, q])) : m.push(['com', /^#[^\n\r]*/, 35 | q, '#'])); a.cStyleComments && (e.push(['com', /^\/\/[^\n\r]*/, q]), e.push(['com', /^\/\*[\S\s]*?(?:\*\/|$)/, q])); a.regexLiterals && e.push(['lang-regex', /^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]); (h = a.types) && e.push(['typ', h]); a = ('' + a.keywords).replace(/^ | $/g, 36 | ''); a.length && e.push(['kwd', RegExp('^(?:' + a.replace(/[\s,]+/g, '|') + ')\\b'), q]); m.push(['pln', /^\s+/, q, ' \r\n\t\xa0']); e.push(['lit', /^@[$_a-z][\w$@]*/i, q], ['typ', /^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/, q], ['pln', /^[$_a-z][\w$@]*/i, q], ['lit', /^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i, q, '0123456789'], ['pln', /^\\[\S\s]?/, q], ['pun', /^.[^\s\w"-$'./@\\`]*/, q]); return x(m, e) 37 | } function D (a, m) { 38 | function e (a) { 39 | switch (a.nodeType) { 40 | case 1:if (k.test(a.className)) break; if (a.nodeName === 'BR') { 41 | h(a), 42 | a.parentNode && a.parentNode.removeChild(a) 43 | } else for (a = a.firstChild; a; a = a.nextSibling)e(a); break; case 3:case 4:if (p) { var b = a.nodeValue; var d = b.match(t); if (d) { var c = b.substring(0, d.index); a.nodeValue = c; (b = b.substring(d.index + d[0].length)) && a.parentNode.insertBefore(s.createTextNode(b), a.nextSibling); h(a); c || a.parentNode.removeChild(a) } } 44 | } 45 | } function h (a) { 46 | function b (a, d) { var e = d ? a.cloneNode(!1) : a; var f = a.parentNode; if (f) { var f = b(f, 1); var g = a.nextSibling; f.appendChild(e); for (var h = g; h; h = g)g = h.nextSibling, f.appendChild(h) } return e } 47 | for (;!a.nextSibling;) if (a = a.parentNode, !a) return; for (var a = b(a.nextSibling, 0), e; (e = a.parentNode) && e.nodeType === 1;)a = e; d.push(a) 48 | } var k = /(?:^|\s)nocode(?:\s|$)/; var t = /\r\n?|\n/; var s = a.ownerDocument; var l; a.currentStyle ? l = a.currentStyle.whiteSpace : window.getComputedStyle && (l = s.defaultView.getComputedStyle(a, q).getPropertyValue('white-space')); var p = l && l.substring(0, 3) === 'pre'; for (l = s.createElement('LI'); a.firstChild;)l.appendChild(a.firstChild); for (var d = [l], g = 0; g < d.length; ++g)e(d[g]); m === (m | 0) && d[0].setAttribute('value', 49 | m); var r = s.createElement('OL'); r.className = 'linenums'; for (var n = Math.max(0, m - 1 | 0) || 0, g = 0, z = d.length; g < z; ++g)l = d[g], l.className = 'L' + (g + n) % 10, l.firstChild || l.appendChild(s.createTextNode('\xa0')), r.appendChild(l); a.appendChild(r) 50 | } function k (a, m) { for (var e = m.length; --e >= 0;) { var h = m[e]; A.hasOwnProperty(h) ? window.console && console.warn('cannot override language handler %s', h) : A[h] = a } } function C (a, m) { if (!a || !A.hasOwnProperty(a))a = /^\s*= o && (h += 2); e >= c && (a += 2) 58 | } 59 | } catch (w) { 'console' in window && console.log(w && w.stack ? w.stack : w) } 60 | } var v = ['break,continue,do,else,for,if,return,while']; var w = [[v, 'auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile'], 61 | 'catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof']; var F = [w, 'alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where']; var G = [w, 'abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient'] 62 | 63 | var H = [G, 'as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var']; var w = [w, 'debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN']; var I = [v, 'and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None'] 64 | 65 | var J = [v, 'alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END']; var v = [v, 'case,done,elif,esac,eval,fi,function,in,local,set,then,until']; var K = /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/; var N = /\S/; var O = u({ keywords: [F, H, w, 'caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END' + 66 | I, J, v], 67 | hashComments: !0, 68 | cStyleComments: !0, 69 | multiLineStrings: !0, 70 | regexLiterals: !0 }); var A = {}; k(O, ['default-code']); k(x([], [['pln', /^[^]*(?:>|$)/], ['com', /^<\!--[\S\s]*?(?:--\>|$)/], ['lang-', /^<\?([\S\s]+?)(?:\?>|$)/], ['lang-', /^<%([\S\s]+?)(?:%>|$)/], ['pun', /^(?:<[%?]|[%?]>)/], ['lang-', /^]*>([\S\s]+?)<\/xmp\b[^>]*>/i], ['lang-js', /^]*>([\S\s]*?)(<\/script\b[^>]*>)/i], ['lang-css', /^]*>([\S\s]*?)(<\/style\b[^>]*>)/i], ['lang-in.tag', /^(<\/?[a-z][^<>]*>)/i]]), 71 | ['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl']); k(x([['pln', /^\s+/, q, ' \t\r\n'], ['atv', /^(?:"[^"]*"?|'[^']*'?)/, q, "\"'"]], [['tag', /^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i], ['atn', /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i], ['lang-uq.val', /^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/], ['pun', /^[/<->]+/], ['lang-js', /^on\w+\s*=\s*"([^"]+)"/i], ['lang-js', /^on\w+\s*=\s*'([^']+)'/i], ['lang-js', /^on\w+\s*=\s*([^\s"'>]+)/i], ['lang-css', /^style\s*=\s*"([^"]+)"/i], ['lang-css', /^style\s*=\s*'([^']+)'/i], ['lang-css', 72 | /^style\s*=\s*([^\s"'>]+)/i]]), ['in.tag']); k(x([], [['atv', /^[\S\s]+/]]), ['uq.val']); k(u({ keywords: F, hashComments: !0, cStyleComments: !0, types: K }), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']); k(u({ keywords: 'null,true,false' }), ['json']); k(u({ keywords: H, hashComments: !0, cStyleComments: !0, verbatimStrings: !0, types: K }), ['cs']); k(u({ keywords: G, cStyleComments: !0 }), ['java']); k(u({ keywords: v, hashComments: !0, multiLineStrings: !0 }), ['bsh', 'csh', 'sh']); k(u({ keywords: I, hashComments: !0, multiLineStrings: !0, tripleQuotedStrings: !0 }), 73 | ['cv', 'py']); k(u({ keywords: 'caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END', hashComments: !0, multiLineStrings: !0, regexLiterals: !0 }), ['perl', 'pl', 'pm']); k(u({ keywords: J, hashComments: !0, multiLineStrings: !0, regexLiterals: !0 }), ['rb']); k(u({ keywords: w, cStyleComments: !0, regexLiterals: !0 }), ['js']); k(u({ keywords: 'all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes', 74 | hashComments: 3, 75 | cStyleComments: !0, 76 | multilineStrings: !0, 77 | tripleQuotedStrings: !0, 78 | regexLiterals: !0 }), ['coffee']); k(x([], [['str', /^[\S\s]+/]]), ['regex']); window.prettyPrintOne = function (a, m, e) { var h = document.createElement('PRE'); h.innerHTML = a; e && D(h, e); E({ g: m, i: e, h: h }); return h.innerHTML }; window.prettyPrint = function (a) { 79 | function m () { 80 | for (var e = window.PR_SHOULD_USE_CONTINUATION ? l.now() + 250 : Infinity; p < h.length && l.now() < e; p++) { 81 | var n = h[p]; var k = n.className; if (k.indexOf('prettyprint') >= 0) { 82 | var k = k.match(g); var f; var b; if (b = 83 | !k) { b = n; for (var o = void 0, c = b.firstChild; c; c = c.nextSibling) var i = c.nodeType; var o = i === 1 ? o ? b : c : i === 3 ? N.test(c.nodeValue) ? b : o : o; b = (f = o === b ? void 0 : o) && f.tagName === 'CODE' }b && (k = f.className.match(g)); k && (k = k[1]); b = !1; for (o = n.parentNode; o; o = o.parentNode) if ((o.tagName === 'pre' || o.tagName === 'code' || o.tagName === 'xmp') && o.className && o.className.indexOf('prettyprint') >= 0) { b = !0; break }b || ((b = (b = n.className.match(/\blinenums\b(?::(\d+))?/)) ? b[1] && b[1].length ? +b[1] : !0 : !1) && D(n, b), d = { g: k, h: n, i: b }, E(d)) 84 | } 85 | }p < h.length ? setTimeout(m, 86 | 250) : a && a() 87 | } for (var e = [document.getElementsByTagName('pre'), document.getElementsByTagName('code'), document.getElementsByTagName('xmp')], h = [], k = 0; k < e.length; ++k) for (var t = 0, s = e[k].length; t < s; ++t)h.push(e[k][t]); var e = q; var l = Date; l.now || (l = { now: function () { return +new Date() } }); var p = 0; var d; var g = /\blang(?:uage)?-([\w.]+)(?!\S)/; m() 88 | }; window.PR = { createSimpleLexer: x, 89 | registerLangHandler: k, 90 | sourceDecorator: u, 91 | PR_ATTRIB_NAME: 'atn', 92 | PR_ATTRIB_VALUE: 'atv', 93 | PR_COMMENT: 'com', 94 | PR_DECLARATION: 'dec', 95 | PR_KEYWORD: 'kwd', 96 | PR_LITERAL: 'lit', 97 | PR_NOCODE: 'nocode', 98 | PR_PLAIN: 'pln', 99 | PR_PUNCTUATION: 'pun', 100 | PR_SOURCE: 'src', 101 | PR_STRING: 'str', 102 | PR_TAG: 'tag', 103 | PR_TYPE: 'typ' } 104 | })() 105 | -------------------------------------------------------------------------------- /docs/styles/jsdoc-default.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-weight: normal; 4 | font-style: normal; 5 | src: url('../fonts/OpenSans-Regular-webfont.eot'); 6 | src: 7 | local('Open Sans'), 8 | local('OpenSans'), 9 | url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), 10 | url('../fonts/OpenSans-Regular-webfont.woff') format('woff'), 11 | url('../fonts/OpenSans-Regular-webfont.svg#open_sansregular') format('svg'); 12 | } 13 | 14 | @font-face { 15 | font-family: 'Open Sans Light'; 16 | font-weight: normal; 17 | font-style: normal; 18 | src: url('../fonts/OpenSans-Light-webfont.eot'); 19 | src: 20 | local('Open Sans Light'), 21 | local('OpenSans Light'), 22 | url('../fonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'), 23 | url('../fonts/OpenSans-Light-webfont.woff') format('woff'), 24 | url('../fonts/OpenSans-Light-webfont.svg#open_sanslight') format('svg'); 25 | } 26 | 27 | html 28 | { 29 | overflow: auto; 30 | background-color: #fff; 31 | font-size: 14px; 32 | } 33 | 34 | body 35 | { 36 | font-family: 'Open Sans', sans-serif; 37 | line-height: 1.5; 38 | color: #4d4e53; 39 | background-color: white; 40 | } 41 | 42 | a, a:visited, a:active { 43 | color: #0095dd; 44 | text-decoration: none; 45 | } 46 | 47 | a:hover { 48 | text-decoration: underline; 49 | } 50 | 51 | header 52 | { 53 | display: block; 54 | padding: 0px 4px; 55 | } 56 | 57 | tt, code, kbd, samp { 58 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 59 | } 60 | 61 | .class-description { 62 | font-size: 130%; 63 | line-height: 140%; 64 | margin-bottom: 1em; 65 | margin-top: 1em; 66 | } 67 | 68 | .class-description:empty { 69 | margin: 0; 70 | } 71 | 72 | #main { 73 | float: left; 74 | width: 70%; 75 | } 76 | 77 | article dl { 78 | margin-bottom: 40px; 79 | } 80 | 81 | article img { 82 | max-width: 100%; 83 | } 84 | 85 | section 86 | { 87 | display: block; 88 | background-color: #fff; 89 | padding: 12px 24px; 90 | border-bottom: 1px solid #ccc; 91 | margin-right: 30px; 92 | } 93 | 94 | .variation { 95 | display: none; 96 | } 97 | 98 | .signature-attributes { 99 | font-size: 60%; 100 | color: #aaa; 101 | font-style: italic; 102 | font-weight: lighter; 103 | } 104 | 105 | nav 106 | { 107 | display: block; 108 | float: right; 109 | margin-top: 28px; 110 | width: 30%; 111 | box-sizing: border-box; 112 | border-left: 1px solid #ccc; 113 | padding-left: 16px; 114 | } 115 | 116 | nav ul { 117 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif; 118 | font-size: 100%; 119 | line-height: 17px; 120 | padding: 0; 121 | margin: 0; 122 | list-style-type: none; 123 | } 124 | 125 | nav ul a, nav ul a:visited, nav ul a:active { 126 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 127 | line-height: 18px; 128 | color: #4D4E53; 129 | } 130 | 131 | nav h3 { 132 | margin-top: 12px; 133 | } 134 | 135 | nav li { 136 | margin-top: 6px; 137 | } 138 | 139 | footer { 140 | display: block; 141 | padding: 6px; 142 | margin-top: 12px; 143 | font-style: italic; 144 | font-size: 90%; 145 | } 146 | 147 | h1, h2, h3, h4 { 148 | font-weight: 200; 149 | margin: 0; 150 | } 151 | 152 | h1 153 | { 154 | font-family: 'Open Sans Light', sans-serif; 155 | font-size: 48px; 156 | letter-spacing: -2px; 157 | margin: 12px 24px 20px; 158 | } 159 | 160 | h2, h3.subsection-title 161 | { 162 | font-size: 30px; 163 | font-weight: 700; 164 | letter-spacing: -1px; 165 | margin-bottom: 12px; 166 | } 167 | 168 | h3 169 | { 170 | font-size: 24px; 171 | letter-spacing: -0.5px; 172 | margin-bottom: 12px; 173 | } 174 | 175 | h4 176 | { 177 | font-size: 18px; 178 | letter-spacing: -0.33px; 179 | margin-bottom: 12px; 180 | color: #4d4e53; 181 | } 182 | 183 | h5, .container-overview .subsection-title 184 | { 185 | font-size: 120%; 186 | font-weight: bold; 187 | letter-spacing: -0.01em; 188 | margin: 8px 0 3px 0; 189 | } 190 | 191 | h6 192 | { 193 | font-size: 100%; 194 | letter-spacing: -0.01em; 195 | margin: 6px 0 3px 0; 196 | font-style: italic; 197 | } 198 | 199 | table 200 | { 201 | border-spacing: 0; 202 | border: 0; 203 | border-collapse: collapse; 204 | } 205 | 206 | td, th 207 | { 208 | border: 1px solid #ddd; 209 | margin: 0px; 210 | text-align: left; 211 | vertical-align: top; 212 | padding: 4px 6px; 213 | display: table-cell; 214 | } 215 | 216 | thead tr 217 | { 218 | background-color: #ddd; 219 | font-weight: bold; 220 | } 221 | 222 | th { border-right: 1px solid #aaa; } 223 | tr > th:last-child { border-right: 1px solid #ddd; } 224 | 225 | .ancestors, .attribs { color: #999; } 226 | .ancestors a, .attribs a 227 | { 228 | color: #999 !important; 229 | text-decoration: none; 230 | } 231 | 232 | .clear 233 | { 234 | clear: both; 235 | } 236 | 237 | .important 238 | { 239 | font-weight: bold; 240 | color: #950B02; 241 | } 242 | 243 | .yes-def { 244 | text-indent: -1000px; 245 | } 246 | 247 | .type-signature { 248 | color: #aaa; 249 | } 250 | 251 | .name, .signature { 252 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 253 | } 254 | 255 | .details { margin-top: 14px; border-left: 2px solid #DDD; } 256 | .details dt { width: 120px; float: left; padding-left: 10px; padding-top: 6px; } 257 | .details dd { margin-left: 70px; } 258 | .details ul { margin: 0; } 259 | .details ul { list-style-type: none; } 260 | .details li { margin-left: 30px; padding-top: 6px; } 261 | .details pre.prettyprint { margin: 0 } 262 | .details .object-value { padding-top: 0; } 263 | 264 | .description { 265 | margin-bottom: 1em; 266 | margin-top: 1em; 267 | } 268 | 269 | .code-caption 270 | { 271 | font-style: italic; 272 | font-size: 107%; 273 | margin: 0; 274 | } 275 | 276 | .prettyprint 277 | { 278 | border: 1px solid #ddd; 279 | width: 80%; 280 | overflow: auto; 281 | } 282 | 283 | .prettyprint.source { 284 | width: inherit; 285 | } 286 | 287 | .prettyprint code 288 | { 289 | font-size: 100%; 290 | line-height: 18px; 291 | display: block; 292 | padding: 4px 12px; 293 | margin: 0; 294 | background-color: #fff; 295 | color: #4D4E53; 296 | } 297 | 298 | .prettyprint code span.line 299 | { 300 | display: inline-block; 301 | } 302 | 303 | .prettyprint.linenums 304 | { 305 | padding-left: 70px; 306 | -webkit-user-select: none; 307 | -moz-user-select: none; 308 | -ms-user-select: none; 309 | user-select: none; 310 | } 311 | 312 | .prettyprint.linenums ol 313 | { 314 | padding-left: 0; 315 | } 316 | 317 | .prettyprint.linenums li 318 | { 319 | border-left: 3px #ddd solid; 320 | } 321 | 322 | .prettyprint.linenums li.selected, 323 | .prettyprint.linenums li.selected * 324 | { 325 | background-color: lightyellow; 326 | } 327 | 328 | .prettyprint.linenums li * 329 | { 330 | -webkit-user-select: text; 331 | -moz-user-select: text; 332 | -ms-user-select: text; 333 | user-select: text; 334 | } 335 | 336 | .params .name, .props .name, .name code { 337 | color: #4D4E53; 338 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 339 | font-size: 100%; 340 | } 341 | 342 | .params td.description > p:first-child, 343 | .props td.description > p:first-child 344 | { 345 | margin-top: 0; 346 | padding-top: 0; 347 | } 348 | 349 | .params td.description > p:last-child, 350 | .props td.description > p:last-child 351 | { 352 | margin-bottom: 0; 353 | padding-bottom: 0; 354 | } 355 | 356 | .disabled { 357 | color: #454545; 358 | } 359 | -------------------------------------------------------------------------------- /docs/styles/prettify-jsdoc.css: -------------------------------------------------------------------------------- 1 | /* JSDoc prettify.js theme */ 2 | 3 | /* plain text */ 4 | .pln { 5 | color: #000000; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | /* string content */ 11 | .str { 12 | color: #006400; 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | /* a keyword */ 18 | .kwd { 19 | color: #000000; 20 | font-weight: bold; 21 | font-style: normal; 22 | } 23 | 24 | /* a comment */ 25 | .com { 26 | font-weight: normal; 27 | font-style: italic; 28 | } 29 | 30 | /* a type name */ 31 | .typ { 32 | color: #000000; 33 | font-weight: normal; 34 | font-style: normal; 35 | } 36 | 37 | /* a literal value */ 38 | .lit { 39 | color: #006400; 40 | font-weight: normal; 41 | font-style: normal; 42 | } 43 | 44 | /* punctuation */ 45 | .pun { 46 | color: #000000; 47 | font-weight: bold; 48 | font-style: normal; 49 | } 50 | 51 | /* lisp open bracket */ 52 | .opn { 53 | color: #000000; 54 | font-weight: bold; 55 | font-style: normal; 56 | } 57 | 58 | /* lisp close bracket */ 59 | .clo { 60 | color: #000000; 61 | font-weight: bold; 62 | font-style: normal; 63 | } 64 | 65 | /* a markup tag name */ 66 | .tag { 67 | color: #006400; 68 | font-weight: normal; 69 | font-style: normal; 70 | } 71 | 72 | /* a markup attribute name */ 73 | .atn { 74 | color: #006400; 75 | font-weight: normal; 76 | font-style: normal; 77 | } 78 | 79 | /* a markup attribute value */ 80 | .atv { 81 | color: #006400; 82 | font-weight: normal; 83 | font-style: normal; 84 | } 85 | 86 | /* a declaration */ 87 | .dec { 88 | color: #000000; 89 | font-weight: bold; 90 | font-style: normal; 91 | } 92 | 93 | /* a variable name */ 94 | .var { 95 | color: #000000; 96 | font-weight: normal; 97 | font-style: normal; 98 | } 99 | 100 | /* a function name */ 101 | .fun { 102 | color: #000000; 103 | font-weight: bold; 104 | font-style: normal; 105 | } 106 | 107 | /* Specify class=linenums on a pre to get line numbering */ 108 | ol.linenums { 109 | margin-top: 0; 110 | margin-bottom: 0; 111 | } 112 | -------------------------------------------------------------------------------- /docs/styles/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: #718c00; } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: #8959a8; } 17 | 18 | /* a comment */ 19 | .com { 20 | color: #8e908c; } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: #4271ae; } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: #f5871f; } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #4d4d4c; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #4d4d4c; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #4d4d4c; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@moneybutton/api-client", 3 | "version": "0.38.5", 4 | "description": "Money Button API JavaScript client.", 5 | "main": "dist/moneybutton.client.cjs.js", 6 | "module": "dist/moneybutton.client.esm.js", 7 | "unpkg": "dist/moneybutton.client.iife.js", 8 | "browser": "dist/moneybutton.client.browser.esm.js", 9 | "library": "moneyButtonClient", 10 | "files": [ 11 | "dist/**" 12 | ], 13 | "scripts": { 14 | "lint": "standard --parser=babel-eslint", 15 | "pretest": "yarn lint", 16 | "test": "NODE_ENV=test dotenv -e ../../.env.test -- yarn test:mocha", 17 | "test:mocha": "NODE_ENV=test mocha -R progress", 18 | "build": "rollup -c", 19 | "build:dev": "dotenv -e ../../.env.dev -- yarn build", 20 | "build:prod": "NODE_ENV=production dotenv -e ../../.env.prod -- yarn build", 21 | "build:watch": "yarn build -w", 22 | "dev": "dotenv -e ../../.env.dev -- yarn build:watch", 23 | "prepublish": "yarn build:prod", 24 | "predocs": "jsdoc -d docs src/", 25 | "docs": "cd docs && http-server ." 26 | }, 27 | "repository": "https://github.com/moneybutton/api-client", 28 | "author": "Yours Inc.", 29 | "license": "Open-BSV", 30 | "publishConfig": { 31 | "access": "public" 32 | }, 33 | "dependencies": { 34 | "@babel/runtime": "7.5.5", 35 | "@moneybutton/config": "^0.38.5", 36 | "@moneybutton/json-api": "^0.38.5", 37 | "@moneybutton/logger": "^0.38.5", 38 | "@trust/webcrypto": "0.9.2", 39 | "fast-sha256": "1.1.1", 40 | "http-status-codes": "1.3.0", 41 | "isomorphic-fetch": "2.2.1", 42 | "localstorage-memory": "1.0.2", 43 | "moment": "2.24.0", 44 | "query-string": "6.1.0", 45 | "uuid": "3.3.2", 46 | "window": "4.2.5" 47 | }, 48 | "devDependencies": { 49 | "@babel/cli": "7.5.5", 50 | "@babel/core": "7.5.5", 51 | "@babel/plugin-proposal-object-rest-spread": "7.5.5", 52 | "@babel/plugin-transform-runtime": "7.5.5", 53 | "@babel/preset-env": "7.5.5", 54 | "@babel/register": "7.5.5", 55 | "@rollup/plugin-commonjs": "11.0.1", 56 | "@rollup/plugin-node-resolve": "7.0.0", 57 | "@rollup/plugin-replace": "2.3.0", 58 | "babel-eslint": "9.0.0", 59 | "dotenv-cli": "2.0.1", 60 | "http-server": "0.11.1", 61 | "jsdoc": "3.5.5", 62 | "jsdom": "11.11.0", 63 | "mocha": "6.2.3", 64 | "nock": "9.2.6", 65 | "rollup": "1.29.0", 66 | "rollup-plugin-babel": "4.3.3", 67 | "rollup-plugin-node-builtins": "2.1.2", 68 | "should": "13.2.1", 69 | "standard": "12.0.1" 70 | }, 71 | "standard": { 72 | "globals": [ 73 | "beforeEach", 74 | "describe", 75 | "it" 76 | ], 77 | "ignore": [ 78 | "docs/" 79 | ] 80 | }, 81 | "gitHead": "3ac0d2f8bdcd9bbb7b9934b30e92380c6600c95a" 82 | } 83 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel' 2 | import builtins from 'rollup-plugin-node-builtins' 3 | import commonJS from '@rollup/plugin-commonjs' 4 | import fs from 'fs' 5 | import { terser } from 'rollup-plugin-terser' 6 | import path from 'path' 7 | import pkg from './package.json' 8 | import replace from '@rollup/plugin-replace' 9 | import resolve from '@rollup/plugin-node-resolve' 10 | 11 | const PRODUCTION_BUILD = process.env.NODE_ENV === 'production' 12 | 13 | export default [ 14 | { 15 | input: path.resolve(__dirname, 'src', 'index.js'), 16 | external: isExternal, 17 | output: [ 18 | { 19 | file: pkg.main, 20 | format: 'cjs', 21 | sourcemap: true 22 | }, 23 | { 24 | file: pkg.module, 25 | format: 'es', 26 | sourcemap: true 27 | } 28 | ], 29 | plugins: [ 30 | replace(getReplacements()), 31 | babel({ 32 | presets: [ 33 | [ 34 | '@babel/preset-env', 35 | { 36 | modules: false, 37 | targets: { 38 | node: '10' 39 | } 40 | } 41 | ] 42 | ], 43 | plugins: getBabelPlugins() 44 | }) 45 | ] 46 | }, 47 | { 48 | input: path.resolve(__dirname, 'src', 'index.browser.js'), 49 | external: isExternal, 50 | output: [ 51 | { 52 | file: pkg.browser, 53 | format: 'es', 54 | sourcemap: true 55 | } 56 | ], 57 | plugins: [ 58 | replace(getReplacements()), 59 | babel({ 60 | presets: [ 61 | [ 62 | '@babel/preset-env', 63 | { 64 | modules: false, 65 | targets: { 66 | browsers: ['> 2%'] 67 | } 68 | } 69 | ] 70 | ], 71 | runtimeHelpers: true, 72 | plugins: getBabelPlugins({ includeTransformRuntime: true }) 73 | }) 74 | ] 75 | }, 76 | ...(PRODUCTION_BUILD ? [{ 77 | input: path.resolve(__dirname, 'src', 'index.browser.js'), 78 | output: [ 79 | { 80 | file: pkg.unpkg, 81 | format: 'iife', 82 | name: pkg.library, 83 | sourcemap: true 84 | } 85 | ], 86 | context: 'window', 87 | plugins: [ 88 | replace(getReplacements()), 89 | builtins(), 90 | resolve({ 91 | browser: true, 92 | preferBuiltins: true 93 | }), 94 | commonJS({ 95 | namedExports: { 96 | '../../node_modules/loglevel/lib/loglevel.js': [ 97 | 'setLevel', 98 | 'trace', 99 | 'debug', 100 | 'info', 101 | 'warn', 102 | 'error' 103 | ] 104 | } 105 | }), 106 | babel({ 107 | exclude: ['../../node_modules/**', 'node_modules/**'], 108 | presets: [ 109 | [ 110 | '@babel/preset-env', 111 | { 112 | modules: false, 113 | targets: { 114 | browsers: ['> 2%'] 115 | } 116 | } 117 | ] 118 | ], 119 | runtimeHelpers: true, 120 | plugins: getBabelPlugins({ includeTransformRuntime: true }) 121 | }), 122 | terser({ 123 | sourcemap: true, 124 | output: { 125 | preamble: getBanner() 126 | } 127 | }) 128 | ] 129 | }] : []) 130 | ] 131 | 132 | function isExternal (candidate) { 133 | return Object.keys(pkg.dependencies).some(dependency => { 134 | return candidate.startsWith(dependency) 135 | }) 136 | } 137 | 138 | function getBanner () { 139 | const filePath = path.resolve(__dirname, 'src', 'banner.js') 140 | return fs.readFileSync(filePath).toString().trim() 141 | } 142 | 143 | function getBabelPlugins (options = {}) { 144 | const plugins = [ 145 | '@babel/plugin-proposal-object-rest-spread' 146 | ] 147 | if (options.includeTransformRuntime) { 148 | plugins.push('@babel/plugin-transform-runtime') 149 | } 150 | return plugins 151 | } 152 | 153 | function getReplacements () { 154 | const replacements = {} 155 | for (const key in process.env) { 156 | replacements[`process.env.${key}`] = JSON.stringify(process.env[key]) 157 | } 158 | return replacements 159 | } 160 | -------------------------------------------------------------------------------- /src/auth-error.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Authentication API error. 3 | */ 4 | export default class AuthError { 5 | /** 6 | * @param {string} title - Error title. 7 | * @param {string} detail - Error detail. 8 | */ 9 | constructor (title, detail) { 10 | this.title = title 11 | this.detail = detail 12 | this.message = detail !== undefined ? detail : title 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/banner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Yours Inc. 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 furnished 9 | * 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 IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | import MoneyButtonConfigBuilder from '@moneybutton/config' 2 | 3 | const config = new MoneyButtonConfigBuilder() 4 | .addValue('MONEY_BUTTON_API_REST_URI', process.env.MONEY_BUTTON_API_REST_URI) 5 | .addValue('MONEY_BUTTON_API_AUTH_URI', process.env.MONEY_BUTTON_API_AUTH_URI) 6 | .build() 7 | 8 | export default config 9 | -------------------------------------------------------------------------------- /src/index.browser.js: -------------------------------------------------------------------------------- 1 | import AuthError from './auth-error' 2 | import getMoneyButtonClient from './get-money-button-client' 3 | import RestError from './rest-error' 4 | 5 | const MoneyButtonClient = getMoneyButtonClient( 6 | window.localStorage, 7 | window.crypto, 8 | window.location 9 | ) 10 | 11 | export { 12 | MoneyButtonClient, 13 | AuthError, 14 | RestError 15 | } 16 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Window from 'window' 2 | import crypto from '@trust/webcrypto' 3 | import localStorage from 'localstorage-memory' 4 | 5 | import AuthError from './auth-error' 6 | import getMoneyButtonClient from './get-money-button-client' 7 | import RestError from './rest-error' 8 | 9 | const MoneyButtonClient = getMoneyButtonClient( 10 | localStorage, 11 | crypto, 12 | new Window().location 13 | ) 14 | 15 | export { 16 | MoneyButtonClient, 17 | AuthError, 18 | RestError 19 | } 20 | -------------------------------------------------------------------------------- /src/rest-error.js: -------------------------------------------------------------------------------- 1 | /** 2 | * REST API error. 3 | */ 4 | export default class RestError { 5 | /** 6 | * 7 | * @param {number} status - HTTP status code. 8 | * @param {string} title - Error title. 9 | * @param {string} detail - Error detail. 10 | */ 11 | constructor (status, title, detail) { 12 | this.status = status 13 | this.title = title 14 | this.detail = detail 15 | this.message = detail !== undefined ? detail : title 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/babel.js: -------------------------------------------------------------------------------- 1 | require('@babel/register')({ 2 | 'presets': ['@babel/preset-env'], 3 | 'plugins': ['@babel/plugin-transform-runtime'] 4 | }) 5 | -------------------------------------------------------------------------------- /test/config.js: -------------------------------------------------------------------------------- 1 | import MoneyButtonConfigBuilder from '@moneybutton/config' 2 | 3 | const config = new MoneyButtonConfigBuilder() 4 | .addValue('MONEY_BUTTON_API_REST_URI', process.env.MONEY_BUTTON_API_REST_URI) 5 | .addValue('MONEY_BUTTON_API_AUTH_URI', process.env.MONEY_BUTTON_API_AUTH_URI) 6 | .addValue('MONEY_BUTTON_PUBLIC_CLIENT_IDENTIFIER', process.env.MONEY_BUTTON_PUBLIC_CLIENT_IDENTIFIER) 7 | .build() 8 | 9 | export default config 10 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import should from 'should' 2 | 3 | import { getClient } from './util' 4 | 5 | describe('MoneyButtonClient', () => { 6 | let client 7 | 8 | beforeEach(() => { 9 | client = getClient() 10 | }) 11 | 12 | it('should exist', async () => { 13 | should.exist(client) 14 | }) 15 | 16 | it('should not be logged in by default', async () => { 17 | let isLoggedIn = await client.isLoggedIn() 18 | isLoggedIn.should.be.false() 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive 2 | --require test/babel.js 3 | -------------------------------------------------------------------------------- /test/resources/not-found.json: -------------------------------------------------------------------------------- 1 | { 2 | "errors": [ 3 | { 4 | "title": "Not Found" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /test/resources/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "users", 4 | "id": "0", 5 | "attributes": { 6 | "email": "some@email.com" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/util.js: -------------------------------------------------------------------------------- 1 | import HttpStatus from 'http-status-codes' 2 | import nock from 'nock' 3 | import path from 'path' 4 | 5 | import config from './config' 6 | import { MoneyButtonClient } from '..' 7 | 8 | const API_REST_URI = config.get('MONEY_BUTTON_API_REST_URI') 9 | const CLIENT_IDENTIFIER = config.get('MONEY_BUTTON_PUBLIC_CLIENT_IDENTIFIER') 10 | 11 | export function getClient () { 12 | return new MoneyButtonClient(CLIENT_IDENTIFIER) 13 | } 14 | 15 | export function getMockApi () { 16 | return nock(API_REST_URI) 17 | } 18 | 19 | export function replyWithResource (request, resource) { 20 | let filePath = path.resolve(__dirname, 'resources', resource) 21 | let headers = { 'Content-Type': 'application/vnd.api+json' } 22 | request.replyWithFile(HttpStatus.OK, filePath, headers) 23 | return request 24 | } 25 | 26 | export async function shouldEventuallyThrow (fn) { 27 | await fn().should.be.rejected() 28 | } 29 | --------------------------------------------------------------------------------