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

  1. Setup your Hydrogen store. In case you haven't already, click here to get started with Shopify's guide.
  2. 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.
  3. Copy the Swym utility folder for Hydrogen onto your files using this link.
  4. 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)};
  }
}