-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add pass headers guide #6960
base: main
Are you sure you want to change the base?
Changes from 10 commits
fea83b2
8df509c
af7c294
3c4508b
fd2959e
882068f
b5e7541
8c61597
83ecaa7
2730ea6
cf6a36b
93d6ae1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
--- | ||
title: Available Guides | ||
layout: default | ||
--- | ||
|
||
# Cookbook | ||
|
||
Here you will find guides, best practices and recipes that will teach you how to achieve certain use cases in the most optimal way with Vue Storefront stack. | ||
|
||
:card{to="/cookbook/pass-custom-headers-and-cookies-to-requests" title="Pass Custom Headers and Cookies to Requests" description="In this tutorial, we'll demonstrate how to set up an interceptor to pass headers when making requests using the axios client inside the Vue Storefront SDK. This is useful when you need to append custom headers or cookies to your API calls."} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,263 @@ | ||
--- | ||
title: Pass Custom Headers and Cookies to Requests | ||
layout: default | ||
scope: sdk, middleware, integrations | ||
--- | ||
|
||
# Pass Custom Headers and Cookies to Requests | ||
|
||
In this tutorial, we'll demonstrate how to set up an interceptor to pass headers when making requests using the axios client inside the Vue Storefront SDK. This is useful when you need to append custom headers or cookies to your API calls. | ||
|
||
After passing the headers to the SDK client, we'll also show you how to pass them further from the Server Middleware to external services (e.g. SAP OCC API). | ||
|
||
## Notes | ||
::info | ||
This guide uses the integrations boilerplate with the nuxt option. The same basic principles apply to other frameworks, but the implementation may vary. | ||
:: | ||
|
||
All SDK modules export a client instance of axios. This is the axios instance that is used to make requests to the Server Middleware. You can access this instance by importing the `client` from the SDK. | ||
|
||
## Steps | ||
::steps | ||
#step-1 | ||
**Import Required Modules** | ||
|
||
```javascript | ||
import { initSDK, buildModule } from '@vue-storefront/sdk'; | ||
import { client, boilerplateModule, BoilerplateModuleType } from '../../packages/sdk/src'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand, where is the client coming from, is it exposed by every SDK module? Is it coordinated with the unified team, do they expose it as well the same way? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This import path should be less ambigious. But long story short: the client is coming from an SDK module (e.g. sapcc-sdk), every module exports the client. Not sure this is going to be the case in a few weeks/months (considering the ongoing discussion initiated by @bartoszherba) but, for now, that's the state of things. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really think this doc should be agreed upon with unified. Consider how confused a customer on unified storefront would be after seeing these docs if unified module doesn't expose this (and honestly they probably couldn't know before that they should). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This guide sat on the back burner for a while. Given the change in landscape since it's inception I agree there should be a consensus and standard way of doing this. RE: comments about ootb, I think it would be sensible for for the Unified team to implement the boilerplate to allow passing custom headers and allow the end dev to use a one-liner, if possible, to add headers to requests. The guide for an ootb solution would be a separate guide though. we still have two scenarios:
|
||
import useHeaders from 'path-to-useHeaders-file'; // Replace with the correct path to the `useHeaders` composable. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we provide it for our storefronts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah it's below There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably we should make it more clear There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand, who does provide the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It also feels like it's strictly vue specific since it mentions composables, is this what we want? I don't see any references to vue in the path of this file so it seems like you provide the general advice There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see that the customer is responsible for creation of the composable, but my question regarding the vue-centric docs remain. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, if we can create a solution that framework agnostic that would be ideal. But I think given the differences in how Next and Nuxt handle ssr/state, I'm not sure that's possible. I'm not familiar enough with Next to know. For context, the guide was made in response to an integrator's query that was using Nuxt. This started as general enablement and turned into a guide when several more folks asked the same question and all were using Nuxt. If it's not possible to provide a framework agnostic solution, we can at least add examples in both support frameworks. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm ok for both frameworks! :) Just including nuxt only solution felt a bit off |
||
``` | ||
|
||
#step-2 | ||
**Define the SDK Configuration** | ||
```javascript | ||
const sdkConfig = { | ||
boilerplate: buildModule<BoilerplateModuleType>(boilerplateModule, { | ||
apiUrl: 'http://localhost:8181/boilerplate', | ||
}), | ||
}; | ||
``` | ||
#step-3 | ||
**Fetch Headers with the useHeaders Composable** | ||
|
||
Use the `useHeaders` composable to fetch the necessary headers. This composable manages both server-side and client-side headers for you. | ||
|
||
```javascript | ||
const { addHeadersToState, headerData } = useHeaders(); | ||
addHeadersToState(); | ||
``` | ||
|
||
#step-4 | ||
**Setting Up Axios Interceptor** | ||
|
||
Before making requests using axios, establish an interceptor to automatically append the desired headers. | ||
|
||
#### a. **Check for Existing Interceptor** | ||
|
||
Check if an interceptor already exists and eject (remove) it to prevent shared headers across instances. | ||
|
||
```javascript | ||
let interceptorId: number | null = null; | ||
|
||
if (interceptorId !== null) { | ||
client.interceptors.request.eject(interceptorId); | ||
} | ||
``` | ||
|
||
#### b. **Define the Interceptor** | ||
|
||
Append custom headers to each request using the interceptor. | ||
|
||
```javascript | ||
interceptorId = client.interceptors.request.use( | ||
(config) => { | ||
if (!config.headers) { | ||
config.headers = {}; | ||
} | ||
|
||
// Add custom headers here | ||
config.headers['amazing-header'] = 'coolest header ever'; | ||
config.headers['dev-mode'] = headerData.value?.isDevMode ?? 'dude'; | ||
|
||
return config; | ||
}, | ||
(error) => { | ||
// Handle request error here | ||
return Promise.reject(error); | ||
} | ||
); | ||
``` | ||
|
||
#step-4 | ||
**Initialize and Export the SDK** | ||
|
||
Finally, initialize the SDK with your configuration and export it. | ||
|
||
```javascript | ||
return initSDK<typeof sdkConfig>(sdkConfig); | ||
``` | ||
|
||
#step-5 | ||
**sdk.config.ts** all together | ||
|
||
```javascript | ||
import { initSDK, buildModule } from '@vue-storefront/sdk'; | ||
import { client, boilerplateModule, BoilerplateModuleType } from '../../packages/sdk/src'; | ||
|
||
// Maintain a reference to the interceptor | ||
let interceptorId: number | null = null; | ||
|
||
export const useSdk = () => { | ||
const sdkConfig = { | ||
boilerplate: buildModule<BoilerplateModuleType>(boilerplateModule, { | ||
apiUrl: 'http://localhost:8181/boilerplate', | ||
}), | ||
}; | ||
|
||
const {addHeadersToState, headerData} = useHeaders() | ||
|
||
addHeadersToState() | ||
|
||
// If an interceptor is already set, eject it to remove it | ||
// this prevents headers being shared across instances | ||
if (interceptorId !== null) { | ||
client.interceptors.request.eject(interceptorId); | ||
} | ||
|
||
interceptorId = client.interceptors.request.use( | ||
Comment on lines
+123
to
+127
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it will become flaky and unpredictable when there are many parallel connections to applications and shared client There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you elaborate? Not sure about the dangers if somebody uses the SDK server-side (e.g. in an Express application) but when it comes to the usage inside a storefront, how can SDK client be shared across multiple users running the storefront in their own browser? π€ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I confirm that's the case. |
||
(config) => { | ||
if (!config.headers) { | ||
config.headers = {}; | ||
} | ||
|
||
// here you can set any headers you want | ||
config.headers['amazing-header'] = 'coolest header ever'; | ||
config.headers['dev-mode'] = headerData.value?.isDevMode ?? 'false' | ||
|
||
return config; | ||
}, | ||
(error) => { | ||
// Do something with request error | ||
return Promise.reject(error); | ||
} | ||
); | ||
|
||
return initSDK<typeof sdkConfig>(sdkConfig); | ||
}; | ||
|
||
``` | ||
::tip | ||
This guide uses a `useHeaders` composable. This composable manages both server-side and client-side headers for you. This is just an example, you can use any method you want to fetch the headers. | ||
:: | ||
|
||
#step-6 | ||
**Create a Composable to Handle Headers** | ||
|
||
```javascript | ||
export default function () { | ||
const serverHeaders = useRequestHeaders(['cookie']); | ||
const devModeCookie = useCookie('dev-mode')?.value || ''; | ||
|
||
const state = useState<{ headerData: null | { isDevMode: string }, loading: boolean }>('headerState', () => ({ | ||
headerData: null, | ||
loading: false, | ||
})); | ||
|
||
function addHeadersToState() { | ||
getDevModeHeader(devModeCookie, serverHeaders.cookie || ''); | ||
} | ||
|
||
function getHeaderObj(headers: string): { [key: string]: string } { | ||
if (!headers) return {}; | ||
|
||
return headers.split(';').reduce<{ [key: string]: string }>((acc, item) => { | ||
const parts = item.trim().split('='); | ||
if (parts.length === 2) { | ||
const [key, value] = parts; | ||
acc[key] = value; | ||
} | ||
return acc; | ||
}, {}); | ||
} | ||
|
||
function getDevModeHeader(cookieHeader: string, serverHeader: string) { | ||
if (process.server) { | ||
const headerObj = getHeaderObj(serverHeader); | ||
state.value.headerData = { isDevMode: headerObj['dev-mode'] || '' }; | ||
return | ||
} | ||
|
||
state.value.headerData = { isDevMode: cookieHeader }; | ||
} | ||
|
||
return { | ||
addHeadersToState, | ||
...toRefs(state.value), | ||
} | ||
} | ||
Comment on lines
+156
to
+197
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we provide it ootb, instead of asking user to write it themselves? We even have the code already, you just wrote it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that would be great. But I created the example quickly and I'm not certain there aren't edge cases that need to be addressed. This PR is a bit old, so Unified wasn't on my radar when I wrote the guide and we didn't have a storefront in which I could add it. But now we do, so I think it would indeed be great to have this ootb in Unified. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jagoral wdyt |
||
``` | ||
|
||
## Checkpoint 1 | ||
|
||
You've now set up an interceptor to append custom headers to requests using the axios client in the Vue Storefront SDK. This ensures that all outgoing requests contain the headers you've defined and the Server Middleware will be able to access them through the request object. | ||
|
||
:: | ||
|
||
## Passing Headers from API Client to external services | ||
|
||
Now that you're passing headers from the Vue Storefront SDK client to the Server Middleware, you may want to pass them further to external services (e.g. SAP OCC API). To achieve that, you're going to create a Server Middleware extension which reads the headers from the request object and appends them as defaults to the Server Middleware client. | ||
|
||
## Steps | ||
::steps | ||
#step-1 | ||
|
||
Create a new Server Middleware extension in the `middleware.config.ts` file. In the extension, utilize the `afterCreate` hook which fires after the Server Middleware client had been created. | ||
|
||
::warning | ||
Do not use the below solution if: | ||
- the headers you're passing are user-specific (e.g. they are some sort of a session token), | ||
- the integration you're using leverages the Init Function under the hood (i.e. its Server Middleware client is created only once during the application startup). | ||
|
||
This might lead to mixing user-specific headers between requests. | ||
|
||
For the time being, there is no simple way to circumvent the issue. In integrations which leverage the Init Function, user-specific headers have to be set up within API Methods (where the final request to the external platform is created). | ||
:: | ||
|
||
```javascript | ||
const devHeaderExtension = { | ||
name: 'extension-dev-headers', | ||
hooks: (req, res) => { | ||
return { | ||
afterCreate: ({ configuration }) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why afterCreate instead of beforeCall? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe it could be both |
||
/** | ||
* This adds both `devMode` and `amazingHeader` to the Server Middleware client. | ||
*/ | ||
configuration.client.defaults.headers['dev-mode'] = req.headers['dev-mode']; | ||
configuration.client.defaults.headers['amazing-header'] = req.headers['amazing-header']; | ||
|
||
return configuration; | ||
} | ||
}; | ||
}, | ||
}; | ||
|
||
module.exports = { | ||
integrations: { | ||
<integration_key>: { | ||
// ... | ||
extensions: (extensions) => [...extensions, devHeaderExtension], | ||
} | ||
} | ||
}; | ||
``` | ||
|
||
You can verify the extension is loading by checking the console output when you start the server. You should see something like this: | ||
```bash | ||
βΉ - Loading: boilerplate extension: extension-dev-headers | ||
``` | ||
|
||
## Checkpoint 2 | ||
If everything is working correctly, you should now be able to see the custom header in your external service. | ||
|
||
You can verify this by logging the headers in your external service, or by using a tool like HTTP Toolkit to inspect the requests. | ||
:: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
title: Cookbook | ||
sidebarRoot: true | ||
navigation: | ||
icon: ri:book-2-fill |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it proper path?