OSDN Git Service

Merge pull request #58 from Bytom/vapor
[bytom/bytom-electron.git] / src / sdk / connection.js
1 // FIXME: Microsoft Edge has issues returning errors for responses
2 // with a 401 status. We should add browser detection to only
3 // use the ponyfill for unsupported browsers.
4 const { fetch } = require('fetch-ponyfill')()
5 const errors = require('./errors')
6 const btoa = require('btoa')
7
8 const blacklistAttributes = [
9   'after',
10   'asset_tags',
11   'asset_definition',
12   'account_tags',
13   'next',
14   'reference_data',
15   'tags',
16 ]
17
18 const snakeize = (object) => {
19   for(let key in object) {
20     let value = object[key]
21     let newKey = key
22
23     // Skip all-caps keys
24     if (/^[A-Z]+$/.test(key)) {
25       continue
26     }
27
28     if (/[A-Z]/.test(key)) {
29       newKey = key.replace(/([A-Z])/g, v => `_${v.toLowerCase()}`)
30       delete object[key]
31     }
32
33     if (typeof value == 'object' && blacklistAttributes.indexOf(newKey) == -1) {
34       value = snakeize(value)
35     }
36
37     object[newKey] = value
38   }
39
40   return object
41 }
42
43 const camelize = (object) => {
44   for (let key in object) {
45     let value = object[key]
46     let newKey = key
47
48     if (/_/.test(key)) {
49       newKey = key.replace(/([_][a-z])/g, v => v[1].toUpperCase())
50       delete object[key]
51     }
52
53     if (typeof value == 'object' && blacklistAttributes.indexOf(key) == -1) {
54       value = camelize(value)
55     }
56
57     object[newKey] = value
58   }
59
60   return object
61 }
62
63 class Connection {
64   /**
65    * constructor - create a new Chain client object capable of interacting with
66    * the specified Chain Core.
67    *
68    * @param {String} baseUrl Chain Core URL.
69    * @param {String} token   Chain Core client token for API access.
70    * @param {String} agent   https.Agent used to provide TLS config.
71    * @returns {Client}
72    */
73   constructor(baseUrl, token = '', agent) {
74     this.baseUrl = baseUrl
75     this.token = token || ''
76     this.agent = agent
77   }
78
79   /**
80    * Submit a request to the specified Chain Core.
81    *
82    * @param  {String} path
83    * @param  {object} [body={}]
84    * @returns {Promise}
85    */
86   request(path, body = {}, skipSnakeize = false) {
87     if (!body) {
88       body = {}
89     }
90
91     // Convert camelcased request body field names to use snakecase for API
92     // processing.
93     const snakeBody = skipSnakeize ? body : snakeize(body) // Ssssssssssss
94
95     let req = {
96       method: 'POST',
97       headers: {
98         'Accept': 'application/json',
99         // 'Content-Type': 'application/json',
100
101         // TODO(jeffomatic): The Fetch API has inconsistent behavior between
102         // browser implementations and polyfills.
103         //
104         // - For Edge: we can't use the browser's fetch API because it doesn't
105         // always returns a WWW-Authenticate challenge to 401s.
106         // - For Safari/Chrome: using fetch-ponyfill (the polyfill) causes
107         // console warnings if the user agent string is provided.
108         //
109         // For now, let's not send the UA string.
110         //'User-Agent': 'chain-sdk-js/0.0'
111       },
112       body: JSON.stringify(snakeBody)
113     }
114
115     if (this.token) {
116       req.headers['Authorization'] = `Basic ${btoa(this.token)}`
117     }
118
119     if (this.agent) {
120       req.agent = this.agent
121     }
122
123     return fetch(this.baseUrl + path, req).catch((err) => {
124       throw errors.create(
125         errors.types.FETCH,
126         'Fetch error: ' + err.toString(),
127         {sourceError: err}
128       )
129     }).then((resp) => {
130       if (resp.status == 204) {
131         return { status: 204 }
132       }
133
134       return resp.json().catch(() => {
135         throw errors.create(
136           errors.types.JSON,
137           'Could not parse JSON response',
138           {response: resp, status: resp.status}
139         )
140       }).then((body) => {
141         if (resp.status / 100 == 2) {
142           return body
143         }
144
145         // Everything else is a status error.
146         let errType = null
147         if (resp.status == 401) {
148           errType = errors.types.UNAUTHORIZED
149         } else if (resp.status == 404) {
150           errType = errors.types.NOT_FOUND
151         } else if (resp.status / 100 == 4) {
152           errType = errors.types.BAD_REQUEST
153         } else {
154           errType = errors.types.SERVER
155         }
156
157         throw errors.create(
158           errType,
159           errors.formatErrMsg(body, null),
160           {
161             response: resp,
162             status: resp.status,
163             body: body,
164             requestId: null
165           }
166         )
167       }).then((body) => {
168         if(body.status === 'fail'){
169           throw body
170         }
171         // After processing the response, convert snakecased field names to
172         // camelcase to match language conventions.
173         return skipSnakeize? body : camelize(body)
174       })
175     })
176   }
177 }
178
179 Connection.snakeize = snakeize
180 Connection.camelize = camelize
181
182 module.exports = Connection