Skip to content

Commit

Permalink
Add support for calling a separate api host in fetchFromAPI based on … (
Browse files Browse the repository at this point in the history
#54)

* Add support for calling a separate api host in fetchFromAPI based on an API_HOST environment variable.

* bump version to 7.7.0
  • Loading branch information
markbrocato authored Mar 2, 2020
1 parent 7b930ba commit d60baeb
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 52 deletions.
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-storefront",
"version": "7.6.0",
"version": "7.7.0",
"description": "Build and deploy e-commerce progressive web apps (PWAs) in record time.",
"module": "./index.js",
"license": "Apache-2.0",
Expand Down
2 changes: 1 addition & 1 deletion src/props/createLazyProps.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
* component will be rendered and can display a skeleton while waiting for the promise returned
* by `fetchCallback` to resolve
*/
export default function createLazyProps(fetchCallback, { timeout = 50 } = {}) {
export default function createLazyProps(fetchCallback, { timeout = 100 } = {}) {
return (options /* from getInitialProps */) => {
if (typeof window === 'undefined') {
// server
Expand Down
28 changes: 18 additions & 10 deletions src/props/fetchFromAPI.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import fetch from '../fetch'
import isBrowser from '../utils/isBrowser'

/**
* A convenience function to be used in `getInitialProps` to fetch data for the page from an
Expand All @@ -24,23 +23,32 @@ import isBrowser from '../utils/isBrowser'
* @param {Object} opts The options object provided to `getInitialProps`
* @return {Promise} A promise that resolves to the data that the page should display
*/
export default function fetchFromAPI({ req, asPath }) {
const server = !isBrowser()
const host = server ? req.headers['host'] : ''
const protocol = server ? (host.startsWith('localhost') ? 'http://' : 'https://') : ''
export default function fetchFromAPI({ req, asPath, pathname }) {
const host = req ? process.env.API_HOST || req.headers['host'] : ''
const protocol = req ? (host.startsWith('localhost') ? 'http://' : 'https://') : ''
const [path, search] = asPath.split('?')

if (asPath === '/') asPath = ''
if (asPath.startsWith('/?')) asPath = asPath.substring(1)
let uri = `/api${path.replace(/\/$/, '')}`

let uri = `/api${asPath}`
if (search) {
uri += `?${search}`
}

let headers = {}

if (server) {
if (req) {
// on the server
if (uri.indexOf('?') === -1) {
uri = uri + '?_includeAppData=1'
} else {
uri = uri + '&_includeAppData=1'
}

headers = {
host: req.headers['host'],
'x-next-page': `/api${pathname.replace(/\/$/, '')}`,
}
}

return fetch(`${protocol}${host}${uri}`).then(res => res.json())
return fetch(`${protocol}${host}${uri}`, { headers }).then(res => res.json())
}
93 changes: 61 additions & 32 deletions test/props/fetchFromAPI.test.js
Original file line number Diff line number Diff line change
@@ -1,95 +1,124 @@
import fetchFromAPI from 'react-storefront/props/fetchFromAPI'

describe('fetchFromAPI', () => {
const headers = {
headers: { 'x-rsf-api-version': '1' },
}
let headers

fetchMock.mockResponse(JSON.stringify({}))

let fetchFromAPI,
isBrowser = true

beforeEach(() => {
jest.isolateModules(() => {
jest.doMock('react-storefront/utils/isBrowser', () => () => isBrowser)
fetchFromAPI = require('react-storefront/props/fetchFromAPI').default
})
})

afterAll(() => {
jest.resetAllMocks()
})

describe('in the browser', () => {
beforeEach(() => {
isBrowser = true
headers = {
'x-rsf-api-version': '1',
}
})

it('should prepend api to the path', () => {
fetchFromAPI({
asPath: '/p/1',
})
expect(fetchMock).toHaveBeenCalledWith('/api/p/1', headers)
expect(fetchMock).toHaveBeenCalledWith('/api/p/1', { headers })
})

it('should call /api when the path is /', () => {
fetchFromAPI({
asPath: '/',
})
expect(fetchMock).toHaveBeenCalledWith('/api', headers)
expect(fetchMock).toHaveBeenCalledWith('/api', { headers })
})

it('should append query params directly to api if the root with query params is called', () => {
fetchFromAPI({
asPath: '/?test=1',
})
expect(fetchMock).toHaveBeenCalledWith('/api?test=1', headers)
expect(fetchMock).toHaveBeenCalledWith('/api?test=1', { headers })
})
})

describe('on the server', () => {
beforeEach(() => {
isBrowser = false
headers = {
'x-rsf-api-version': '1',
host: 'www.domain.com',
}
})

it('should include the protocol, domain, and ?_includeAppData=1', () => {
fetchFromAPI({
asPath: '/p/1',
pathname: '/p/[productId]',
req: {
headers: {
host: 'www.domain.com',
},
},
})
expect(fetchMock).toHaveBeenCalledWith(
'https://www.domain.com/api/p/1?_includeAppData=1',
headers,
)
expect(fetchMock).toHaveBeenCalledWith('https://www.domain.com/api/p/1?_includeAppData=1', {
headers: {
...headers,
'x-next-page': '/api/p/[productId]',
},
})
})

it('should use API_HOST when provided', () => {
process.env.API_HOST = 'localhost:3001'

fetchFromAPI({
asPath: '/p/1',
pathname: '/p/[productId]',
req: {
headers: {
host: 'www.domain.com',
},
},
})

expect(fetchMock).toHaveBeenCalledWith('http://localhost:3001/api/p/1?_includeAppData=1', {
headers: {
...headers,
'x-next-page': '/api/p/[productId]',
},
})

delete process.env.API_HOST
})

it('should use http:// for localhost', () => {
fetchFromAPI({
asPath: '/p/1',
pathname: '/p/[productId]',
req: {
headers: {
...headers,
host: 'localhost',
},
},
})
expect(fetchMock).toHaveBeenCalledWith('http://localhost/api/p/1?_includeAppData=1', headers)
expect(fetchMock).toHaveBeenCalledWith('http://localhost/api/p/1?_includeAppData=1', {
headers: {
...headers,
host: 'localhost',
'x-next-page': '/api/p/[productId]',
},
})
})

it('should append _includeAppData to the existing query string', () => {
fetchFromAPI({
asPath: '/foo?x=1',
pathname: '/foo',
req: {
headers: {
host: 'localhost',
},
headers,
},
})
expect(fetchMock).toHaveBeenCalledWith(
'http://localhost/api/foo?x=1&_includeAppData=1',
headers,
'https://www.domain.com/api/foo?x=1&_includeAppData=1',
{
headers: {
...headers,
'x-next-page': '/api/foo',
},
},
)
})
})
Expand Down

0 comments on commit d60baeb

Please sign in to comment.