8000 Merge remote-tracking branch 'core-api/master' into link-encodings · core-api/javascript-client@6487582 · GitHub
[go: up one dir, main page]

Skip to content
This repository was archived by the owner on Mar 18, 2019. It is now read-only.

Commit 6487582

Browse files
committed
Merge remote-tracking branch 'core-api/master' into link-encodings
# Conflicts: # lib/transports/http.js # tests/__helpers__/utils.js # tests/transports/http.js
2 parents 1ca608e + 733cb05 commit 6487582

File tree

8 files changed

+101
-40
lines changed

8 files changed

+101
-40
lines changed

lib/client.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ function lookupLink (node, keys) {
2222
}
2323

2424
class Client {
25-
constructor (decoders = null, transports = null) {
25+
constructor (decoders = null, transports = null, csrf = null) {
2626
this.decoders = decoders || [new codecs.CoreJSONCodec(), new codecs.JSONCodec(), new codecs.TextCodec()]
27-
this.transports = transports || [new transportsModule.HTTPTransport()]
27+
this.transports = transports || [new transportsModule.HTTPTransport(csrf)]
2828
}
2929

3030
action (document, keys, params = {}) {

lib/errors.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,17 @@ class LinkLookupError extends Error {
1414
}
1515
}
1616

17+
class ErrorMessage extends Error {
18+
constructor (message, content) {
19+
super(message)
20+
this.message = message
21+
this.content = content
22+
this.name = 'ErrorMessage'
23+
}
24+
}
25+
1726
module.exports = {
1827
ParameterError: ParameterError,
19-
LinkLookupError: LinkLookupError
28+
LinkLookupError: LinkLookupError,
29+
ErrorMessage: ErrorMessage
2030
}

lib/transports/http.js

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ const parseResponse = (response, decoders) => {
1414
}
1515

1616
class HTTPTransport {
17-
constructor (_fetch) {
17+
constructor (csrf, _fetch) {
1818
this.schemes = ['http', 'https']
19+
this.csrf = csrf
1920
this.fetch = _fetch || fetch
2021
}
2122

2223
action (link, decoders, params = {}) {
2324
const fields = link.fields
25+
const method = link.method.toUpperCase()
2426
let queryParams = {}
2527
let pathParams = {}
2628
let formParams = {}
@@ -60,7 +62,7 @@ class HTTPTransport {
6062
}
6163
}
6264

63-
let options = {method: link.method}
65+
let options = {method: method, headers: {}}
6466

6567
if (hasBody) {
6668
if (link.encoding === 'application/json') {
@@ -86,6 +88,13 @@ class HTTPTransport {
8688
}
8789
}
8890

91+
if (this.csrf) {
92+
options.credentials = 'same-origin'
93+
if (!utils.csrfSafeMethod(method)) {
94+
Object.assign(options.headers, this.csrf)
95+
}
96+
}
97+
8998
let parsedUrl = urlTemplate.parse(link.url)
9099
parsedUrl = parsedUrl.expand(pathParams)
91100
parsedUrl = new URL(parsedUrl)
@@ -94,11 +103,15 @@ class HTTPTransport {
94103

95104
return this.fetch(finalUrl, options)
96105
.then(function (response) {
97-
if (response.ok) {
98-
return parseResponse(response, decoders)
99-
} else {
100-
throw Error('Network response was not ok.')
101-
}
106+
return parseResponse(response, decoders).then(function (data) {
107+
if (response.ok) {
108+
return data
109+
} else {
110+
const title = response.status + ' ' + response.statusText
111+
const error = new errors.ErrorMessage(title, data)
112+
return Promise.reject(error)
113+
}
114+
})
102115
})
103116
}
104117
}

lib/utils.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,13 @@ const negotiateDecoder = function (decoders, contentType) {
3232
throw Error(`Unsupported media in Content-Type header: ${contentType}`)
3333
}
3434

35+
const csrfSafeMethod = function (method) {
36+
// these HTTP methods do not require CSRF protection
37+
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method))
38+
}
39+
3540
module.exports = {
3641
determineTransport: determineTransport,
37-
negotiateDecoder: negotiateDecoder
42+
negotiateDecoder: negotiateDecoder,
43+
csrfSafeMethod: csrfSafeMethod
3844
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "coreapi",
3-
"version": "0.0.12",
3+
"version": "0.0.17",
44
"description": "Javascript client library for Core API",
55
"main": "lib/index.js",
66
"scripts": {

tests/__helpers__/utils.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,17 @@ const mockedFetch = function (responseBody, contentType, statusCode = 200) {
2929

3030
const echo = function (url, options = {}) {
3131
const method = options.method
32+
const headers = options.headers
3233
const body = options.body
3334

3435
return new Promise((resolve, reject) => {
3536
const textPromise = () => {
3637
return new Promise((resolve, reject) => {
3738
let result
3839
if (body) {
39-
result = JSON.stringify({url: url, method: method, body: body})
40+
result = JSON.stringify({url: url, method: method, headers: headers, body: body})
4041
} else {
41-
result = JSON.stringify({url: url, method: method})
42+
result = JSON.stringify({url: url, method: method, headers: headers})
4243
}
4344
process.nextTick(
4445
resolve(result)

tests/client.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const testUtils = require('./__helpers__/utils')
66
describe('Test the Client', function () {
77
it('should get the content of page (text/html)', function () {
88
const fetch = testUtils.mockedFetch('Hello, world', 'text/html')
9-
const transport = new transportsModule.HTTPTransport(fetch)
9+
const transport = new transportsModule.HTTPTransport(null, fetch)
1010
const client = new coreapi.Client(null, [transport])
1111
const url = 'http://example.com'
1212

@@ -22,7 +22,7 @@ describe('Test the Client', function () {
2222

2323
it('should get the content of page (application/json)', function () {
2424
const fetch = testUtils.mockedFetch('{"text": "hello"}', 'application/json')
25-
const transport = new transportsModule.HTTPTransport(fetch)
25+
const transport = new transportsModule.HTTPTransport(null, fetch)
2626
const client = new coreapi.Client(null, [transport])
2727
const url = 'http://example.com'
2828

@@ -38,7 +38,7 @@ describe('Test the Client', function () {
3838

3939
it('action should get the content of page (application/json)', function () {
4040
const fetch = testUtils.mockedFetch('{"text": "hello"}', 'application/json')
41-
const transport = new transportsModule.HTTPTransport(fetch)
41+
const transport = new transportsModule.HTTPTransport(null, fetch)
4242
const client = new coreapi.Client(null, [transport])
4343
const document = new coreapi.Document('', '', '', {nested: {link: new coreapi.Link('http://example.com', 'get')}})
4444

@@ -54,7 +54,7 @@ describe('Test the Client', function () {
5454

5555
it('action should raise an error for invalid link keys', function () {
5656
const fetch = testUtils.mockedFetch('{"text": "hello"}', 'application/json')
57-
const transport = new transportsModule.HTTPTransport(fetch)
57+
const transport = new transportsModule.HTTPTransport(null, fetch)
5858
const client = new coreapi.Client(null, [transport])
5959
const document = new coreapi.Document('', '', '', {nested: {link: new coreapi.Link('http://example.com', 'get')}})
6060

tests/transports/http.js

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe('Test the HTTPTransport', function () {
1010
it('should check the action function of an HTTP transport (text/html)', function () {
1111
const url = 'http://www.example.com/'
1212
const link = new document.Link(url, 'get')
13-
const transport = new transports.HTTPTransport(testUtils.mockedFetch('Hello, world', 'text/html'))
13+
const transport = new transports.HTTPTransport(null, testUtils.mockedFetch('Hello, world', 'text/html'))
1414

1515
return transport.action(link, decoders)
1616
.then((res) => {
@@ -21,7 +21,7 @@ describe('Test the HTTPTransport', function () {
2121
it('should check the action function of an HTTP transport (json)', function () {
2222
const url = 'http://www.example.com/'
2323
const link = new document.Link(url, 'get')
24-
const transport = new transports.HTTPTransport(testUtils.mockedFetch('{"text": "Hello, world"}', 'application/json'))
24+
const transport = new transports.HTTPTransport(null, testUtils.mockedFetch('{"text": "Hello, world"}', 'application/json'))
2525

2626
return transport.action(link, decoders)
2727
.then(res => expect(res).toEqual({text: 'Hello, world'}))
@@ -40,117 +40,148 @@ describe('Test the HTTPTransport', function () {
4040
const url = 'http://www.example.com/'
4141
const fields = [new document.Field('firstField', true, 'form'), new document.Field('secondField', true, 'form')]
4242
const link = new document.Link(url, 'post', 'application/x-www-form-urlencoded', fields)
43-
const transport = new transports.HTTPTransport(testUtils.echo)
43+
const transport = new transports.HTTPTransport(null, testUtils.echo)
4444
const params = {
4545
firstField: 'hello',
4646
secondField: 'world'
4747
}
4848

4949
return transport.action(link, decoders, params)
5050
.then(res => expect(res).toEqual({
51-
body: 'firstField=hello&secondField=world',
52-
method: 'post',
53-
url: 'http://www.example.com/'
51+
url: 'http://www.example.com/',
52+
10000 method: 'POST',
53+
headers: {
54+
'Content-Type': 'application/x-www-form-urlencoded'
55+
},
56+
body: 'firstField=hello&secondField=world'
5457
}))
5558
})
5659

5760
it('should check the action function of an HTTP transport (network fail)', function () {
5861
const url = 'http://www.example.com/'
5962
const link = new document.Link(url, 'get')
60-
const transport = new transports.HTTPTransport(testUtils.mockedFetch('ERROR', 'text/html', 500))
63+
const transport = new transports.HTTPTransport(null, testUtils.mockedFetch('ERROR', 'text/html', 500))
6164

62-
expect(transport.action(link, decoders).then(() => {}).catch(() => {})).toThrow()
65+
return transport.action(link, decoders)
66+
.catch(function (result) {
67+
expect(result.message).toEqual('500 BAD REQUEST')
68+
expect(result.content).toEqual('ERROR')
69+
})
6370
})
6471

6572
it('should check the action function of an HTTP transport (json) with query params', function () {
6673
const url = 'http://www.example.com/'
6774
const fields = [new document.Field('page', false, 'query')]
6875
const link = new document.Link(url, 'get', 'application/json', fields)
69-
const transport = new transports.HTTPTransport(testUtils.echo)
76+
const transport = new transports.HTTPTransport(null, testUtils.echo)
7077
const params = {
7178
page: 23
7279
}
7380

7481
return transport.action(link, decoders, params)
7582
.then((res) => {
76-
expect(res).toEqual({url: 'http://www.example.com/?page=23', method: 'get'})
83+
expect(res).toEqual({url: 'http://www.example.com/?page=23', headers: {}, method: 'GET'})
7784
})
7885
})
7986

8087
it('should check the action function of an HTTP transport (json) with path params', function () {
8188
const url = 'http://www.example.com/{user}/'
8289
const fields = [new document.Field('user', true, 'path')]
8390
const link = new document.Link(url, 'get', 'application/json', fields)
84-
const transport = new transports.HTTPTransport(testUtils.echo)
91+
const transport = new transports.HTTPTransport(null, testUtils.echo)
8592
const params = {
8693
user: 23
8794
}
8895

8996
return transport.action(link, decoders, params)
9097
.then((res) => {
91-
expect(res).toEqual({url: 'http://www.example.com/23/', method: 'get'})
98+
expect(res).toEqual({url: 'http://www.example.com/23/', headers: {}, method: 'GET'})
9299
})
93100
})
94101

95102
it('should check the action function of an HTTP transport (json) with post request', function () {
96103
const url = 'http://www.example.com/'
97104
const link = new document.Link(url, 'post')
98-
const transport = new transports.HTTPTransport(testUtils.echo)
105+
const transport = new transports.HTTPTransport(null, testUtils.echo)
106+
107+
return transport.action(link, decoders)
108+
.then((res) => {
109+
expect(res).toEqual({url: 'http://www.example.com/', headers: {}, method: 'POST'})
110+
})
111+
})
112+
113+
it('CSRF should be included with POST requests.', function () {
114+
const url = 'http://www.example.com/'
115+
const link = new document.Link(url, 'post')
116+
const csrf = {'X-CSRFToken': 'abc'}
117+
const transport = new transports.HTTPTransport(csrf, testUtils.echo)
118+
119+
return transport.action(link, decoders)
120+
.then((res) => {
121+
expect(res).toEqual({url: 'http://www.example.com/', headers: {'X-CSRFToken': 'abc'}, method: 'POST'})
122+
})
123+
})
124+
125+
it('CSRF should not be included with GET requests.', function () {
126+
const url = 'http://www.example.com/'
127+
const link = new document.Link(url, 'get')
128+
const csrf = {'X-CSRFToken': 'abc'}
129+
const transport = new transports.HTTPTransport(csrf, testUtils.echo)
99130

100131
return transport.action(link, decoders)
101132
.then((res) => {
102-
expect(res).toEqual({url: 'http://www.example.com/', method: 'post'})
133+
expect(res).toEqual({url: 'http://www.example.com/', headers: {}, method: 'GET'})
103134
})
104135
})
105136

106137
it('should check the action function of an HTTP transport (json) with post request and form parameters', function () {
107138
const url = 'http://www.example.com/'
108139
const fields = [new document.Field('hello', true, 'form')]
109140
const link = new document.Link(url, 'post', 'application/json', fields)
110-
const transport = new transports.HTTPTransport(testUtils.echo)
141+
const transport = new transports.HTTPTransport(null, testUtils.echo)
111142
const params = {
112143
hello: 'world'
113144
}
114145

115146
return transport.action(link, decoders, params)
116147
.then((res) => {
117-
expect(res).toEqual({url: 'http://www.example.com/', method: 'post', body: JSON.stringify({hello: 'world'})})
148+
expect(res).toEqual({url: 'http://www.example.com/', method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({hello: 'world'})})
118149
})
119150
})
120151

121152
it('should check the action function of an HTTP transport (json) with post request and a body parameter', function () {
122153
const url = 'http://www.example.com/'
123154
const fields = [new document.Field('hello', true, 'body')]
124155
const link = new document.Link(url, 'post', 'application/json', fields)
125-
const transport = new transports.HTTPTransport(testUtils.echo)
156+
const transport = new transports.HTTPTransport(null, testUtils.echo)
126157
const params = {
127158
hello: 'world'
128159
}
129160

130161
return transport.action(link, decoders, params)
131162
.then((res) => {
132-
expect(res).toEqual({url: 'http://www.example.com/', method: 'post', body: JSON.stringify('world')})
163+
expect(res).toEqual({url: 'http://www.example.com/', method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify('world')})
133164
})
134165
})
135166

136167
it('should check the action function of an HTTP transport (json) with missing optional query params', function () {
137168
const url = 'http://www.example.com/'
138169
const fields = [new document.Field('page', false, 'query')]
139170
const link = new document.Link(url, 'get', 'application/json', fields)
140-
const transport = new transports.HTTPTransport(testUtils.echo)
171+
const transport = new transports.HTTPTransport(null, testUtils.echo)
141172
const params = {}
142173

143174
return transport.action(link, decoders, params)
144175
.then((res) => {
145-
expect(res).toEqual({url: 'http://www.example.com/', method: 'get'})
176+
expect(res).toEqual({url: 'http://www.example.com/', headers: {}, method: 'GET'})
146177
})
147178
})
148179

149180
it('should check the action function of an HTTP transport (json) with missing required parameter', function () {
150181
const url = 'http://www.example.com/{user}/'
151182
const fields = [new document.Field('user', true, 'path')]
152183
const link = new document.Link(url, 'get', 'application/json', fields)
153-
const transport = new transports.HTTPTransport(testUtils.echo)
184+
const transport = new transports.HTTPTransport(null, testUtils.echo)
154185
const params = {}
155186

156187
const callTransport = () => transport.action(link, decoders, params)
@@ -160,7 +191,7 @@ describe('Test the HTTPTransport', function () {
160191
it('should check the action function of an HTTP transport (json) with unknown paramater', function () {
161192
const url = 'http://www.example.com/'
162193
const link = new document.Link(url, 'get')
163-
const transport = new transports.HTTPTransport(testUtils.echo)
194+
const transport = new transports.HTTPTransport(null, testUtils.echo)
164195
const params = {
165196
hello: 'world'
166197
}

0 commit comments

Comments
 (0)
0