This page covers adding APIs endpoints to your Headless store that cannot be used on the storefront and explicitly need to be called on a server. The guide also provides links to Swym utilities and some components that we recommend adding to your implementation.
Pre-requisites
- Setup your Hydrogen store. In case you haven't already, click here to get started with Shopify's guide.
- Create a shop access token: You need to sync your shop store with your hydrogen store. Doing so sync's your Shopify store's products with your Hydrogen store.
- Copy the Swym utility folder for Hydrogen onto your files using this link.
- Copy the Swym components folder for Hydogen onto your files using this link.
The below APIs must be called on a backend server. They cannot be called on the storefront/front-end as they carry sensitive information. Trying to call these APIs on the storefront may result in a CORS error (cross-origin-resource-sharing).
Step 1: Setup a config file to store Swym credentials and API endpoint
/*
@author: swym
@notice: Important config for swym workflow.
@dev: tokens to be added for target store
@return: REST_API_KEY - key from swym ( get it from swym-admin )
@return: ENDPOINT - swym base URL
@return: PID - swym pid for your store
@return: defaultWishlistName - default list name ( useful for single list )
@return: swymSharedURL - this 'swym' can be updated to any folder name for custom implementation
@return: alertTimeOut - alert popup timout in ms
*/
const SWYM_CONFIG = {
REST_API_KEY:
'',
ENDPOINT: '',
PID: '',
defaultWishlistName: 'My Wishlist',
alertTimeOut: 5000,
swymSharedURL: 'swym/shared-wishlist',
swymSharedMediumCopyLink: 'copylink',
};
export default SWYM_CONFIG;
Step 2: Get Email from shop access token using GraphQL
Code Overview:
This snippet of code retrieves the shop access token from browser cookies, and setup a GraphQL query for retrieving detailed information about a customer using their access token
import { v4 as uuidv4 } from 'uuid';
import { Buffer } from 'buffer';
import { PRODUCT_CARD_FRAGMENT } from '../../lib/fragments';
import { CacheNone, gql } from '@shopify/hydrogen';
import swymConfig from '../../swym/swym.config';
async function getUserEmailFromAccessToken(_request, queryShop) {
let useremail;
try {
const parsedCookie = parseCookie(_request.headers.get('cookie'));
const { customerAccessToken } = JSON.parse(parsedCookie.__session);
const { data } = await queryShop({
query: CUSTOMER_QUERY,
variables: {
customerAccessToken,
language: 'EN',
country: 'US',
},
cache: CacheNone(),
});
useremail = data?.customer?.email;
} catch (e) {
console.error('Cookie parse error', e);
}
return useremail;
}
const CUSTOMER_QUERY = gql`
${PRODUCT_CARD_FRAGMENT}
query CustomerDetails(
$customerAccessToken: String!
$country: CountryCode
$language: LanguageCode
) @inContext(country: $country, language: $language) {
customer(customerAccessToken: $customerAccessToken) {
id
firstName
lastName
phone
email
defaultAddress {
id
formatted
}
addresses(first: 6) {
edges {
node {
id
formatted
firstName
lastName
company
address1
address2
country
province
city
zip
phone
}
}
}
orders(first: 250, sortKey: PROCESSED_AT, reverse: true) {
edges {
node {
id
orderNumber
processedAt
financialStatus
fulfillmentStatus
currentTotalPrice {
amount
currencyCode
}
lineItems(first: 2) {
edges {
node {
variant {
image {
url
altText
height
width
}
}
title
}
}
}
}
}
}
}
featuredProducts: products(first: 12) {
nodes {
...ProductCard
}
}
featuredCollections: collections(first: 3, sortKey: UPDATED_AT) {
nodes {
id
title
handle
image {
altText
width
height
url
}
}
}
}
`;
const parseCookie = (str) =>
str
.split(';')
.map((v) => v.split('='))
.reduce((acc, v) => {
acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim());
return acc;
}, {});
Step 3: Generate a Swym Registration ID for a user
Code Overview:
async function generateRegID(_request, useremail): Uses the generate-regid API to generate a Swym registration ID for a user.
/*
@author: swym
@notice: This will call swym APIs and genrate regid for user
@dev: Post request take useremail or use uuid to return regid
@param: headers.cookie.__session to get customerAccessToken
@param: useragenttype mobile/desktop
*/
const REGIDURL = `${swymConfig.swymEndpoint}storeadmin/v3/user/generate-regid?appId=Wishlist`;
export async function api(_request, { queryShop }) {
const useremail = await getUserEmailFromAccessToken(_request, queryShop);
const regID = await generateRegID(_request, useremail);
return regID;
}
//Handles only logged in users ie when email is known
function attachUserUniqueIdentifier(useremail, urlencoded) {
urlencoded.append('useremail', useremail);
}
async function generateRegID(_request, useremail) {
if (useremail === null || useremail === 'undefined' || useremail === undefined) {
console.log("UserEmail is null");
return;
}
const parsedRequest = await _request.json();
const { useragenttype } = parsedRequest;
const config = getRequestConfig(swymConfig.pid, swymConfig.accessToken, useragenttype);
attachUserUniqueIdentifier(useremail, config.body);
try {
const res = await fetch(REGIDURL, config);
const jsonValue = await res.json(); // Get JSON value from the response body
if (res.ok) {
return jsonValue;
} else {
return { error: true };
}
} catch (e) {
console.error(JSON.stringify(e));
return { error: true };
}
}
function getRequestConfig(pid, accessToken, useragenttype ) {
const urlencoded = new URLSearchParams();
urlencoded.append('useragenttype', useragenttype);
const config = {
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${Buffer.from(`${pid}:${accessToken}`).toString('base64')}`,
},
redirect: 'follow',
body: urlencoded,
};
return config;
}
//When null set uuid as the unique identifier for anonymous user
//Call ValidateUserSync when the same user logs in to add an identity
function attachUserUniqueIdentifier(useremail, urlencoded) {
if (useremail != null && useremail !== 'undefined' && useremail !== undefined) {
urlencoded.append('useremail', useremail);
} else {
urlencoded.append('uuid', uuidv4());
}
}
Step 4: Call the guest-validate-sync API to update the Reg ID when a user logs in.
This is an optional step and not needed if wishlist is hidden behind login.
API used:
guest-validate-sync API: Updates guest registration ID by assosciating with a user-email address to the existing regid when a user signs up or log in.
import {Buffer} from 'buffer';
import swymConfig from '../../swym/swym.config';
import {CacheNone, gql} from '@shopify/hydrogen';
import {PRODUCT_CARD_FRAGMENT} from '../../lib/fragments';
/*
@author: swym
@notice: This will call swym APIs and sync regid with useremail after user log-in
@dev: Post request that use accesstoken to fetch user email and sync user activities done before login
@param: headers.cookie.__session to get customerAccessToken
@param: useragenttype mobile/desktop
@param: regid previously genrated regid
*/
const GUESTUSERSYNCURL =
`${swymConfig.swymEndpoint}storeadmin/v3/user/guest-validate-sync?appId=Wishlist`;
export async function guestUserSync(_request, {queryShop}) {
const useremail = await getUserEmailFromAccessToken(_request, queryShop);
const parsedRequest = await _request.json();
const {regid, useragenttype} = parsedRequest;
const config = getRequestConfig(swymConfig.pid, swymConfig.accessToken, useremail, useragenttype);
attachUserUniqueIdentifier(useremail, config.body);
config.body.append('regid', regid);
try {
const res = await fetch(GUESTUSERSYNCURL, config);
const jsonValue = await res.json(); // Get JSON value from the response body
if (res.ok) {
return jsonValue;
} else {
return {
error: true,
msg: JSON.stringify(res),
urlencoded: data,
};
}
} catch (e) {
console.error(JSON.stringify(e));
return {error: true, msg: JSON.stringify(e)};
}
}