Skip to content

Commit 25a6bd1

Browse files
authored
Merge pull request #3 from bc-chaz/step-3-integrate-api
feat(common): add step 3, integrate bc api
2 parents e775733 + 4f3d9f4 commit 25a6bd1

File tree

16 files changed

+306
-22
lines changed

16 files changed

+306
-22
lines changed

.env-sample

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@ CLIENT_SECRET={app secret}
88
# https://developer.bigcommerce.com/api-docs/apps/guide/development#testing-locally-with-ngrok
99

1010
AUTH_CALLBACK=https://{ngrok_id}.ngrok.io/api/auth
11+
12+
# Set cookie variables, replace jwt key with a 32+ random character secret
13+
14+
COOKIE_NAME=__{NAME}
15+
JWT_KEY={SECRET}

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ To get the app running locally, follow these instructions:
1919
5. Copy .env-sample to `.env`.
2020
6. [Replace client_id and client_secret in .env](https://devtools.bigcommerce.com/my/apps) (from `View Client ID` in the dev portal).
2121
7. Update AUTH_CALLBACK in `.env` with the `ngrok_id` from step 5.
22-
8. Start your dev environment in a **separate** terminal from `ngrok`. If `ngrok` restarts, update callbacks in steps 4 and 7 with the new ngrok_id.
22+
8. Enter a cookie name, as well as a jwt secret in `.env`.
23+
- The cookie name should be unique
24+
- JWT key should be at least 32 random characters (256 bits) for HS256
25+
9. Start your dev environment in a **separate** terminal from `ngrok`. If `ngrok` restarts, update callbacks in steps 4 and 7 with the new ngrok_id.
2326
- `npm run dev`
24-
9. [Install the app and launch.](https://developer.bigcommerce.com/api-docs/apps/quick-start#install-the-app)
27+
10. [Install the app and launch.](https://developer.bigcommerce.com/api-docs/apps/quick-start#install-the-app)

components/header.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { H2, Link } from '@bigcommerce/big-design';
2+
import { useStore } from '../lib/hooks';
3+
4+
interface HeaderProps {
5+
title: string;
6+
}
7+
8+
const Header = ({ title }: HeaderProps) => {
9+
const { storeId } = useStore();
10+
const storeLink = `https://store-${storeId}.mybigcommerce.com/manage/marketplace/apps/my-apps`;
11+
12+
return (
13+
<>
14+
<Link href={storeLink}>My Apps</Link>
15+
<H2 marginTop="medium">{title}</H2>
16+
</>
17+
);
18+
};
19+
20+
export default Header;

lib/auth.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,33 @@
11
import * as BigCommerce from 'node-bigcommerce';
2+
import { QueryParams } from '../types';
3+
4+
const { AUTH_CALLBACK, CLIENT_ID, CLIENT_SECRET } = process.env;
25

36
// Create BigCommerce instance
47
// https://github.com/getconversio/node-bigcommerce
58
const bigcommerce = new BigCommerce({
69
logLevel: 'info',
7-
clientId: process.env.CLIENT_ID,
8-
secret: process.env.CLIENT_SECRET,
9-
callback: process.env.AUTH_CALLBACK,
10+
clientId: CLIENT_ID,
11+
secret: CLIENT_SECRET,
12+
callback: AUTH_CALLBACK,
1013
responseType: 'json',
1114
headers: { 'Accept-Encoding': '*' },
1215
apiVersion: 'v3'
1316
});
1417

1518
const bigcommerceSigned = new BigCommerce({
16-
secret: process.env.CLIENT_SECRET,
19+
secret: CLIENT_SECRET,
1720
responseType: 'json'
1821
});
1922

20-
interface QueryParams {
21-
[key: string]: string;
23+
export function bigcommerceClient(accessToken: string, storeId: string) {
24+
return new BigCommerce({
25+
clientId: CLIENT_ID,
26+
accessToken,
27+
storeHash: storeId,
28+
responseType: 'json',
29+
apiVersion: 'v3'
30+
});
2231
}
2332

2433
export function getBCAuth(query: QueryParams) {

lib/cookie.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { parse, serialize } from 'cookie';
2+
import * as jwt from 'jsonwebtoken';
3+
import { NextApiRequest, NextApiResponse } from 'next';
4+
import { SessionProps } from '../types';
5+
6+
const { COOKIE_NAME, JWT_KEY } = process.env;
7+
const MAX_AGE = 60 * 60 * 24; // 24 hours
8+
9+
export function setCookie(res: NextApiResponse, session: SessionProps) {
10+
const { access_token: token, context } = session;
11+
const storeId = context?.split('/')[1] || '';
12+
const cookie = serialize(COOKIE_NAME, encode(token, storeId), {
13+
expires: new Date(Date.now() + MAX_AGE * 1000),
14+
httpOnly: true,
15+
path: '/',
16+
sameSite: 'none',
17+
secure: true,
18+
});
19+
20+
res.setHeader('Set-Cookie', cookie);
21+
}
22+
23+
export function parseCookies(req: NextApiRequest) {
24+
if (req.cookies) return req.cookies; // API routes don't parse cookies
25+
26+
const cookie = req.headers?.cookie;
27+
return parse(cookie || '');
28+
}
29+
30+
export function getCookie(req: NextApiRequest) {
31+
return parseCookies(req)[COOKIE_NAME];
32+
}
33+
34+
export function encode(token: string, storeId: string) {
35+
return jwt.sign({ accessToken: token, storeId }, JWT_KEY);
36+
}
37+
38+
export function decode(encodedCookie: string) {
39+
return jwt.verify(encodedCookie, JWT_KEY);
40+
}

lib/hooks.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import useSWR from 'swr';
2+
3+
function fetcher(url: string) {
4+
return fetch(url).then(res => res.json());
5+
}
6+
7+
export function useStore() {
8+
const { data, error } = useSWR('/api/store', fetcher);
9+
10+
return {
11+
storeId: data?.storeId,
12+
isError: error,
13+
};
14+
}
15+
16+
export function useProducts() {
17+
const { data, error } = useSWR('/api/products', fetcher);
18+
19+
return {
20+
summary: data,
21+
isError: error,
22+
};
23+
}

package-lock.json

Lines changed: 109 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@
1414
"license": "ISC",
1515
"dependencies": {
1616
"@bigcommerce/big-design": "^0.27.0",
17+
"cookie": "^0.4.1",
18+
"jsonwebtoken": "^8.5.1",
1719
"next": "^10.0.5",
1820
"node-bigcommerce": "^4.1.0",
1921
"react": "^17.0.1",
2022
"react-dom": "^17.0.1",
21-
"styled-components": "^4.4.1"
23+
"styled-components": "^4.4.1",
24+
"swr": "^0.4.1"
2225
},
2326
"devDependencies": {
2427
"@types/node": "^14.14.22",

pages/api/auth.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import { NextApiRequest, NextApiResponse } from 'next';
22
import { getBCAuth } from '../../lib/auth';
3+
import { setCookie } from '../../lib/cookie';
34

45
export default async function auth(req: NextApiRequest, res: NextApiResponse) {
56
try {
6-
await getBCAuth(req.query);
7+
// Authenticate the app on install
8+
const session = await getBCAuth(req.query);
79

10+
setCookie(res, session);
811
res.redirect(302, '/');
912
} catch (error) {
10-
const { data, response } = error;
11-
res.status(response?.status || 500).json(data);
13+
const { message, response } = error;
14+
res.status(response?.status || 500).json(message);
1215
}
1316
}

pages/api/load.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export default async function load(req: NextApiRequest, res: NextApiResponse) {
77

88
res.redirect(302, '/');
99
} catch (error) {
10-
const { data, response } = error;
11-
res.status(response?.status || 500).json(data);
10+
const { message, response } = error;
11+
res.status(response?.status || 500).json(message);
1212
}
1313
}

0 commit comments

Comments
 (0)