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

saltfish

v0.3.82

Published

An interactive video-guided tour system for web applications

Readme

Saltfish

Interactive video-guided tours for web applications. Create engaging onboarding experiences with synchronized video playback, interactive overlays, and smart triggering.

npm version License

Features

  • 🎥 Video-Guided Tours - Synchronized video content with interactive UI overlays
  • 🎯 Smart Triggers - Automatic playlist triggering based on URL, user behavior, and custom conditions
  • 📱 Responsive - Automatically adapts to different screen sizes and devices
  • 🎨 Shadow DOM - Complete style isolation prevents CSS conflicts
  • 📊 Analytics - Built-in tracking for user engagement and completion rates
  • 🔄 State Persistence - Resume tours where users left off
  • 🌐 Multi-language - User-specific language preferences
  • Lightweight - Minimal bundle size with efficient loading

Installation

CDN (Recommended)

<script src="https://storage.saltfish.ai/player/player.js"></script>

NPM

npm install saltfish

Quick Start

Basic Setup (CDN)

<!DOCTYPE html>
<html>
<head>
  <title>My App</title>
</head>
<body>
  <h1>Welcome to My App</h1>

  <!-- Load Saltfish -->
  <script src="https://storage.saltfish.ai/player/player.js"></script>
  <script>
    // 1. Initialize with your token
    saltfish.init({
      token: 'YOUR_TOKEN_HERE',
      enableAnalytics: true
    });

    // 2. Identify the user
    saltfish.identify('user-123', {
      email: '[email protected]',
      name: 'John Doe',
      language: 'en'
    });

    // 3. (Optional) Manually start a playlist
    saltfish.startPlaylist('playlist-id');
  </script>
</body>
</html>

NPM/ES Modules

import saltfish from 'saltfish';

// Initialize
saltfish.init({
  token: 'YOUR_TOKEN_HERE',
  enableAnalytics: true
});

// Identify user
saltfish.identify('user-123', {
  email: '[email protected]',
  language: 'en'
});

// Start playlist
saltfish.startPlaylist('playlist-id');

API Reference

saltfish.init(config)

Initialize the Saltfish player. Must be called before any other methods.

Parameters:

  • config (Object or String):
    • If string: Your API token
    • If object:
      • token (String, required): Your API token from Saltfish dashboard
      • enableAnalytics (Boolean, optional): Enable/disable analytics tracking (default: true)

Returns: Promise<void> (can be called without await)

Examples:

// Simple initialization with token only
saltfish.init('YOUR_TOKEN_HERE');

// With configuration object
saltfish.init({
  token: 'YOUR_TOKEN_HERE',
  enableAnalytics: false  // Disable analytics for development
});

// Using async/await
await saltfish.init('YOUR_TOKEN_HERE');

// Using events
saltfish.on('initialized', () => {
  console.log('Saltfish ready!');
});
saltfish.init('YOUR_TOKEN_HERE');

saltfish.identify(userId, attributes)

Identify the current user. This enables playlist triggers, progress tracking, and personalized experiences.

Parameters:

  • userId (String, required): Unique identifier for the user
  • attributes (Object, optional):
    • email (String): User's email address
    • name (String): User's display name
    • language (String): Language preference ('en', 'sv', 'es', 'fr', 'de', or 'auto' for browser detection)
    • Any custom attributes you want to track

Examples:

// Basic identification
saltfish.identify('user-123');

// With email and name
saltfish.identify('user-123', {
  email: '[email protected]',
  name: 'John Doe'
});

// With language preference
saltfish.identify('user-123', {
  email: '[email protected]',
  language: 'sv'  // Swedish
});

// Auto-detect language from browser
saltfish.identify('user-123', {
  language: 'auto'
});

// With custom attributes
saltfish.identify('user-123', {
  email: '[email protected]',
  plan: 'premium',
  signupDate: '2024-01-15'
});

saltfish.identifyAnonymous(attributes)

Identify anonymous users without backend communication. Uses localStorage for progress tracking and trigger evaluation.

Parameters:

  • attributes (Object, optional): Same as identify() method

Examples:

// Anonymous user with language preference
saltfish.identifyAnonymous({
  language: 'en'
});

// Anonymous user with no data
saltfish.identifyAnonymous();

// With custom tracking data
saltfish.identifyAnonymous({
  language: 'auto',
  theme: 'dark'
});

saltfish.startPlaylist(playlistId, options)

Manually start a specific playlist. If a playlist is already running, it will be reset and the new one started.

Parameters:

  • playlistId (String, required): The playlist ID from your Saltfish dashboard
  • options (Object, optional):
    • startNodeId (String): Start from a specific step instead of the beginning
    • position (String): Player position ('bottom-left' or 'bottom-right')

Returns: Promise<void> (can be called without await)

Examples:

// Basic usage
saltfish.startPlaylist('playlist-123');

// Start from a specific step
saltfish.startPlaylist('playlist-123', {
  startNodeId: 'step-5'
});

// With custom position
saltfish.startPlaylist('playlist-123', {
  position: 'bottom-right'
});

// Using async/await
await saltfish.startPlaylist('playlist-123');

// Using events
saltfish.on('playlistStarted', (data) => {
  console.log('Started:', data.playlist.id);
});
saltfish.startPlaylist('playlist-123');

Event Listeners

Listen to player events using on() and remove listeners with off().

saltfish.on(eventName, handler)

Register an event listener.

Available Events:

  • 'initialized' - Player initialized successfully
  • 'playlistStarted' - Playlist has started playing
  • 'playlistEnded' - Playlist completed
  • 'playlistDismissed' - User dismissed/closed the playlist
  • 'stepStarted' - New step/video started
  • 'stepEnded' - Step/video completed
  • 'error' - An error occurred

Examples:

// Listen for playlist completion
saltfish.on('playlistEnded', (data) => {
  console.log('Playlist completed:', data.playlist.id);
  console.log('Completion rate:', data.completionRate);
});

// Listen for step changes
saltfish.on('stepStarted', (data) => {
  console.log('Now on step:', data.step.id);
});

// Listen for errors
saltfish.on('error', (data) => {
  console.error('Player error:', data.message);
});

// Listen for initialization
saltfish.on('initialized', () => {
  console.log('Player ready!');
});

saltfish.off(eventName, handler)

Remove an event listener.

Returns: boolean - true if listener was removed, false if not found

Example:

function onPlaylistEnd(data) {
  console.log('Playlist ended:', data.playlist.id);
}

// Add listener
saltfish.on('playlistEnded', onPlaylistEnd);

// Remove listener
saltfish.off('playlistEnded', onPlaylistEnd);

Other Methods

saltfish.getSessionId()

Get the current session ID.

Returns: String

const sessionId = saltfish.getSessionId();
console.log('Session ID:', sessionId);

saltfish.getRunId()

Get the current run ID (unique per playlist execution).

Returns: String | null

const runId = saltfish.getRunId();
console.log('Run ID:', runId);

saltfish.resetPlaylist()

Reset the current playlist to its initial state.

saltfish.resetPlaylist();

saltfish.destroy()

Destroy the player instance and clean up all resources.

saltfish.destroy();

saltfish.version()

Get the current Saltfish player version.

Returns: String

const version = saltfish.version();
console.log('Saltfish version:', version);

Playlist Triggers

Playlists can be automatically triggered based on conditions you configure in the Saltfish CMS. No code changes needed!

Trigger Types

URL-Based Triggers

Automatically show playlists when users visit specific URLs:

// Just initialize and identify - triggers happen automatically
saltfish.init('YOUR_TOKEN');
saltfish.identify('user-123');

// Playlist will auto-trigger when user navigates to configured URLs
// e.g., "/dashboard", "/pricing", "/features/*"

CMS Configuration Examples:

  • Exact match: /dashboard - Only triggers on exactly /dashboard
  • Contains: /dashboard with "contains" mode - Triggers on /dashboard, /dashboard/settings, etc.
  • Wildcard: /products/* - Triggers on any product page
  • Regex: /playlist-[0-9]+ - Triggers on /playlist-1, /playlist-42, etc.

Element Click Triggers

Trigger playlists when users click specific elements:

<button id="help-button">Help</button>
<button class="support-btn">Support</button>
<button data-action="guide">Guide</button>

<script>
  saltfish.init('YOUR_TOKEN');
  saltfish.identify('user-123');

  // Configure in CMS with CSS selector:
  // #help-button, .support-btn, [data-action="guide"]
</script>

Conditional Triggers

Configure complex trigger logic in the CMS:

  • Once per user: Show playlist only once, never again
  • Seen/Not seen: Trigger based on whether user has seen other playlists
  • A/B Testing: Show different playlists to different user segments
  • Combine conditions: URL + click + seen/not seen with AND/OR logic

Manual Override

You can always manually start a playlist even if triggers are configured:

// Manually start any playlist
saltfish.startPlaylist('onboarding-tour');

Common Use Cases

Onboarding Flow

// Initialize on page load
saltfish.init({
  token: 'YOUR_TOKEN',
  enableAnalytics: true
});

// Identify user after signup
saltfish.identify(userId, {
  email: userEmail,
  language: 'auto',
  plan: 'free'
});

// Backend triggers will automatically show onboarding
// when configured in CMS for new users

Feature Announcement

saltfish.init('YOUR_TOKEN');
saltfish.identify(userId);

// Configure in CMS to trigger on /new-feature page
// Playlist auto-plays when user visits

Help Button

<button id="help-btn">Help</button>

<script>
  saltfish.init('YOUR_TOKEN');
  saltfish.identify(userId);

  // Configure in CMS with element selector: #help-btn
  // Playlist auto-triggers when user clicks
</script>

Context-Specific Tours

// Different tours for different pages
saltfish.init('YOUR_TOKEN');
saltfish.identify(userId);

// Configure in CMS:
// - "/dashboard" → dashboard-tour
// - "/settings" → settings-tour
// - "/billing" → billing-tour

// Tours trigger automatically based on URL

Framework Integration

React

import { useEffect } from 'react';
import saltfish from 'saltfish';

function App() {
  useEffect(() => {
    // Initialize Saltfish
    saltfish.init({
      token: process.env.REACT_APP_SALTFISH_TOKEN,
      enableAnalytics: true
    });

    // Identify user when auth state changes
    if (user) {
      saltfish.identify(user.id, {
        email: user.email,
        name: user.name,
        language: 'auto'
      });
    }

    // Cleanup on unmount
    return () => {
      saltfish.destroy();
    };
  }, [user]);

  return <div>Your App</div>;
}

Vue

<template>
  <div>Your App</div>
</template>

<script>
import saltfish from 'saltfish';

export default {
  mounted() {
    saltfish.init({
      token: process.env.VUE_APP_SALTFISH_TOKEN,
      enableAnalytics: true
    });

    if (this.user) {
      saltfish.identify(this.user.id, {
        email: this.user.email,
        language: 'auto'
      });
    }
  },
  beforeUnmount() {
    saltfish.destroy();
  }
};
</script>

Angular

import { Component, OnInit, OnDestroy } from '@angular/core';
import saltfish from 'saltfish';

@Component({
  selector: 'app-root',
  template: '<div>Your App</div>'
})
export class AppComponent implements OnInit, OnDestroy {
  ngOnInit() {
    saltfish.init({
      token: environment.saltfishToken,
      enableAnalytics: true
    });

    if (this.authService.user) {
      saltfish.identify(this.authService.user.id, {
        email: this.authService.user.email,
        language: 'auto'
      });
    }
  }

  ngOnDestroy() {
    saltfish.destroy();
  }
}

Next.js

// app/layout.js or pages/_app.js
'use client';

import { useEffect } from 'react';
import saltfish from 'saltfish';

export default function RootLayout({ children }) {
  useEffect(() => {
    if (typeof window !== 'undefined') {
      saltfish.init({
        token: process.env.NEXT_PUBLIC_SALTFISH_TOKEN,
        enableAnalytics: true
      });

      // Identify user if logged in
      // saltfish.identify(userId, { email, language: 'auto' });
    }

    return () => {
      if (typeof window !== 'undefined') {
        saltfish.destroy();
      }
    };
  }, []);

  return <html><body>{children}</body></html>;
}

TypeScript Support

Full TypeScript definitions included:

import saltfish, { SaltfishAPI } from 'saltfish';

// All methods are fully typed
saltfish.init({
  token: 'YOUR_TOKEN',
  enableAnalytics: true
});

saltfish.identify('user-123', {
  email: '[email protected]',
  language: 'en'
});

saltfish.startPlaylist('playlist-id', {
  startNodeId: 'step-2'
});

// Event handlers are typed
saltfish.on('playlistEnded', (data) => {
  console.log(data.playlist.id); // Fully typed
  console.log(data.completionRate);
});

Browser Support

  • Chrome/Edge 90+
  • Firefox 88+
  • Safari 14+
  • Opera 76+

Getting Started

  1. Sign up at saltfish.ai
  2. Get your token from the dashboard
  3. Create playlists using the CMS
  4. Configure triggers (optional) for automatic playback
  5. Install Saltfish in your app

Support

License

PROPRIETARY - See LICENSE file for details.


Made with ❤️ by Saltfish AB