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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zixflow/analytics-browser

v1.1.5

Published

Complete guide for integrating the Zixflow JavaScript SDK into your browser and Node.js applications.

Readme

Zixflow JavaScript SDK - Integration Guide

Complete guide for integrating the Zixflow JavaScript SDK into your browser and Node.js applications.

Table of Contents

  1. Introduction
  2. Requirements
  3. Installation
  4. Quick Start
  5. Core Features
  6. Browser-Specific Features
  7. Node.js-Specific Features
  8. Web Push Notifications
  9. Push Notification Tracking
  10. Advanced Configuration
  11. TypeScript Support
  12. Example Apps
  13. API Reference

Introduction

The Zixflow JavaScript SDK enables you to send data from your browser and Node.js applications to Zixflow, allowing you to track user behavior, send targeted push notifications, display in-app messages, and more.

SDK Packages

The SDK is distributed as three npm packages:

  • @zixflow/analytics-browser: For web browsers (client-side)
  • @zixflow/analytics-node: For Node.js servers (server-side)
  • @zixflow/analytics-core: Shared core functionality (automatically installed)

SDK Features

Browser SDK:

  • User identification and event tracking
  • Web push notifications with service workers
  • In-app messaging and inbox
  • Page view tracking
  • Automatic page enrichment
  • Video plugin support (YouTube, Vimeo)

Node.js SDK:

  • User identification and event tracking
  • Server-side analytics
  • Event batching and retries
  • Flexible configuration
  • TypeScript support

Requirements

Browser

  • Modern browsers: Chrome, Firefox, Safari, Edge (latest versions)
  • ES6 support: Required for optimal performance
  • Service Worker support: Required for web push notifications
  • HTTPS: Required for web push notifications

Node.js

  • Node.js: 14.0 or higher
  • npm: 6.0 or higher
  • TypeScript: 4.0+ (optional, for TypeScript projects)

Installation

The Zixflow JavaScript SDK is available on npm. The latest version is 1.1.5.

📦 Browser SDK: npm - @zixflow/analytics-browser 📦 Node.js SDK: npm - @zixflow/analytics-node

Browser SDK

Install via npm:

npm install @zixflow/analytics-browser@^1.1.5

Or via yarn:

yarn add @zixflow/analytics-browser@^1.1.5

Node.js SDK

Install via npm:

npm install @zixflow/analytics-node@^1.1.5

Or via yarn:

yarn add @zixflow/analytics-node

Quick Start

Browser Quick Start

import { AnalyticsBrowser } from '@zixflow/analytics-browser'

// Initialize with your API key
const analytics = AnalyticsBrowser.load({
  apiKey: 'YOUR_API_KEY'
})

// Identify a user
analytics.identify('[email protected]', {
  first_name: 'John',
  last_name: 'Doe',
  email: '[email protected]'
})

// Track an event
analytics.track('button_clicked', {
  button_name: 'signup',
  page: 'homepage'
})

// Track a page view
analytics.page('Home', {
  title: 'Homepage',
  url: window.location.href
})

Node.js Quick Start

import { Analytics } from '@zixflow/analytics-node'

// Initialize with your API key
const analytics = new Analytics({
  writeKey: 'YOUR_API_KEY'
})

// Identify a user
analytics.identify({
  userId: '[email protected]',
  traits: {
    first_name: 'John',
    last_name: 'Doe',
    email: '[email protected]'
  }
})

// Track an event
analytics.track({
  userId: '[email protected]',
  event: 'purchase_completed',
  properties: {
    product_id: '123',
    price: 29.99,
    currency: 'USD'
  }
})

// Flush events before exit
await analytics.closeAndFlush()

Core Features

User Identification

Identify users to track their activity across sessions.

Browser

// Identify with user ID only
analytics.identify('[email protected]')

// Identify with traits
analytics.identify('[email protected]', {
  first_name: 'John',
  last_name: 'Doe',
  email: '[email protected]',
  plan: 'premium',
  created_at: new Date().toISOString()
})

// With callback
analytics.identify('[email protected]', {
  email: '[email protected]'
}, () => {
  console.log('User identified successfully')
})

Node.js

// Identify with user ID and traits
analytics.identify({
  userId: '[email protected]',
  traits: {
    first_name: 'John',
    last_name: 'Doe',
    email: '[email protected]',
    plan: 'premium'
  }
})

// With anonymous ID
analytics.identify({
  userId: '[email protected]',
  anonymousId: 'anon-123',
  traits: {
    email: '[email protected]'
  }
})

// With callback
analytics.identify({
  userId: '[email protected]',
  traits: { email: '[email protected]' }
}, (err) => {
  if (err) {
    console.error('Identification failed:', err)
  } else {
    console.log('User identified successfully')
  }
})

Event Tracking

Track custom events to understand user behavior.

Browser

// Simple event
analytics.track('button_clicked')

// Event with properties
analytics.track('purchase_completed', {
  product_id: '123',
  product_name: 'Widget',
  price: 29.99,
  currency: 'USD',
  quantity: 1
})

// With callback
analytics.track('signup_completed', {
  method: 'email'
}, () => {
  console.log('Event tracked successfully')
})

Node.js

// Track event with user ID
analytics.track({
  userId: '[email protected]',
  event: 'purchase_completed',
  properties: {
    product_id: '123',
    product_name: 'Widget',
    price: 29.99,
    currency: 'USD'
  }
})

// Track event with anonymous ID
analytics.track({
  anonymousId: 'anon-123',
  event: 'page_viewed',
  properties: {
    page: 'homepage'
  }
})

// With timestamp
analytics.track({
  userId: '[email protected]',
  event: 'order_completed',
  properties: { order_id: 'ABC123' },
  timestamp: new Date()
})

// With callback
analytics.track({
  userId: '[email protected]',
  event: 'signup_completed'
}, (err) => {
  if (err) {
    console.error('Tracking failed:', err)
  }
})

Page Tracking

Track page views in your web application.

Browser

// Track current page
analytics.page()

// Track with page name
analytics.page('Home')

// Track with category and name
analytics.page('Docs', 'Getting Started')

// Track with properties
analytics.page('Product Detail', {
  product_id: '123',
  category: 'Electronics',
  url: window.location.href,
  referrer: document.referrer
})

// Automatic page tracking on route changes
// For Single Page Applications (SPAs):
window.addEventListener('popstate', () => {
  analytics.page()
})

Node.js

// Track page view
analytics.page({
  userId: '[email protected]',
  name: 'Home',
  properties: {
    title: 'Homepage',
    url: 'https://example.com'
  }
})

// With category
analytics.page({
  userId: '[email protected]',
  category: 'Docs',
  name: 'Getting Started',
  properties: {
    url: 'https://example.com/docs/getting-started'
  }
})

Screen Tracking

Track screen views in your application.

Browser

// Track screen view
analytics.screen('Dashboard', {
  user_type: 'premium'
})

Node.js

// Track screen view
analytics.screen({
  userId: '[email protected]',
  name: 'Dashboard',
  properties: {
    user_type: 'premium',
    version: '2.0'
  }
})

Group Tracking

Associate users with groups or organizations.

Browser

// Associate user with group
analytics.group('company-123', {
  name: 'Acme Inc',
  plan: 'enterprise',
  employees: 100
})

Node.js

// Associate user with group
analytics.group({
  userId: '[email protected]',
  groupId: 'company-123',
  traits: {
    name: 'Acme Inc',
    plan: 'enterprise',
    employees: 100,
    industry: 'technology'
  }
})

Alias

Merge two user identities.

Browser

// Merge anonymous user with identified user
analytics.alias('[email protected]', 'anon-123')

Node.js

// Merge user identities
analytics.alias({
  userId: '[email protected]',
  previousId: 'anon-123'
})

Reset/Logout

Clear user identification when they log out.

Browser

// Clear user identification
analytics.reset()

// Good practice: reset on logout
function handleLogout() {
  analytics.reset()
  // ... rest of logout logic
}

Browser-Specific Features

Automatic Page Enrichment

The browser SDK automatically enriches page events with contextual information:

  • Page URL and referrer
  • Page title
  • User agent
  • Screen dimensions
  • Locale and timezone
  • Campaign parameters (UTM tags)

Loading State

// Wait for analytics to be ready
analytics.ready(() => {
  console.log('Analytics is ready!')
  // Perform actions that depend on analytics
})

Plugin System

Extend the SDK with custom plugins:

const customPlugin = {
  name: 'Custom Plugin',
  type: 'enrichment',
  version: '1.0.0',

  isLoaded: () => true,
  load: async (ctx, analytics) => {
    console.log('Plugin loaded')
  },

  track: async (ctx) => {
    // Modify or enrich track events
    ctx.event.properties = {
      ...ctx.event.properties,
      custom_property: 'value'
    }
    return ctx
  }
}

analytics.register(customPlugin)

CDN Usage

Load the SDK directly from CDN:

<!DOCTYPE html>
<html>
<head>
  <script>
    !function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Zixflow snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on","addSourceMiddleware","addIntegrationMiddleware","setAnonymousId","addDestinationMiddleware"];analytics.factory=function(e){return function(){var t=Array.prototype.slice.call(arguments);t.unshift(e);analytics.push(t);return analytics}};for(var e=0;e<analytics.methods.length;e++){var key=analytics.methods[e];analytics[key]=analytics.factory(key)}analytics.load=function(key,e){var t=document.createElement("script");t.type="text/javascript";t.async=!0;t.src="https://cdn.zixflow.com/analytics.js/v1/"+key+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(t,n);analytics._loadOptions=e};analytics._writeKey="YOUR_API_KEY";analytics.SNIPPET_VERSION="4.15.3";
    analytics.load("YOUR_API_KEY");
    analytics.page();
    }}();
  </script>
</head>
<body>
  <h1>My Website</h1>
</body>
</html>

Node.js-Specific Features

Event Batching

The Node.js SDK automatically batches events for efficiency:

const analytics = new Analytics({
  writeKey: 'YOUR_API_KEY',
  maxEventsInBatch: 15,  // Default: 15 events per batch
  flushInterval: 10000    // Default: 10 seconds
})

Flushing Events

Ensure all events are sent before application exit:

// Flush all pending events
await analytics.closeAndFlush()

// With timeout
await analytics.closeAndFlush({ timeout: 5000 })

// Good practice: flush on process exit
process.on('SIGTERM', async () => {
  await analytics.closeAndFlush()
  process.exit(0)
})

Error Handling

Handle errors with event emitters:

analytics.on('error', (err) => {
  console.error('Analytics error:', err)
})

analytics.on('http_request', (event) => {
  console.log('HTTP request:', event)
})

Callbacks

Use callbacks for individual events:

analytics.track({
  userId: '[email protected]',
  event: 'signup_completed'
}, (err, batch) => {
  if (err) {
    console.error('Failed to track event:', err)
  } else {
    console.log('Event tracked successfully')
  }
})

Web Push Notifications

Enable web push notifications in the browser to send timely, engaging messages to users.

** Track your campaigns:** The SDK automatically tracks delivery, opens, and clicks. For advanced tracking customization and implementation details, see Push Notification Tracking.

Prerequisites

  1. HTTPS: Web push requires a secure context (HTTPS)
  2. Service Worker: A service worker file to handle push events
  3. API Key: Your Zixflow API key
  4. User Permission: Users must grant notification permission

Step 1: Install Dependencies

npm install @zixflow/analytics-browser

Step 2: Create Service Worker

Create a file public/sw.js (or public/zixflow-sw.js):

// Service Worker for Zixflow Web Push Notifications

self.addEventListener('install', (event) => {
  console.log('[SW] Installing service worker')
  self.skipWaiting()
})

self.addEventListener('activate', (event) => {
  console.log('[SW] Activating service worker')
  event.waitUntil(self.clients.claim())
})

self.addEventListener('push', (event) => {
  console.log('[SW] Push event received')

  if (!event.data) {
    console.warn('[SW] Push event has no data')
    return
  }

  let data
  try {
    data = event.data.json()
  } catch (e) {
    console.error('[SW] Failed to parse push data:', e)
    return
  }

  const title = data.title || 'Notification'
  const options = {
    body: data.body || '',
    icon: data.icon || '/icon.png',
    badge: data.badge || '/badge.png',
    image: data.image,
    data: data,
    tag: data.tag || 'zixflow-push',
    requireInteraction: data.requireInteraction || false,
    actions: data.actions || []
  }

  event.waitUntil(
    self.registration.showNotification(title, options)
  )
})

self.addEventListener('notificationclick', (event) => {
  console.log('[SW] Notification clicked:', event.action)

  event.notification.close()

  const clickedUrl = event.action
    ? event.notification.data.actions?.[event.action]?.url
    : event.notification.data.url || '/'

  event.waitUntil(
    clients.openWindow(clickedUrl).then((windowClient) => {
      if (windowClient) {
        windowClient.focus()
      }
    })
  )
})

Step 3: Initialize Web Push Plugin

import { AnalyticsBrowser } from '@zixflow/analytics-browser'

// Initialize analytics with web push
const analytics = AnalyticsBrowser.load({
  apiKey: 'YOUR_API_KEY',
  plugins: [
    {
      name: 'WebPush',
      settings: {
        apiHost: 'api-events.zixflow.com/v1',  // Optional
        protocol: 'https',                      // Optional
        swUrl: '/sw.js',                 // Path to service worker
        autoSubscribe: false,            // Don't auto-request permission
        onNotificationClick: (url, action, data) => {
          // Handle notification clicks when app is open
          console.log('Notification clicked:', url, action, data)
          // Navigate to URL or perform action
          window.location.href = url
        }
      }
    }
  ]
})

Step 4: Request Push Permission

Request permission on user action (e.g., button click):

// HTML
<button id="subscribe-btn">Enable Notifications</button>

// JavaScript
document.getElementById('subscribe-btn').addEventListener('click', async () => {
  try {
    await analytics.plugins.WebPush.subscribeToPush()
    console.log('Push notifications enabled')
  } catch (err) {
    console.error('Failed to enable push notifications:', err)
  }
})

Step 5: Identify Users

Associate push subscription with user:

// After user logs in
analytics.identify('[email protected]', {
  email: '[email protected]',
  first_name: 'John'
})

// The push subscription is automatically linked to the user

Web Push API Reference

Subscribe to Push

// Request permission and subscribe
await analytics.plugins.WebPush.subscribeToPush()

Unsubscribe from Push

// Unsubscribe from push notifications
await analytics.plugins.WebPush.unsubscribeFromPush()

Check Subscription Status

// Check if user is subscribed
const subscription = await analytics.plugins.WebPush.getSubscription()
console.log('Subscription:', subscription)

Web Push Best Practices

  1. Request permission thoughtfully - Only ask after explaining value
  2. Use user gestures - Request permission on button clicks, not page load
  3. Provide unsubscribe option - Let users easily disable notifications
  4. Test on HTTPS - Web push requires secure context
  5. Handle errors gracefully - Permission can be denied or blocked
  6. Use meaningful content - Provide value in notifications
  7. Deep link properly - Navigate users to relevant content

Web Push Troubleshooting

Permission Denied

Problem: User denied notification permission

Solution:

  • Explain value before requesting permission
  • Guide users to browser settings to re-enable
  • Provide clear unsubscribe option

Service Worker Not Registered

Error: Failed to register service worker

Solution:

// Check service worker registration
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(reg => console.log('SW registered:', reg))
    .catch(err => console.error('SW registration failed:', err))
}

Push Not Received

Checklist:

  • [ ] HTTPS enabled (required for web push)
  • [ ] Service worker registered successfully
  • [ ] User granted notification permission
  • [ ] User identified with analytics.identify()
  • [ ] Push subscription active
  • [ ] Browser supports web push

Push Notification Tracking

Overview

Zixflow automatically tracks web push notification delivery and engagement metrics to help you measure campaign performance. The SDK tracks three key lifecycle events:

  1. Delivery Confirmed - When the notification arrives on the device
  2. Notification Opened - When the user taps the notification
  3. Action Button Clicked - When the user taps an action button

How Push Tracking Works

When Zixflow sends a push notification, it includes special tracking fields in the data payload:

| Field | Purpose | |-------|---------| | Zixflow-Delivery-ID | Unique ID for this delivery (links to campaign record) | | Zixflow-Delivery-Token | The push subscription endpoint |

Example push payload:

{
  "Zixflow-Delivery-ID": "626533406292836846",
  "Zixflow-Delivery-Token": "https://fcm.googleapis.com/...",
  "title": "Flash Sale! 70% OFF",
  "body": "Limited time only — grab your deal now!",
  "deeplink_url": "https://yourapp.com/sale",
  "image_url": "https://cdn.yourapp.com/banner.png",
  "action_buttons": "[{\"name\":\"Shop Now\",\"deeplink\":\"https://yourapp.com/sale\"},{\"name\":\"Remind Me\",\"deeplink\":\"\"}]"
}

Note: When using the Zixflow service worker implementation, tracking is handled automatically. This section covers custom implementations.


Tracking Events

1. Delivery Confirmed

When to track: The moment the push event fires in the service worker

Implementation (service-worker.js):

// service-worker.js
self.addEventListener('push', (event) => {
  const data = event.data?.json() ?? {};
  const deliveryId = data['Zixflow-Delivery-ID'] ?? '';
  const token = data['Zixflow-Delivery-Token'] ?? '';

  if (deliveryId && token) {
    // Track delivery using fetch API
    fetch('https://events.zixflow.in/v1/track', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Basic ${btoa(WRITE_KEY + ':')}`
      },
      body: JSON.stringify({
        event: 'Push Notification Delivered',
        userId: self.__ZIXFLOW_USER_ID__,
        properties: {
          'Zixflow-Delivery-ID': deliveryId,
          'Zixflow-Delivery-Token': token,
        },
      }),
    }).catch(() => {});
  }

  // Show notification
  event.waitUntil(
    self.registration.showNotification(data.title ?? 'Notification', {
      body: data.body ?? '',
      icon: data.image_url,
      data: data,
      actions: parseButtons(data.action_buttons),
    })
  );
});

function parseButtons(raw) {
  if (!raw) return [];
  try {
    const decoded = typeof raw === 'string' ? JSON.parse(raw) : raw;
    return decoded.slice(0, 2).map((btn, i) => ({
      action: `ACTION_${i}`,
      title: btn.name || `Action ${i + 1}`,
    }));
  } catch {
    return [];
  }
}

2. Notification Opened

When to track: When the user clicks the notification

Implementation (service-worker.js):

// service-worker.js
self.addEventListener('notificationclick', (event) => {
  const data = event.notification.data ?? {};
  const deliveryId = data['Zixflow-Delivery-ID'] ?? '';
  const token = data['Zixflow-Delivery-Token'] ?? '';

  event.notification.close();

  // Track opened
  if (deliveryId && token) {
    fetch('https://events.zixflow.in/v1/track', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Basic ${btoa(WRITE_KEY + ':')}`
      },
      body: JSON.stringify({
        event: 'Push Notification Opened',
        userId: self.__ZIXFLOW_USER_ID__,
        properties: {
          'Zixflow-Delivery-ID': deliveryId,
          'Zixflow-Delivery-Token': token,
        },
      }),
    }).catch(() => {});
  }

  // Handle action button click
  if (event.action) {
    const actionIndex = parseInt(event.action.replace(/\D/g, ''), 10);
    const buttons = parseButtons(data.action_buttons);
    const actionName = buttons[actionIndex]?.name ?? '';
    const deeplink = buttons[actionIndex]?.deeplink ?? '';

    // Track action click
    fetch('https://events.zixflow.in/v1/track', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Basic ${btoa(WRITE_KEY + ':')}`
      },
      body: JSON.stringify({
        event: 'Push Notification Action Clicked',
        userId: self.__ZIXFLOW_USER_ID__,
        properties: {
          'Zixflow-Delivery-ID': deliveryId,
          'Zixflow-Delivery-Token': token,
          'action_index': actionIndex,
          'action_name': actionName,
          'action_deeplink': deeplink,
          'source': 'web_push',
        },
      }),
    }).catch(() => {});

    if (deeplink) {
      event.waitUntil(clients.openWindow(deeplink));
      return;
    }
  }

  // Default: open deeplink or focus existing tab
  const deeplink = data['deeplink_url'] ?? '';
  event.waitUntil(
    deeplink ? clients.openWindow(deeplink) : clients.openWindow('/')
  );
});

3. Action Button Clicked

Action button clicks are tracked within the notificationclick event handler shown above when event.action is present.

Action button properties:

| Property | Type | Required | Description | |----------|------|----------|-------------| | Zixflow-Delivery-ID | string | ✅ | Links event to campaign delivery | | action_index | integer | ✅ | 0-based index of button tapped | | action_name | string | ✅ | Button label (e.g., "Shop Now") | | Zixflow-Delivery-Token | string | Recommended | Push subscription endpoint | | action_deeplink | string | Recommended | Button's URL | | source | string | Optional | Set to "web_push" |


Complete Service Worker Implementation

Here's a complete service worker with all tracking:

// public/zixflow-sw.js
const WRITE_KEY = 'YOUR_API_KEY';
const ZIXFLOW_API = 'https://events.zixflow.in/v1/track';

// Store user ID (set from main app)
self.__ZIXFLOW_USER_ID__ = null;

// Listen for messages from main app
self.addEventListener('message', (event) => {
  if (event.data?.type === 'SET_USER_ID') {
    self.__ZIXFLOW_USER_ID__ = event.data.userId;
  }
});

// Track delivery
self.addEventListener('push', (event) => {
  const data = event.data?.json() ?? {};
  const deliveryId = data['Zixflow-Delivery-ID'] ?? '';
  const token = data['Zixflow-Delivery-Token'] ?? '';

  // Track delivery
  if (deliveryId && token) {
    trackEvent('Push Notification Delivered', {
      'Zixflow-Delivery-ID': deliveryId,
      'Zixflow-Delivery-Token': token,
    });
  }

  // Parse action buttons
  const buttons = parseActionButtons(data.action_buttons);

  // Show notification
  event.waitUntil(
    self.registration.showNotification(data.title ?? 'Notification', {
      body: data.body ?? '',
      icon: data.image_url,
      badge: data.badge_url,
      data: data,
      actions: buttons,
    })
  );
});

// Track opens and action clicks
self.addEventListener('notificationclick', (event) => {
  const data = event.notification.data ?? {};
  const deliveryId = data['Zixflow-Delivery-ID'] ?? '';
  const token = data['Zixflow-Delivery-Token'] ?? '';

  event.notification.close();

  // Always track opened
  if (deliveryId && token) {
    trackEvent('Push Notification Opened', {
      'Zixflow-Delivery-ID': deliveryId,
      'Zixflow-Delivery-Token': token,
    });
  }

  // Handle action button click
  if (event.action) {
    const actionIndex = parseInt(event.action.replace(/\D/g, ''), 10);
    const rawButtons = parseActionButtonsRaw(data.action_buttons);
    const actionName = rawButtons[actionIndex]?.name ?? '';
    const actionDeeplink = rawButtons[actionIndex]?.deeplink ?? '';

    trackEvent('Push Notification Action Clicked', {
      'Zixflow-Delivery-ID': deliveryId,
      'Zixflow-Delivery-Token': token,
      'action_index': actionIndex,
      'action_name': actionName,
      'action_deeplink': actionDeeplink,
      'source': 'web_push',
    });

    if (actionDeeplink) {
      event.waitUntil(clients.openWindow(actionDeeplink));
      return;
    }
  }

  // Default navigation
  const deeplink = data['deeplink_url'] ?? '';
  event.waitUntil(
    deeplink ? clients.openWindow(deeplink) : clients.openWindow('/')
  );
});

// Helper: Track event
function trackEvent(eventName, properties) {
  fetch(ZIXFLOW_API, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Basic ${btoa(WRITE_KEY + ':')}`,
    },
    body: JSON.stringify({
      event: eventName,
      userId: self.__ZIXFLOW_USER_ID__,
      properties: properties,
    }),
  }).catch((err) => {
    console.error('Failed to track event:', err);
  });
}

// Helper: Parse action buttons for notification API
function parseActionButtons(raw) {
  const buttons = parseActionButtonsRaw(raw);
  return buttons.slice(0, 2).map((btn, i) => ({
    action: `ACTION_${i}`,
    title: btn.name || `Action ${i + 1}`,
  }));
}

// Helper: Parse raw action buttons data
function parseActionButtonsRaw(raw) {
  if (!raw) return [];
  try {
    return typeof raw === 'string' ? JSON.parse(raw) : raw;
  } catch {
    return [];
  }
}

Setting User ID in Service Worker

To associate tracking events with the correct user, pass the user ID from your main application to the service worker:

// main app (after analytics.identify())
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
  navigator.serviceWorker.controller.postMessage({
    type: 'SET_USER_ID',
    userId: '[email protected]',
  });
}

Action Buttons Format

The action_buttons field is a JSON-encoded string:

Raw payload value:

"[{\"name\":\"Shop Now\",\"deeplink\":\"https://yourapp.com/sale\"},{\"name\":\"Remind Me\",\"deeplink\":\"\"}]"

Parsed structure:

[
  { "name": "Shop Now",  "deeplink": "https://yourapp.com/sale" },
  { "name": "Remind Me", "deeplink": "" }
]

Rules:

  • Maximum 2 buttons (browser limitation)
  • deeplink may be empty
  • Button index is 0-based

Tracking Decision Flowchart

Notification received (push event)?
├── YES → Track "Push Notification Delivered"
│         Show notification
│
User clicked notification?
├── Clicked body
│   └── Track "Push Notification Opened"
│       Navigate to deeplink_url
│
└── Clicked action button
    ├── Track "Push Notification Opened"
    └── Track "Push Notification Action Clicked"
        Navigate to button's deeplink

Fallback for Non-Zixflow Push

If a push doesn't contain Zixflow-Delivery-ID, skip tracking or use custom event names:

// Don't use reserved push event names for non-Zixflow pushes
if (!data['Zixflow-Delivery-ID']) {
  trackEvent('External Push Received', {
    'title': data.title ?? '',
    'body': data.body ?? '',
    'source': 'external',
  });
}

Important: Event names Push Notification Delivered, Push Notification Opened, and Push Notification Action Clicked are reserved for Zixflow's delivery pipeline.


Testing Push Tracking

Enable Debug Logging

Add logging to your service worker:

function trackEvent(eventName, properties) {
  console.log('[Zixflow] Tracking:', eventName, properties);

  fetch(ZIXFLOW_API, {
    // ... existing implementation
  }).then(response => {
    console.log('[Zixflow] Track response:', response.status);
  }).catch((err) => {
    console.error('[Zixflow] Track failed:', err);
  });
}

View logs in browser DevTools → Application → Service Workers → Console

Check Campaign Analytics

  1. Log into Zixflow dashboard
  2. Navigate to MessagingPush Notifications
  3. View campaign metrics:
    • Sent - Total notifications sent
    • Delivered - Arrived in browser
    • Opened - User clicked notification
    • Clicked - User clicked action button

Advanced Configuration

Browser Configuration

import { AnalyticsBrowser } from '@zixflow/analytics-browser'

const analytics = AnalyticsBrowser.load({
  // Required
  apiKey: 'YOUR_API_KEY',

  // Delivery strategy
  deliveryStrategy: {
    strategy: 'batching',  // or 'standard'
    config: {
      size: 10,       // Batch size
      timeout: 5000   // Batch timeout (ms)
    }
  },

  // Disable page views
  disablePageView: false,

  // Disable client-side persistence
  disableClientPersistence: false,

  // Plugins
  plugins: [
    // Custom plugins
  ],

  // Middleware
  addSourceMiddleware: (middleware) => {
    // Add source middleware
  },

  // Initial page view
  initialPageview: true,

  // Obfuscate
  obfuscate: false
})

Node.js Configuration

import { Analytics } from '@zixflow/analytics-node'

const analytics = new Analytics({
  // Required
  writeKey: 'YOUR_API_KEY',

  // API path (optional)
  path: '/v1/track',

  // Batching
  maxEventsInBatch: 15,      // Max events per batch
  flushInterval: 10000,      // Flush interval (ms)

  // Retries
  maxRetries: 3,             // Max retry attempts
  httpRequestTimeout: 10000, // Request timeout (ms)

  // Disable events
  disable: false,

  // Enable/disable specific features
  enable: true
})

Delivery Strategies (Browser)

Standard Delivery

Send each event immediately:

const analytics = AnalyticsBrowser.load({
  apiKey: 'YOUR_API_KEY',
  deliveryStrategy: {
    strategy: 'standard'
  }
})

Batched Delivery

Batch events for efficiency:

const analytics = AnalyticsBrowser.load({
  apiKey: 'YOUR_API_KEY',
  deliveryStrategy: {
    strategy: 'batching',
    config: {
      size: 10,        // Send after 10 events
      timeout: 5000    // Or after 5 seconds
    }
  }
})

TypeScript Support

Both packages include TypeScript definitions.

Browser TypeScript Example

import { AnalyticsBrowser, Analytics } from '@zixflow/analytics-browser'

const analytics: Analytics = AnalyticsBrowser.load({
  apiKey: 'YOUR_API_KEY'
})

interface UserTraits {
  email: string
  first_name: string
  last_name: string
  plan: 'free' | 'premium' | 'enterprise'
}

interface PurchaseProperties {
  product_id: string
  price: number
  currency: string
}

// Type-safe identify
analytics.identify('[email protected]', {
  email: '[email protected]',
  first_name: 'John',
  last_name: 'Doe',
  plan: 'premium'
} as UserTraits)

// Type-safe track
analytics.track('purchase_completed', {
  product_id: '123',
  price: 29.99,
  currency: 'USD'
} as PurchaseProperties)

Node.js TypeScript Example

import { Analytics, AnalyticsSettings } from '@zixflow/analytics-node'

const settings: AnalyticsSettings = {
  writeKey: 'YOUR_API_KEY',
  maxEventsInBatch: 20,
  flushInterval: 10000
}

const analytics = new Analytics(settings)

interface UserTraits {
  email: string
  first_name: string
  last_name: string
}

analytics.identify({
  userId: '[email protected]',
  traits: {
    email: '[email protected]',
    first_name: 'John',
    last_name: 'Doe'
  } as UserTraits
})

// Flush before exit
process.on('SIGTERM', async () => {
  await analytics.closeAndFlush()
  process.exit(0)
})


Example Apps

The SDK repository includes complete example applications demonstrating various features.

Browser Examples

Location: /examples/browser-test.html

Demonstrates:

  • SDK initialization
  • User identification
  • Event tracking
  • Page tracking
  • Interactive testing UI

Running the example:

  1. Open examples/browser-test.html in a browser
  2. Enter your API key
  3. Test different tracking methods

Location: /examples/browser-test-dev.html

Pre-configured example for development:

  • Uses dev API endpoint
  • Includes test API key
  • Ready to test immediately

Node.js Examples

Location: /examples/node-test.js

Demonstrates:

  • SDK initialization
  • User identification
  • Event tracking
  • Page and screen tracking
  • Group tracking
  • Proper shutdown with flush

Running the example:

cd examples
npm install
node node-test.js

Test Server

Location: /examples/test-server.js

Local HTTP server to inspect requests:

  • Logs all incoming requests
  • Shows request headers and body
  • Useful for debugging

Running the server:

cd examples
node test-server.js

API Reference

Browser SDK

AnalyticsBrowser.load(settings)

Initialize the analytics instance.

Parameters:

  • settings (object)
    • apiKey (string, required): Your Zixflow API key
    • deliveryStrategy (object, optional): Delivery strategy configuration
    • disablePageView (boolean, optional): Disable automatic page views
    • plugins (array, optional): Custom plugins to load

Returns: Analytics instance


analytics.identify(userId, traits, options, callback)

Identify a user.

Parameters:

  • userId (string, required): User identifier
  • traits (object, optional): User attributes
  • options (object, optional): Additional options
  • callback (function, optional): Callback function

Returns: Promise<Context>


analytics.track(event, properties, options, callback)

Track an event.

Parameters:

  • event (string, required): Event name
  • properties (object, optional): Event properties
  • options (object, optional): Additional options
  • callback (function, optional): Callback function

Returns: Promise<Context>


analytics.page(category, name, properties, options, callback)

Track a page view.

Parameters:

  • category (string, optional): Page category
  • name (string, optional): Page name
  • properties (object, optional): Page properties
  • options (object, optional): Additional options
  • callback (function, optional): Callback function

Returns: Promise<Context>


analytics.screen(category, name, properties, options, callback)

Track a screen view.

Parameters: Same as page()

Returns: Promise<Context>


analytics.group(groupId, traits, options, callback)

Associate user with group.

Parameters:

  • groupId (string, required): Group identifier
  • traits (object, optional): Group attributes
  • options (object, optional): Additional options
  • callback (function, optional): Callback function

Returns: Promise<Context>


analytics.alias(userId, previousId, options, callback)

Merge user identities.

Parameters:

  • userId (string, required): New user ID
  • previousId (string, optional): Previous user ID
  • options (object, optional): Additional options
  • callback (function, optional): Callback function

Returns: Promise<Context>


analytics.reset()

Clear user identification.

Returns: Promise<Context>


analytics.ready(callback)

Execute callback when SDK is ready.

Parameters:

  • callback (function, required): Callback function

Returns: Promise<void>


Node.js SDK

new Analytics(settings)

Create analytics instance.

Parameters:

  • settings (object)
    • writeKey (string, required): Your Zixflow API key
    • host (string, optional): API host URL
    • path (string, optional): API path
    • maxEventsInBatch (number, optional): Max events per batch (default: 15)
    • flushInterval (number, optional): Flush interval in ms (default: 10000)
    • maxRetries (number, optional): Max retry attempts (default: 3)
    • httpRequestTimeout (number, optional): Request timeout in ms

Returns: Analytics instance


analytics.identify(params, callback)

Identify a user.

Parameters:

  • params (object)
    • userId (string, optional): User identifier
    • anonymousId (string, optional): Anonymous identifier
    • traits (object, optional): User attributes
    • context (object, optional): Context information
    • timestamp (Date, optional): Event timestamp
    • integrations (object, optional): Integration settings
    • messageId (string, optional): Message ID
  • callback (function, optional): Callback function

Returns: void


analytics.track(params, callback)

Track an event.

Parameters:

  • params (object)
    • userId (string, optional): User identifier
    • anonymousId (string, optional): Anonymous identifier
    • event (string, required): Event name
    • properties (object, optional): Event properties
    • context (object, optional): Context information
    • timestamp (Date, optional): Event timestamp
    • integrations (object, optional): Integration settings
    • messageId (string, optional): Message ID
  • callback (function, optional): Callback function

Returns: void


analytics.page(params, callback)

Track a page view.

Parameters:

  • params (object)
    • userId (string, optional): User identifier
    • anonymousId (string, optional): Anonymous identifier
    • category (string, optional): Page category
    • name (string, optional): Page name
    • properties (object, optional): Page properties
    • context (object, optional): Context information
    • timestamp (Date, optional): Event timestamp
  • callback (function, optional): Callback function

Returns: void


analytics.screen(params, callback)

Track a screen view.

Parameters: Same as page()

Returns: void


analytics.group(params, callback)

Associate user with group.

Parameters:

  • params (object)
    • userId (string, optional): User identifier
    • anonymousId (string, optional): Anonymous identifier
    • groupId (string, required): Group identifier
    • traits (object, optional): Group attributes
    • context (object, optional): Context information
    • timestamp (Date, optional): Event timestamp
  • callback (function, optional): Callback function

Returns: void


analytics.alias(params, callback)

Merge user identities.

Parameters:

  • params (object)
    • userId (string, required): New user ID
    • previousId (string, required): Previous user ID
    • context (object, optional): Context information
    • timestamp (Date, optional): Event timestamp
  • callback (function, optional): Callback function

Returns: void


analytics.closeAndFlush(options)

Flush all events and close.

Parameters:

  • options (object, optional)
    • timeout (number, optional): Maximum wait time in ms

Returns: Promise<void>