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. Click here to get started, if you haven't already.
- 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)};
}
}