Skip to content

Instantly share code, notes, and snippets.

@panoply
Last active August 6, 2024 11:08
Show Gist options
  • Save panoply/d7e428a1f95392fb1efbdc2944d00d71 to your computer and use it in GitHub Desktop.
Save panoply/d7e428a1f95392fb1efbdc2944d00d71 to your computer and use it in GitHub Desktop.
Shopify Customer Login Status

Shopify Customer Login Status

This client-side implementation provides a deterministic reference for customer login status in Shopify. Open the flems example by pressing the badge to see it in action:

Flems

Theme Layout

In your theme.liquid or layout file, add the following data attribute to the <html> element. While not required, it is recommended for general use, though the solution will work programmatically.

<html data-customer="{%- if customer -%}true{%- else -%}false{%- endif -%}">

Code Snippet

Below is the JavaScript implementation. We use a Mutation Observer to efficiently interface with Liquid and ensure support for cases where partial DOM replacements occur. The global window definition and event-based (pub/sub) approach allow for multiple listeners and sequential operations across the webshop.

Object.defineProperty(window, 'customer', {
  configurable: false,
  writable: false,
  enumerable: false,
  value: (sub => {

    const attr = 'data-customer';
    const root = document.documentElement;
    const auth = (el = root) => el.getAttribute(attr) === 'true';
    const fire = x => x.length === 3 ? x[0].call(x[1], x.pop()) : x[0].call(x[1]);
    const observer = new MutationObserver(mutations => {
      for (const { type, target } of mutations) {
        if (type !== 'attributes') continue;
        target.hasAttribute(attr) && sub[auth(target) ? 'login' : 'logout'].forEach(fire);
      }
    });

    observer.observe(root, {
      attributes: true,
      attributeFilter: [ attr ]
    });

    if (auth()) setTimeout(() => customer.do('login'));

    return {
      get observer () { return observer; },
      get loggedin () { return auth(); },
      on: (name, cb, scope = null) => {
        const idx = sub[name].push([ cb, scope ]) - 1;
        return () => sub[name].splice(idx, 1);
      },
      do: (ev, arg) => {      
        if (arg !== undefined) sub[ev].forEach(x => x.length === 2 && x.push(arg))
        if (ev === 'login') {  
          auth() ? sub.login.forEach(fire) : root.setAttribute(attr, 'true');
        } else {
          auth() ? root.setAttribute(attr, 'false') : sub.logout.forEach(fire);
        }
      }
    };

  })({ login: [], logout: [] })
});

Here is a minified version of the above code:

Object.defineProperty(window,"customer",{configurable:!1,writable:!1,enumerable:!1,value:(t=>{const e="data-customer",o=document.documentElement,r=(t=o)=>"true"===t.getAttribute(e),u=t=>3===t.length?t[0].call(t[1],t.pop()):t[0].call(t[1]),n=new MutationObserver((o=>{for(const{type:n,target:l}of o)"attributes"===n&&l.hasAttribute(e)&&t[r(l)?"login":"logout"].forEach(u)}));return n.observe(o,{attributes:!0,attributeFilter:[e]}),r()&&setTimeout((()=>customer.do("login"))),{get observer(){return n},get loggedin(){return r()},on:(e,o,r=null)=>{const u=t[e].push([o,r])-1;return()=>t[e].splice(u,1)},do:(n,l)=>{void 0!==l&&t[n].forEach((t=>2===t.length&&t.push(l))),"login"===n?r()?t.login.forEach(u):o.setAttribute(e,"true"):r()?o.setAttribute(e,"false"):t.logout.forEach(u)}}})({login:[],logout:[]})});

API / Usage

Simply drop the JavaScript into your theme (bundle or wherever) and use the customer.on method. Below is the complete API and usage details:

customer.on(event, callback, this)

This function expects 2 parameters and optionally accepts a 3rd this context binding. There are only 2 available subscribers: customer.on('login') and/or customer.on('logout'). Examples:

// Basic login callback
customer.on('login', () => console.log('logged in'));

// Login callback with "this" scope
customer.on('login', function() { console.log(this); }, { foo: 'bar' });

// Basic logout callback
customer.on('logout', () => console.log('logged out'));

// Logout callback with "this" scope
customer.on('logout', function() { console.log(this); }, { foo: 'bar' });

Removing Event Subscriptions

If you want to remove subscriptions, each customer.on event returns a function, simply call it and to remove it from the sub store, for example:

const a = customer.on('login', () => console.log('logged in #1'))
const b = customer.on('login', () => console.log('logged in #2'))

// We can remove subscription a as follows:
a()

// Now, if you were to trigger calls only b (logged in #2) will fire
customer.do('login');

// You can optionally trigger a once callback by doing:
customer.on('login', () => console.log('logged in #3'))()

customer.do(event, ...arguments)

This method programmatically triggers subscribed events. It expects a string value of either login or logout as its first parameter. Optionally, you can pass additional arguments that subscribed events will receive.

// Trigger login events
customer.do('login');

// Trigger logout events
customer.do('logout');

// Passing data to subscription events
customer.on('login', (a, b, c) => console.log(a, b, c));

// When calling customer.do, pass data as follows:
customer.do('login', 'foo', {x: 1}, ['xxx']);

customer.loggedin

A readonly getter that returns a boolean value indicating if the customer is logged in.

if (customer.loggedin) console.log('logged in!');

customer.observer

Provides access to the Mutation Observer instance.


Author

Follow me on X if you like Shopify and banter.

License

You are not allowed to use this in themes published to theme store. Everyone else, you are fine, DWTFYW.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment