|
| 1 | +# Configuring a GraphQL Client for WooCommerce User Session Management |
| 2 | + |
| 3 | +In this comprehensive guide, we'll walk you through the process of configuring a GraphQL client to manage user sessions and credentials when working with WooGraphQL. By following the steps outlined in this tutorial, you'll learn how to create a GraphQL client that maintains a valid WooCommerce session in the `woocommerce_sessions` DB table. This knowledge will enable you to build robust applications that interact smoothly with WooCommerce while providing a seamless experience for your users and shortening development time. |
| 4 | + |
| 5 | +By properly handling the session token, you can implement session pass-off functionality, allowing you to fallback on the cart page, my-account page, or any other page living in WordPress that relies on user sessions. Note that implementing the session pass-off functionality is out of the scope of this guide. So, let's dive in and explore the intricacies of setting up a GraphQL client that effectively manages user sessions for your e-commerce store! |
| 6 | + |
| 7 | +When using WooGraphQL cart and customer functionality, there are certain prerequisites. A WooGraphQL session token, distributed by the QL Session Handler, must be passed as an HTTP header with the name `woocommerce-session`, prefixed with `Session`. This header should be included in all session data-altering mutations. Note that the required name `woocommerce-session` can be changed using WordPress filters. |
| 8 | + |
| 9 | +For simple requests using `fetch`, this is quite easy to implement. Here's an example of a WooGraphQL request executed with `fetch`, performing a cart query and passing the woocommerce-session header with a value of `Session ${sessionToken}`. The `sessionToken` is read from `localStorage`. |
| 10 | + |
| 11 | +```javascript |
| 12 | +fetch(endpoint, { |
| 13 | + method: 'POST', |
| 14 | + headers: { |
| 15 | + 'Content-Type': 'application/json', |
| 16 | + 'woocommerce-session': `Session ${sessionToken}`, |
| 17 | + }, |
| 18 | + body: JSON.stringify({ |
| 19 | + query: ` |
| 20 | + query { |
| 21 | + cart { |
| 22 | + contents { |
| 23 | + nodes { |
| 24 | + key |
| 25 | + product { |
| 26 | + node { |
| 27 | + id |
| 28 | + name |
| 29 | + } |
| 30 | + } |
| 31 | + quantity |
| 32 | + subtotal |
| 33 | + } |
| 34 | + } |
| 35 | + } |
| 36 | + } |
| 37 | + `, |
| 38 | + }), |
| 39 | +}) |
| 40 | + .then((response) => response.json()) |
| 41 | + .then((data) => console.log(data)); |
| 42 | +``` |
| 43 | + |
| 44 | +However, if you're using a library or framework like Apollo, configuring a middleware layer is required, which can be confusing if not explained or demonstrated effectively. In this guide, we'll walk you through setting up the Apollo Client and its middleware to work with WooGraphQL. |
| 45 | + |
| 46 | +## Creating the Apollo Client instance |
| 47 | + |
| 48 | +First, let's create the Apollo Client instance and focus on the `link` option. We'll use the `from` utility function from `@apollo/client`: |
| 49 | + |
| 50 | +```javascript |
| 51 | +import { from } from '@apollo/client'; |
| 52 | + |
| 53 | +// ... |
| 54 | + |
| 55 | +const client = new ApolloClient({ |
| 56 | + link: from([ |
| 57 | + createSessionLink(), |
| 58 | + createErrorLink(), |
| 59 | + new HttpLink({ uri: endpoint }), |
| 60 | + ]), |
| 61 | + cache: new InMemoryCache(), |
| 62 | +}); |
| 63 | +``` |
| 64 | + |
| 65 | +## Defining the `createSessionLink` function |
| 66 | + |
| 67 | +Next, define the `createSessionLink` function as follows: |
| 68 | + |
| 69 | +```javascript |
| 70 | +import { setContext } from '@apollo/client/link/context'; |
| 71 | + |
| 72 | +function createSessionLink() { |
| 73 | + return setContext(async (operation) => { |
| 74 | + const headers = {}; |
| 75 | + const sessionToken = await getSessionToken(); |
| 76 | + |
| 77 | + if (sessionToken) { |
| 78 | + headers['woocommerce-session'] = `Session ${sessionToken}`; |
| 79 | + |
| 80 | + return { headers }; |
| 81 | + } |
| 82 | + |
| 83 | + return {}; |
| 84 | + }); |
| 85 | +} |
| 86 | +``` |
| 87 | + |
| 88 | +## About the environment variables |
| 89 | + |
| 90 | +Before we dive into the guide, it's important to note that the `process.env.*` variables used throughout the tutorial are simply string values stored in an `.env` file and loaded using the [**dotenv**](https://www.npmjs.com/package/dotenv) package. As a reader, you can replace these variables with any values that suit your needs. |
| 91 | + |
| 92 | +Here's a sample .env file to help you get started: |
| 93 | + |
| 94 | +```makefile |
| 95 | +SESSION_TOKEN_LS_KEY=my_session_token |
| 96 | +REFRESH_TOKEN_LS_KEY=my_refresh_token |
| 97 | +AUTH_TOKEN_LS_KEY=my_auth_token |
| 98 | +AUTH_KEY_TIMEOUT=30000 |
| 99 | +GRAPHQL_ENDPOINT=http://woographql.local/graphql |
| 100 | +``` |
| 101 | + |
| 102 | +## Defining the `getSessionToken` and `fetchSessionToken` functions |
| 103 | + |
| 104 | +Here are the `getSessionToken` and `fetchSessionToken` functions: |
| 105 | + |
| 106 | +```javascript |
| 107 | +import { GraphQLClient } from 'graphql-request'; |
| 108 | + |
| 109 | +// Session Token Management. |
| 110 | +async function fetchSessionToken() { |
| 111 | + let sessionToken; |
| 112 | + try { |
| 113 | + const graphQLClient = new GraphQLClient(process.env.GRAPHQL_ENDPOINT); |
| 114 | + |
| 115 | + const cartData = await graphQLClient.request(GetCartDocument); |
| 116 | + |
| 117 | + // If user doesn't have an account return accountNeeded flag. |
| 118 | + sessionToken = cartData?.cart?.sessionToken; |
| 119 | + |
| 120 | + if (!sessionToken) { |
| 121 | + throw new Error('Failed to retrieve a new session token'); |
| 122 | + } |
| 123 | + } catch (err) { |
| 124 | + console.error(err); |
| 125 | + } |
| 126 | + |
| 127 | + return sessionToken; |
| 128 | +} |
| 129 | + |
| 130 | +export async function getSessionToken(forceFetch = false) { |
| 131 | + let sessionToken = localStorage.getItem(process.env.SESSION_TOKEN_LS_KEY as string); |
| 132 | + if (!sessionToken || forceFetch) { |
| 133 | + sessionToken = await fetchSessionToken(); |
| 134 | + } |
| 135 | + return sessionToken; |
| 136 | +} |
| 137 | +``` |
| 138 | +
|
| 139 | +Defining the `GetCartDocument` |
| 140 | +
|
| 141 | +Here's the `GetCartDocument`: |
| 142 | +
|
| 143 | +```javascript |
| 144 | +import { gql } from '@apollo/client'; |
| 145 | + |
| 146 | +export const GetCartDocument = gql` |
| 147 | + query { |
| 148 | + customer { |
| 149 | + sessionToken |
| 150 | + } |
| 151 | + } |
| 152 | +`; |
| 153 | + |
| 154 | +``` |
| 155 | +
|
| 156 | +It's highly recommended to retrieve the session token outside of the Apollo Client for better control of its value. To ensure no request gets sent without a session token, we must define an error link to capture failed queries caused by an invalid or expired session token, delete the current session, and retrieve a new one. Here's the `createErrorLink` function: |
| 157 | +
|
| 158 | +```javascript |
| 159 | +import { onError } from '@apollo/client/link/error'; |
| 160 | +import { Observable } from '@apollo/client/utilities'; |
| 161 | + |
| 162 | +function createErrorLink() { |
| 163 | + return onError(({ graphQLErrors, operation, forward }) => { |
| 164 | + const targetErrors = [ |
| 165 | + 'The iss do not match with this server', |
| 166 | + 'invalid-secret-key | Expired token', |
| 167 | + 'invalid-secret-key | Signature verification failed', |
| 168 | + 'Expired token', |
| 169 | + 'Wrong number of segments', |
| 170 | + ]; |
| 171 | + let observable; |
| 172 | + if (graphQLErrors?.length) { |
| 173 | + graphQLErrors.map(({ debugMessage, message }) => { |
| 174 | + if (targetErrors.includes(message) || targetErrors.includes(debugMessage)) { |
| 175 | + observable = new Observable((observer) => { |
| 176 | + getSessionToken(true) |
| 177 | + .then((sessionToken) => { |
| 178 | + operation.setContext(({ headers = {} }) => { |
| 179 | + const nextHeaders = headers; |
| 180 | + |
| 181 | + if (sessionToken) { |
| 182 | + nextHeaders['woocommerce-session'] = `Session ${sessionToken}`; |
| 183 | + } else { |
| 184 | + delete nextHeaders['woocommerce-session']; |
| 185 | + } |
| 186 | + |
| 187 | + return { |
| 188 | + headers: nextHeaders, |
| 189 | + }; |
| 190 | + }); |
| 191 | + }) |
| 192 | + .then(() => { |
| 193 | + const subscriber = { |
| 194 | + next: observer.next.bind(observer), |
| 195 | + error: observer.error.bind(observer), |
| 196 | + complete: observer.complete.bind(observer), |
| 197 | + }; |
| 198 | + forward(operation).subscribe(subscriber); |
| 199 | + }) |
| 200 | + .catch((error) => { |
| 201 | + observer.error(error); |
| 202 | + }); |
| 203 | + }); |
| 204 | + } |
| 205 | + return message; |
| 206 | + }); |
| 207 | + } |
| 208 | + return observable; |
| 209 | + }); |
| 210 | +} |
| 211 | +``` |
| 212 | +
|
| 213 | +With the creation of the error link, we now have an Apollo Client that completely manages the WooCommerce session. Note that this doesn't account for all use cases, specifically dealing with registered WooCommerce customers. In such cases, you'll need to use a second JWT for identifying their WordPress account, called an Authentication Token or auth token for short. For handling user authentication, auth tokens, and refresh tokens, refer to the next guide. |
| 214 | +
|
| 215 | +This should provide you with a solid foundation for setting up a GraphQL client that effectively manages user sessions in your e-commerce application. By following the steps outlined, you'll be able to create a seamless experience for your users when interacting with both WooCommerce, ultimately saving development time and effort. |
0 commit comments