Skip to content

Commit 1612959

Browse files
committed
Add Request/Response classes
1 parent 76f7cc9 commit 1612959

File tree

7 files changed

+1699
-1
lines changed

7 files changed

+1699
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
# rails-fetch
1+
# rails-fetch
2+
3+
# Credits
4+
5+
[Basecamp](https://github.com/basecamp), since this piece of code has been extracted from hey.com.

package.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "rails-fetch",
3+
"version": "0.0.1",
4+
"description": "A tiny Fetch API wrapper to allow you make http requests without need to handle to send the CSRF Token on every request",
5+
"main": "index.js",
6+
"repository": "https://github.com/marcelolx/rails-fetch.git",
7+
"author": "Marcelo Lauxen <marcelolauxen16@gmail.com>",
8+
"license": "MIT",
9+
"private": false,
10+
"scripts": {
11+
"lint": "standard src"
12+
},
13+
"devDependencies": {
14+
"standard": "^16.0.3"
15+
}
16+
}

src/lib/cookie.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export function getCookie (name) {
2+
const cookies = document.cookie ? document.cookie.split('; ') : []
3+
const prefix = `${encodeURIComponent(name)}=`
4+
const cookie = cookies.find(cookie => cookie.startsWith(prefix))
5+
6+
if (cookie) {
7+
const value = cookie.split('=').slice(1).join('=')
8+
return value ? decodeURIComponent(value) : undefined
9+
}
10+
}

src/request.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { Response } from './response'
2+
import { getCookie } from './lib/cookie'
3+
4+
export class Request {
5+
constructor (method, url, options = {}) {
6+
this.method = method
7+
this.url = url
8+
this.options = options
9+
}
10+
11+
async perform () {
12+
const response = new Response(await window.fetch(this.url, this.fetchOptions))
13+
if (response.unauthenticated && response.authenticationURL) {
14+
return Promise.reject(window.location.href = response.authenticationURL)
15+
} else {
16+
return response
17+
}
18+
}
19+
20+
get fetchOptions () {
21+
return {
22+
method: this.method,
23+
headers: this.headers,
24+
body: this.body,
25+
signal: this.signal,
26+
credentials: 'same-origin',
27+
redirect: 'follow'
28+
}
29+
}
30+
31+
get headers () {
32+
return compact({
33+
'X-Requested-With': 'XMLHttpRequest',
34+
'X-CSRF-Token': this.csrfToken,
35+
'Content-Type': this.contentType,
36+
Accept: this.accept
37+
})
38+
}
39+
40+
get csrfToken () {
41+
return getCookie(metaContent('csrf-param')) || metaContent('csrf-token')
42+
}
43+
44+
get contentType () {
45+
if (this.options.contentType) {
46+
return this.options.contentType
47+
} else if (this.body == null || this.body instanceof window.FormData) {
48+
return undefined
49+
} else if (this.body instanceof window.File) {
50+
return this.body.type
51+
} else {
52+
return 'application/octet-stream'
53+
}
54+
}
55+
56+
get accept () {
57+
switch (this.responseKind) {
58+
case 'html':
59+
return 'text/html, application/xhtml+xml'
60+
case 'json':
61+
return 'application/json'
62+
default:
63+
return '*/*'
64+
}
65+
}
66+
67+
get body () {
68+
return this.options.body
69+
}
70+
71+
get responseKind () {
72+
return this.options.responseKind || 'html'
73+
}
74+
75+
get signal () {
76+
return this.options.signal
77+
}
78+
}
79+
80+
function compact (object) {
81+
const result = {}
82+
for (const key in object) {
83+
const value = object[key]
84+
if (value !== undefined) {
85+
result[key] = value
86+
}
87+
}
88+
return result
89+
}
90+
91+
function metaContent (name) {
92+
const element = document.head.querySelector(`meta[name="${name}"]`)
93+
return element && element.content
94+
}

src/response.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
export class Response {
2+
constructor (response) {
3+
this.response = response
4+
}
5+
6+
get statusCode () {
7+
return this.response.status
8+
}
9+
10+
get ok () {
11+
return this.response.ok
12+
}
13+
14+
get unauthenticated () {
15+
return this.statusCode === 401
16+
}
17+
18+
get authenticationURL () {
19+
return this.response.headers.get('WWW-Authenticate')
20+
}
21+
22+
get contentType () {
23+
const contentType = this.response.headers.get('Content-Type') || ''
24+
return contentType.replace(/;.*$/, '')
25+
}
26+
27+
get headers () {
28+
return this.response.headers
29+
}
30+
31+
get html () {
32+
if (this.contentType.match(/^(application|text)\/(html|xhtml\+xml)$/)) {
33+
return this.response.text()
34+
} else {
35+
return Promise.reject(new Error(`Expected an HTML response but got "${this.contentType}" instead`))
36+
}
37+
}
38+
39+
get json () {
40+
if (this.contentType.match(/^application\/json/)) {
41+
return this.response.json()
42+
} else {
43+
return Promise.reject(new Error(`Expected a JSON response but got "${this.contentType}" instead`))
44+
}
45+
}
46+
47+
get text () {
48+
return this.response.text()
49+
}
50+
}

0 commit comments

Comments
 (0)