npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

enhanced-analytics

v1.0.308

Published

Couple of convenient tools for populating dataLayer ecommerce event data.

Downloads

1,303

Readme

enhanced-analytics

A couple of convenient tools for populating dataLayer ecommerce event data or even more.

You can use them either on the Frontend or the Backend sides. The Faacebook Pixel events can be configured on both sides, just to increase pixel's performance.

integrated services:
  ✅ Google Analytics & GA4 / Tag Manager (Browser Only)
  ✅ Klaviyo (Server+Browser) https://www.klaviyo.com/

  • Install npm i -S [email protected] when this lib is being used at NodeJs
  • This will auto-install js when running it in a broswer.

  ✅ Facebook (Server+Browser)

  ✅ FullStory (Broswer Only) https://www.fullstory.com/

  • Install npm i @fullstory/browser when this lib is being used at NodeJs

1 Configure (UI Side)

import { useEffect } from 'react';
import { configureAnalytics } from 'enhanced-analytics';
import * as EATypes from 'enhanced-analytics';

const MyApp = () => {
  // store configuration
  const activeStore = {
    name: 'My Store',
    homepage: 'www.my-store.com',
    localization: {
      currency: 'USD',
    },
  };

  useEffect(() => {
    configureAnalytics({
      affiliation: activeStore.name,
      description: 'Your store description. This will appear in product feeds too',
      absoluteURL: activeStore.homepage,
      currency: activeStore.localization.currency,
      debug: /* set true when localhost or dev env */
      integrations: {
        // Klaviyo
        klaviyo: {
          enabled: true,
          siteId: 'YOUR-SITE-ID',
        },
        // Google Analytics (TagManager)
        ga: {
          enabled: true,
          trackId: 'GTM-XXXXXXX',
          ga4: true, // <-- publish GA4 events data
          // more default params:
          // defaultCatalogName: `${activeStore.name} Landing Products`,
          // defaultBasketName: 'Shopping Cart',
          // dataLayerName: 'dataLayer'
        },
        // FullStory
        fullstory: {
          enabled: true,
          orgId: 'YOUR-ORG-ID',
          // @ts-ignore
          sdk: FullStory, // <-- this requires: npm i @fullstory/browser
        },
        // Facebook Pixel
        fb: {
          enabled: true,
          pixelId: 'YOUR-PIXEL-ID',
          testCode: 'TEST61709', // <---- test code, when testing Pixel data (server side only)
        },
      },
      // you may have your own data structure
      // therefore we need it converted for the lib here
      // This is just real use-case.
      resolvers: {
        // custom data transformation configuration
        // prettier-ignore
        page(input) {//  <================================|
          //   ^^ this would be 'test'                    |
          return {                          //            |
            id: '',                         //            |
            name: document.title,           //            |
            path: window.location.pathname, //            |
            url: window.location.href,      //            |
            title: document.title,          //            |
          };                                //            |
        }, //                               //            |
        //                                                |
        // ^^ here, If you call useAnalytics().withPage('test').integrations.klaviyo.trackPageView();
        //    and the same approach for the other scopes: withUser, withBasket... etc.
        profile(input) {
          const currUser = input || /* get session user */;
          return currUser?.userName && currUser?.email === 12
            ? {
                email: currUser.email,
                firstName: currUser.userName,
              }
            : null;
        },
        product: (p: any) => {
          const res: EATypes.T_EA_DataProduct = {
            id: p.id,
            brand: p.seller,
            category: p.category,
            description: p.description,
            isSale: !!p.promo,
            price: p.price,
            salePrice: p.price,
            title: p.title,
            sku: p.sku,
            viewOrder: p.viewOrder,
          };
          return res;
        },
        basket: () => {
          const diff = CartBuilderStore.getLastDiff();
          const res: EATypes.TDataBasket = {
            coupon: CartBuilderStore.getCouponCode(),
            total: CartBuilderStore.getCartTotal(),
            quantity: CartBuilderStore.getItemsCount(),
            lastAdded: diff?.lastAddedItems.map(mapCartItemToAnalytics) || [],
            lastRemoved:
              diff?.lastRemovedItems.map(mapCartItemToAnalytics) || [],
            products: CartBuilderStore.getItems().map(mapCartItemToAnalytics),
          };
          return res;
        },
        order: (o: any) => {
          const res: EATypes.T_EA_DataOrder = {
            id: o.id,
            coupon: o.coupon,
            dateCreated: o.dateCreated,
            revenue: o.costsDetails.net,
            status: o.status,
            tax: o.taxValue,
            payment: {
              type: o.paymentType,
            },
            products: o.orderProducts,
            quantity: o.orderTotal
            customer: {
              email: o.customerEmail,
              firstName: o.customerFullName,
              lastName: o.customerLastName,
              phone: o.customerPhone,
              address: {
                street: o.customerAddress,
              },
            },
            shipping: {
              cost: o.costsDetails.feeValue,
              name: o.deliveryMethod,
              address: {
                street: o.customerAddress,
              },
            },
          };
          return res;
        },
      },
    });
  }, []);

  return <div>my app</div>;
};

1.2 Google Analytics

... somewhere in components:

import useAnalytics from 'enhanced-analytics';

const MyComponent = () => {
  const analytics = useAnalytics();

  useEffect(() => {
    const myProductItems = [];

    //
    // Google Analytics: track basket add/remove items
    //
    analytics
      .withBasket(/* TDataBasket|Record<any>|null */) // <- this can be empty or TDataBasket AND resolver.basket is being invoked as well
      .events.ga()
      .getEECCheckoutList()
      .when(() => true /* or your condition */) // <----- or omit this call, if there is no any conditions
      .push(); // <- inject event into the dataLayer (config dataLayerName default is 'dataLayer');

    //
    // Google Analytics: track search/on-page product items
    //
    analytics
      .withCatalog(/* your array of goods; any custom data[] or T_EA_DataProduct[] */)
      // ^ the resolver.product is being invoked over the each item in the given collection
      .events.ga()
      .getEECProductsList()
      .push();

    //
    // Google Analytics: track product details
    //
    analytics
      .withCatalog(/* array with just one product data [T_EA_DataProduct] */)
      // ^ the resolver.product is being invoked over the each item in the given collection
      .events.ga()
      .getEECProductDetails()
      .when(() => /* producItem is loaded */)
      .push();

    //
    // Google Analytics: track order creation
    //
    analytics
      .withOrder(/* TDataOrder or any custom object */)
      // ^ invokes resolver.order
      .events.ga()
      .getEECPurchased()
      .when(() => !thisOrderWasSeen) // why not, implement your logic, that prevents duplicated events
      .push();
  }, []);

  return <></>;
};

1.2.Klaviyo UI / FullStory UI / Facebook Pixel

import useAnalytics from 'enhanced-analytics';

const MyComponent = () => {
  const analytics = useAnalytics();
  const order = useOrder();

  // Track PageView
  useEffect(() => {
    // page tracking
    const evtPageView = analytics.withPage().events;

    evtPageView.fullstory().trackPageView();
    evtPageView.klaviyo().trackPageView();
  }, []);

  // Track User Indetify
  useEffect(() => {
    // page tracking
    const evtPageView = analytics.withPage().events;
    evtProfile.fullstory().trackIdentify();
    evtProfile.klaviyo().trackIdentify();
    evtPageView.fb().trackPageView();
  }, []);

  // Track Order Complete + Custom event "OrderSeen"
  useEffect(() => {
    const evtOrder = analytics.withOrder(order).events;
    const evtProfile = analytics.withProfile({
      userName: order.customerFullName,
      phone: order.customerPhone,
    }).events;
    // the custom event. This is going to track 'orderSeen' event
    const evtCustom = analytics.withMisc('orderSeen', {
      orderId: order.id,
      orderDate: moment(order.dateCreated).format(),
      orderTotal: order.total,
    }).events;

    // push the 'eec.purchase' evet along with order details
    evtOrder.ga().getEECPurchased().push();

    // indetify current session (this will link anonymous events to this user by email)
    evtProfile.fullstory().trackIdentify();
    evtProfile.klaviyo().trackIdentify();
    evtPageView.fb().trackIdentify();

    // any other custom events
    evtCustom.fullstory().trackCustom();
    evtCustom.klaviyo().trackCustom();
    evtPageView.fb().trackCustom();
  }, []);
};

2 Configure (Backend Side)

Simple snippet with Express.Js as middleware:

app.use(
  analyticsMiddleware({
    absoluteURL: 'https://www.your-domain.lviv.ua/',
    serverAnalytics: {
      testing: false,
      klaviyo: {
        enabled: true,
        token: 'pk_token-goes-here',
        sdk: require('klaviyo-api'), // npm i [email protected]
      },
      userIdentification: {
        // this field is used this way: incoming request has body and we
        // check if this field contains req.body, if so we store the whole req.body
        // into app.locals.customer = req.body
        reqBodyKey: 'customerPhone',
      },
    },
    resolvers: (req) => ({
      order(evtPayload: any) {
        // in this case { order, products } (see order tracking at 2.2.KalviyoAPI below)
        const order = evtPayload.order;
        const orderProducts = evtPayload.products;
        return {
          id: order.id,
          revenue: order.total,
          tax: 0,
          quantity: order.features.length,
          coupon: order.coupon,
          products: [],
          dateCreated: order.dateCreated,
          status: order.status,
          shipping: {
            cost: order.deliveryFee,
            name: order.deliveryMethod,
            address: {
              street: order.customerAddress,
            },
          },
          customer: {
            firstName: order.customerFullName,
            email: `[email protected]`,
          },
          payment: {
            type: order.paymentType,
          },
          url: `${req.protocol}://${req.hostname}/order/success/${order.externalId}`,
        };
      },
      profile() {
        return app.locals.customer.customerPhone
          ? {
              email: `[email protected]`,
              firstName: app.locals.customer.customerFullName,
              phoneNumber: '5551234567',
              address: {
                country: 'United States',
                city: 'Boston',
                postcode: '02110',
                region: 'MA',
                countryCode: 'UA',
                street: app.locals.customer.customerAddress,
              },
            }
          : null;
      },
      eventUUID() {
        return app.locals.evtUuid;
      },
      product(input: TDataList<IDataProduct>) {
        const l = input.items.map((prodItem) => {
          return {
            id: prodItem.id,
            title: prodItem.title,
            description: prodItem.description,
            price: prodItem.price,
            salePrice: prodItem.price,
            isSale: false,
            brand: prodItem.seller,
            category: prodItem.categoryName,
            sku: prodItem.sku,
            list: 'main',
            url: `${req.protocol}://${req.hostname}/product/${prodItem.id}/${prodItem.sku}`,
            imageUrl: prodItem.imageUrl,
          };
        });
        return l;
      },
      page() {
        return {
          id: req.baseUrl,
          name: req.path.split('/')[0],
          path: req.path,
          title: 'Main Page',
          url: `${req.protocol}://${req.hostname}${req.originalUrl}`,
        };
      },
      session() {
        return {
          // agent, fbp and ip are optional when using fb tracking
          agent: req.headers['user-agent'],
          fbp: req.cookies['_fbp'],
          ip: req.ip,
        };
      },
    }),
  })
);

Another configuration for NextJs:


// src/utils/ea.ts
// Server Side EA Configuration for NextJs
const getServerEA = (req: GetServerSidePropsContext['req']) => {
  const ea = useAnalytics({
    absoluteURL: "https://my-store.com/",
    affiliation: "My Store",
    currency: "USD",
    integrations: {
      testing: true,
      fb: {
        enabled: true,
        pixelId: "PIXEL-ID",
        token: "PIXEL-TOKEN",
        sdk: bizSdk,
        testCode: "TESTxxxxx",
      },
    },
    resolvers: {
      eventUUID() {
        return Date.now().toString(32);
      },
      session() {
          // agent, fbp and ip are optional when using fb tracking
        return {
          agent: req.headers["user-agent"],
          fbp: req.cookies["_fbp"],
          ip: req.socket.remoteAddress,
        };
      },
      profile() {
        const user = /* get session user info */
        return user
          ? {
              email: user.email,
              firstName: user.username,
            }
          : null;
      },
      basket() {
        return {
          total: 0,
          coupon: null,
          quantity: 0,
          lastAdded: [],
          lastRemoved: [],
          products: [],
        };
      },
    },
  });

  return ea;
}

// See usage below at 2.3.FB Pixel

2.1 Use It

2.2.Klaviyo API

Track new order:

import useAnalytics from 'enhanced-analytics';

const evtPayload = { order, products };
// you can define your own payload
// and handle it at your resolvers.order function
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackTransaction();

Begin checkout:

import useAnalytics from 'enhanced-analytics';

const evtPayload = { order, products };
// you can define your own payload
// and handle it at your resolvers.order function
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackInitiateCheckout();

All methods:

// track Identify
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackIdentify();

// track Transaction
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackTransaction();

// track ProductAddToCart
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackProductAddToCart();

// track ProductRemoveFromCart
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackProductRemoveFromCart();

// track ProductItemView
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackProductItemView();

// track ProductsItemView
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackProductsItemView();

// track Search
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackSearch();

// track PageView
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackPageView();

// track InitiateCheckout
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackInitiateCheckout();

// track NewProfile
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackNewProfile();

// track ProfileResetPassword
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackProfileResetPassword();

// track ProfileLogIn
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackProfileLogIn();

// track ProfileLogOut
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackProfileLogOut();

// track ProfileSubscribeNL
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackProfileSubscribeNL();

// track TransactionRefund
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackTransactionRefund();

// track TransactionCancel
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackTransactionCancel();

// track TransactionFulfill
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackTransactionFulfill();

// track Custom
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackCustom();

2.2.FB Pixel (Server+UI)

This examples shows how to send server-side (NextJs) fb events and then re-process them from the UI.

import { GetServerSideProps } from "next";
import { useEffect } from "react";
import {
  EA_FB_Server_RePublish_Events,
  TFbNormalizedEventPayload,
} from "enhanced-analytics/apiTracker/facebook";
import useAnalytics, { configureAnalytics } from "enhanced-analytics";
import * as bizSdk from "facebook-nodejs-business-sdk";
import { GetServerSidePropsContext } from "next";

interface IShopProductResponse {
  eaFbEvents: any;
}

export default function AnyProductPage({
  eaFbEvents,
}: IShopProductResponse) {
    useEffect(() => {
      analytics
        .withPage({
          name: props.title,
          path: window.location.pathname,
        })
        .events.fb()
        .trackPageView();
    }, []);
  return (
    <div>
      <span>testing fb events</span>
      {* The component from EA, which is digesting handed server response *}
      <EA_FB_Server_RePublish_Events serverPayloads={props.eaFbEvents} />
    </div>
  );
}

export const getServerSideProps: GetServerSideProps = async ({ req, }) => {
  // analytics
  const product = fetch(/* your api that fetches product data */);
  const ea = getServerEA(req); /* see NextJs configuration */
  const fbResp = await ea
    .withCatalog([
      {
        id: product.id,
        title: product.title,
        description: product.description,
        salePrice: roundToTwo(product.salePrice),
        price: product.salePrice || product.price,
        isSale: roundToTwo(product.salePrice) > 0,
        brand: product.brand,
        category: product.category,
        color: product.color,
        sku: product.sku,
        imageUrl: product.images.length > 0 ? product.images[0] : void 0,
        url: `https://my-store.com/any-product/${product.shortId}/${product.slug}`,
      },
    ])
    .s2s.fb()
    .trackProductItemView(); // server to server event

  return {
    props: {
      // @ts-ignore
      eaFbEvents: fbResp[0].value.payload
    },
  };
};

3 useAnalytics top methods

const ea = useAnalytics();

// set runtime user
ea.identify(user: T_EA_DataProfile);

// custom events
ea.withMisc(name: string, attributes?: Record<string, any>);

// ...TBD
ea.withPage(payload: T_EA_DataPage | Record<string, any> | null = null);
ea.withProfile(payload: T_EA_DataProfile | Record<string, any> | null = null);
ea.withCatalog(payload: (T_EA_DataProduct | Record<string, any>)[] | null = null);
ea.withBasket(payload: T_EA_DataBasket | Record<string, any> | null = null);
ea.withOrder(payload: T_EA_DataOrder | Record<string, any> | null = null);

// TBD