Site | NPM Module | GitHub Repo
Web Manager is a modern JavaScript utility library for building web applications with Firebase integration. It provides authentication, data binding, storage management, push notifications, error tracking, and more.
Optimized for use with webpack but works standalone too.
- Installation
- Requirements
- Quick Start
- Supported Environments
- Features
- Configuration
- API Reference
- HTML Data Attributes
- Direct Module Imports
- Browser Support
- Projects Using This Library
- Support
npm install web-manager- Node.js: >= 12
- Browser: Modern browsers (ES6+ support, transpiled to ES5 for older browsers)
Note: This library does not include TypeScript definitions.
import Manager from 'web-manager';
// Initialize with your configuration
await Manager.initialize({
environment: 'production',
buildTime: Date.now(),
brand: {
id: 'my-app',
name: 'My Application'
},
firebase: {
app: {
enabled: true,
config: {
apiKey: 'your-api-key',
authDomain: 'your-app.firebaseapp.com',
projectId: 'your-project-id',
storageBucket: 'your-app.appspot.com',
messagingSenderId: '123456789',
appId: '1:123456789:web:abcdef'
}
}
}
});
console.log('Web Manager initialized!');Web Manager is designed to work in multiple environments:
| Environment | Support | Notes |
|---|---|---|
| Web | Full | Primary target, works with webpack bundlers |
| Electron | Full | Works in renderer process |
| Chrome Extension | Full | Content scripts and popup pages |
| Firefox Extension | Full | Content scripts and popup pages |
| Safari Extension | Partial | Basic functionality supported |
- Firebase v12 Integration: Modern Firebase Auth, Firestore, and Cloud Messaging
- Data Binding System: Reactive DOM updates with
data-wm-bindattributes - Storage API: Enhanced localStorage/sessionStorage with path-based access and JSON serialization
- Utilities:
clipboardCopy(),escapeHTML(),getContext(),showNotification(),getPlatform(),getBrowser(),getRuntime(),isMobile(),getDeviceType() - DOM Utilities: Dynamic script loading with retry/timeout support
- Service Worker Management: Registration, messaging, and state tracking
- Push Notifications: Firebase Cloud Messaging with auto-subscription
- Error Tracking: Sentry integration with session replay
- App Check: Optional reCAPTCHA Enterprise protection
- Version Checking: Auto-reload when new version is deployed
- HTML Data Attributes: Automatic
data-platform,data-browser,data-runtime,data-deviceon<html>
await Manager.initialize({
// Environment: 'development' or 'production'
environment: 'production',
// Build timestamp for version checking
buildTime: Date.now(),
// Brand information
brand: {
id: 'my-app', // Used for custom protocol URLs
name: 'My Application',
description: 'App description',
type: 'Organization',
images: {
brandmark: 'https://example.com/logo.png',
wordmark: 'https://example.com/wordmark.png',
combomark: 'https://example.com/combomark.png'
},
contact: {
email: 'support@example.com',
phone: '+1-555-0123'
}
},
// Firebase configuration
firebase: {
app: {
enabled: true,
config: {
apiKey: 'your-api-key',
authDomain: 'your-app.firebaseapp.com',
projectId: 'your-project-id',
storageBucket: 'your-app.appspot.com',
messagingSenderId: '123456789',
appId: '1:123456789:web:abcdef'
}
},
appCheck: {
enabled: false,
config: {
siteKey: 'your-recaptcha-enterprise-site-key'
}
}
},
// Authentication settings
auth: {
enabled: true,
config: {
redirects: {
authenticated: '/account', // Redirect after login
unauthenticated: '/signup' // Redirect when not logged in
}
}
},
// Sentry error tracking
sentry: {
enabled: true,
config: {
dsn: 'https://your-sentry-dsn',
release: '1.0.0',
replaysSessionSampleRate: 0.01, // 1% of sessions
replaysOnErrorSampleRate: 0.01 // 1% of error sessions
}
},
// Push notifications
pushNotifications: {
enabled: true,
config: {
autoRequest: 60000, // Auto-request after 60s of first click
vapidKey: 'your-vapid-key' // Optional VAPID key
}
},
// Service worker
serviceWorker: {
enabled: true,
config: {
path: '/service-worker.js'
}
},
// Version checking (auto-reload on new version)
refreshNewVersion: {
enabled: true,
config: {
interval: 3600000 // Check every hour (1000 * 60 * 60)
}
},
// Valid hosts for auth redirects (security)
validRedirectHosts: ['example.com', 'app.example.com']
});- Timeout values can be specified as strings with math expressions:
'1000 * 60 * 60'(evaluated safely) - Deep merge: Your config is deep-merged with defaults, so you only need to specify what you want to change
- Firebase required: Most features require Firebase to be configured and enabled
The Manager is a singleton that provides access to all modules:
import Manager from 'web-manager';
// Module getters
Manager.storage(); // Storage API
Manager.auth(); // Firebase Auth wrapper
Manager.bindings(); // Data binding system
Manager.firestore(); // Firestore wrapper
Manager.notifications(); // Push notifications
Manager.serviceWorker(); // Service worker management
Manager.sentry(); // Error tracking
Manager.dom(); // DOM utilities
Manager.utilities(); // Utility functions
// Helper methods
Manager.isDevelopment(); // Check if in development mode
Manager.getFunctionsUrl(); // Get Firebase Functions URL
Manager.getFunctionsUrl('development'); // Force development URL
Manager.getApiUrl(); // Get API URL (derived from firebase authDomain)
Manager.isValidRedirectUrl('https://...'); // Validate redirect URL
// Firebase instances (after initialization)
Manager.firebaseApp; // Firebase App instance
Manager.firebaseAuth; // Firebase Auth instance
Manager.firebaseFirestore; // Firestore instance
Manager.firebaseMessaging; // FCM instance
// Configuration
Manager.config; // Access full configurationEnhanced localStorage and sessionStorage with path-based access:
const storage = Manager.storage();
// LocalStorage (persists across browser sessions)
storage.set('user.name', 'John');
storage.set('user.preferences', { theme: 'dark', lang: 'en' });
const name = storage.get('user.name'); // 'John'
const theme = storage.get('user.preferences.theme'); // 'dark'
const all = storage.get(); // Entire storage object
const fallback = storage.get('missing.path', 'default'); // 'default'
storage.remove('user.name');
storage.clear();
// SessionStorage (cleared when browser closes)
storage.session.set('temp.token', 'abc123');
storage.session.get('temp.token');
storage.session.remove('temp.token');
storage.session.clear();Features:
- Automatic JSON serialization/deserialization
- Nested path access using dot notation
- Fallback to in-memory storage if localStorage unavailable
- Uses lodash
get/setfor reliable path access
Firebase Authentication wrapper with automatic account data fetching:
const auth = Manager.auth();
// Listen for auth state changes
const unsubscribe = auth.listen({ account: true }, (result) => {
console.log('User:', result.user); // Firebase user or null
console.log('Account:', result.account); // Firestore account data or null
});
// Listen once (useful for initial state)
auth.listen({ once: true }, (result) => {
console.log('Initial state:', result);
});
// Check authentication status
if (auth.isAuthenticated()) {
const user = auth.getUser();
console.log('Logged in as:', user.email);
}
// Sign in
try {
const user = await auth.signInWithEmailAndPassword('user@example.com', 'password');
} catch (error) {
console.error('Sign in failed:', error.message);
}
// Sign in with custom token (from backend)
await auth.signInWithCustomToken('custom-jwt-token');
// Get ID token for API calls
const idToken = await auth.getIdToken();
const freshToken = await auth.getIdToken(true); // Force refresh
// Sign out
await auth.signOut();
// Stop listening
unsubscribe();getUser() returns enhanced user object:
{
uid: 'abc123',
email: 'user@example.com',
displayName: 'John Doe', // Falls back to email or 'User'
photoURL: 'https://...', // Falls back to ui-avatars.com
emailVerified: true
}HTML Auth Classes: Add these classes to elements for automatic auth functionality:
.auth-signout-btn- Sign out button (shows confirmation dialog)
On fresh page loads, Firebase Auth needs time to restore the user session from IndexedDB/localStorage. Methods like auth.isAuthenticated(), auth.getUser(), and auth.getIdToken() may return null/false if called before auth state is determined.
Problem:
// ❌ May fail on page load - auth state not yet determined
await Manager.dom().ready();
const token = await auth.getIdToken(); // Could throw if currentUser is nullSolution: Use auth.listen({ once: true }) to wait for auth state:
// âś… Wait for auth state to be determined first
auth.listen({ once: true }, async (result) => {
if (result.user) {
const token = await auth.getIdToken(); // Safe - user is authenticated
}
});When this matters:
- Pages making authenticated API calls immediately on load
- OAuth callback pages
- Deep links requiring authentication
When NOT needed:
- User-triggered actions (button clicks) - auth state is always determined by then
Reactive DOM updates with data-wm-bind attributes:
<!-- Display text content (default action) -->
<span data-wm-bind="auth.user.email"></span>
<span data-wm-bind="@text auth.user.displayName"></span><input data-wm-bind="@value settings.email" />
<textarea data-wm-bind="@value user.bio"></textarea><!-- Show when truthy -->
<div data-wm-bind="@show auth.user">Welcome back!</div>
<!-- Hide when truthy -->
<div data-wm-bind="@hide auth.user">Please log in</div>
<!-- Negation -->
<div data-wm-bind="@show !auth.user">Not logged in</div>
<!-- Comparisons -->
<div data-wm-bind="@show auth.account.plan === 'premium'">Premium content</div>
<div data-wm-bind="@hide settings.notifications === false">Notifications on</div><img data-wm-bind="@attr src auth.user.photoURL" />
<a data-wm-bind="@attr href settings.profileUrl">Profile</a>
<input data-wm-bind="@attr disabled auth.loading" /><!-- CSS custom properties -->
<div data-wm-bind="@style --rating-width ratings.percent"></div>
<!-- Inline styles -->
<div data-wm-bind="@style background-color theme.primary"></div>Combine actions with commas:
<img data-wm-bind="@show auth.user, @attr src auth.user.photoURL, @attr alt auth.user.displayName" />const bindings = Manager.bindings();
// Update context data
bindings.update({
settings: { theme: 'dark', email: 'user@example.com' },
custom: { value: 123 }
});
// Get current context
const context = bindings.getContext();
// Clear all bindings
bindings.clear();<!-- Shows shimmer animation until bound -->
<span data-wm-bind="auth.user.name" class="wm-binding-skeleton"></span>The skeleton automatically:
- Displays shimmer animation while loading
- Fades in smoothly when data arrives
- Adds
wm-boundclass when complete - Respects
prefers-reduced-motion
| Action | Syntax | Description |
|---|---|---|
@text |
@text path |
Set text content (default) |
@value |
@value path |
Set input/textarea value |
@show |
@show condition |
Show element if truthy |
@hide |
@hide condition |
Hide element if truthy |
@attr |
@attr name path |
Set attribute value |
@style |
@style prop path |
Set CSS property or variable |
Simplified Firestore wrapper with chainable queries:
const db = Manager.firestore();
// Document operations - two syntax options
await db.doc('users/user123').set({ name: 'John', age: 30 });
await db.doc('users', 'user123').update({ age: 31 });
const docSnap = await db.doc('users/user123').get();
if (docSnap.exists()) {
console.log('Data:', docSnap.data());
console.log('ID:', docSnap.id);
}
await db.doc('users/user123').delete();
// Collection queries
const snapshot = await db.collection('users').get();
console.log('Count:', snapshot.size);
console.log('Empty:', snapshot.empty);
snapshot.docs.forEach(doc => {
console.log(doc.id, doc.data());
});
// Query with filters (chainable)
const results = await db.collection('users')
.where('age', '>=', 18)
.where('active', '==', true)
.orderBy('createdAt', 'desc')
.limit(20)
.get();
// Pagination
const page2 = await db.collection('users')
.orderBy('name')
.startAt('M')
.endAt('N')
.get();Where Operators: <, <=, ==, !=, >=, >, array-contains, in, array-contains-any, not-in
Firebase Cloud Messaging integration:
const notifications = Manager.notifications();
// Check support
if (notifications.isSupported()) {
console.log('Push notifications available');
}
// Check subscription status
const isSubscribed = await notifications.isSubscribed();
// Subscribe
try {
const result = await notifications.subscribe({
vapidKey: 'your-vapid-key' // Optional
});
console.log('Token:', result.token);
} catch (error) {
if (error.message.includes('permission')) {
console.log('User denied permission');
}
}
// Unsubscribe
await notifications.unsubscribe();
// Get current token
const token = await notifications.getToken();
// Listen for foreground messages
const unsubscribe = await notifications.onMessage((payload) => {
console.log('Received:', payload);
});
// Sync subscription with auth state
await notifications.syncSubscription();Features:
- Stores subscription in localStorage and Firestore
- Tracks device context (platform, runtime, deviceType)
- Auto-requests after configurable delay post-click
- Syncs with user authentication state
Service worker registration and messaging:
const sw = Manager.serviceWorker();
// Check support
if (sw.isSupported()) {
console.log('Service workers available');
}
// Register (done automatically during init if enabled)
const registration = await sw.register({
path: '/service-worker.js',
scope: '/'
});
// Wait for ready state
await sw.ready();
// Get registration
const reg = sw.getRegistration();
// Post message with response
try {
const response = await sw.postMessage({
command: 'cache-clear',
payload: { pattern: '*.js' }
}, { timeout: 5000 });
console.log('Response:', response);
} catch (error) {
console.error('Timeout or error:', error);
}
// Listen for messages from service worker
const unsubscribe = sw.onMessage('notification-click', (data, event) => {
console.log('Clicked:', data);
});
// Get current state
const state = sw.getState(); // 'none', 'installing', 'waiting', 'active', 'unknown'Automatic error tracking with Sentry:
const sentry = Manager.sentry();
// Capture an exception
try {
throw new Error('Something went wrong');
} catch (error) {
sentry.captureException(error, {
tags: { feature: 'checkout' },
extra: { orderId: '12345' }
});
}Automatic Features:
- Environment and release tracking from config
- User context from auth state (uid, email)
- Session duration tracking
- Filters out Lighthouse and automated browsers (Selenium, Puppeteer)
- Blocks sending in development mode
- Dynamic import to reduce bundle size
import { loadScript, ready } from 'web-manager/modules/dom';
// Or: const { loadScript, ready } = Manager.dom();
// Wait for DOM ready
await ready();
// Load external script
await loadScript({
src: 'https://example.com/script.js',
async: true,
defer: false,
crossorigin: 'anonymous',
integrity: 'sha384-...',
timeout: 30000,
retries: 2,
parent: document.head,
attributes: { 'data-custom': 'value' }
});
// Simple string syntax
await loadScript('https://example.com/script.js');loadScript Options:
| Option | Type | Default | Description |
|---|---|---|---|
src |
string | required | Script URL |
async |
boolean | true |
Load asynchronously |
defer |
boolean | false |
Defer execution |
crossorigin |
boolean/string | false |
CORS setting |
integrity |
string | null |
SRI hash |
timeout |
number | 60000 |
Timeout in ms |
retries |
number | 0 |
Retry attempts |
parent |
Element | document.head |
Parent element |
attributes |
object | {} |
Custom attributes |
import {
clipboardCopy,
escapeHTML,
showNotification,
getPlatform,
getBrowser,
getRuntime,
isMobile,
getDeviceType,
getContext
} from 'web-manager/modules/utilities';
// Or: const utils = Manager.utilities();
// Copy to clipboard
await clipboardCopy('Text to copy');
await clipboardCopy(document.querySelector('#input')); // From element
// Escape HTML (XSS prevention)
const safe = escapeHTML('<script>alert("xss")</script>');
// '<script>alert("xss")</script>'
// Show notification (Bootstrap-styled)
showNotification('Success!', { type: 'success', timeout: 5000 });
showNotification('Error!', 'danger');
showNotification(new Error('Failed'), { timeout: 0 }); // No auto-dismiss
// Platform detection
getPlatform(); // 'windows', 'mac', 'linux', 'ios', 'android', 'chromeos', 'unknown'
// Browser detection
getBrowser(); // 'chrome', 'firefox', 'safari', 'edge', 'opera', 'brave', null
// Runtime detection
getRuntime(); // 'web', 'browser-extension'
// Device detection
isMobile(); // true/false
getDeviceType(); // 'mobile' (<768px), 'tablet' (768-1199px), 'desktop' (>=1200px)
// Full context
getContext();
// {
// client: { language, mobile, deviceType, platform, browser, vendor, runtime, userAgent, url },
// geolocation: { ip, country, region, city, latitude, longitude }
// }showNotification Options:
| Option | Type | Default | Description |
|---|---|---|---|
type |
string | 'info' |
'info', 'success', 'warning', 'danger' |
timeout |
number | 5000 |
Auto-dismiss after ms (0 = never) |
Web Manager automatically sets these attributes on the <html> element during initialization:
<html data-platform="mac" data-browser="chrome" data-runtime="web" data-device="desktop">| Attribute | Values | Description |
|---|---|---|
data-platform |
windows, mac, linux, ios, android, chromeos, unknown |
Operating system |
data-browser |
chrome, firefox, safari, edge, opera, brave |
Browser name |
data-runtime |
web, browser-extension |
Runtime environment |
data-device |
mobile, tablet, desktop |
Device type by screen width |
CSS Usage:
/* Platform-specific styles */
[data-platform="ios"] .download-btn { display: none; }
[data-platform="windows"] .app-icon { content: url('windows-icon.png'); }
/* Browser-specific styles */
[data-browser="safari"] .webkit-fix { -webkit-transform: translateZ(0); }
[data-browser="firefox"] .gecko-fix { overflow: hidden; }
/* Device-responsive styles */
[data-device="mobile"] .sidebar { display: none; }
[data-device="desktop"] .mobile-menu { display: none; }Import individual modules to reduce bundle size:
// Storage only
import Storage from 'web-manager/modules/storage';
const storage = new Storage();
// Utilities only
import { clipboardCopy, escapeHTML } from 'web-manager/modules/utilities';
// DOM utilities only
import { loadScript, ready } from 'web-manager/modules/dom';
// Full manager (default)
import Manager from 'web-manager';Available Modules:
web-manager/modules/storage- Storage classweb-manager/modules/utilities- Utility functionsweb-manager/modules/dom- DOM utilitiesweb-manager/modules/auth- Auth class (requires Manager)web-manager/modules/bindings- Bindings class (requires Manager)web-manager/modules/firestore- Firestore class (requires Manager)web-manager/modules/notifications- Notifications class (requires Manager)web-manager/modules/service-worker- ServiceWorker class (requires Manager)web-manager/modules/sentry- Sentry class (requires Manager)
Web Manager is transpiled to ES5 for broad browser support:
| Browser | Version | Support |
|---|---|---|
| Chrome | 60+ | Full |
| Firefox | 55+ | Full |
| Safari | 11+ | Full |
| Edge | 79+ | Full |
| IE | 11 | Not supported |
Notes:
- Service Workers require HTTPS (except localhost)
- Push Notifications require Service Worker support
- Some features use modern APIs with fallbacks
- Somiibo: A Social Media Bot with an open-source module library
- JekyllUp: A website devoted to sharing the best Jekyll themes
- Slapform: A backend processor for HTML forms on static sites
- SoundGrail Music App: A resource for producers, musicians, and DJs
- Hammock Report: An API for exploring and listing backyard products
Want your project listed? Open an issue!
If you're having issues or have questions:
- Open an issue on GitHub
- Include code samples and relevant files to help us help you faster