{"id":20719897,"url":"https://github.com/itw-creative-works/web-manager","last_synced_at":"2026-05-11T08:01:09.587Z","repository":{"id":34982042,"uuid":"176912118","full_name":"itw-creative-works/web-manager","owner":"itw-creative-works","description":"Module for streamlining web development with the signature Manger object","archived":false,"fork":false,"pushed_at":"2026-04-11T00:09:37.000Z","size":738,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-04-11T02:18:24.920Z","etag":null,"topics":["css","frontend","html","js","web","webpack","website"],"latest_commit_sha":null,"homepage":"https://itwcreativeworks.com","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc-by-4.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/itw-creative-works.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2019-03-21T09:26:16.000Z","updated_at":"2026-04-11T00:09:30.000Z","dependencies_parsed_at":"2025-11-17T05:05:37.831Z","dependency_job_id":null,"html_url":"https://github.com/itw-creative-works/web-manager","commit_stats":{"total_commits":164,"total_committers":2,"mean_commits":82.0,"dds":0.4207317073170732,"last_synced_commit":"f889ad611b27aa474440dc9ca199eac3bc7015df"},"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"purl":"pkg:github/itw-creative-works/web-manager","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itw-creative-works%2Fweb-manager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itw-creative-works%2Fweb-manager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itw-creative-works%2Fweb-manager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itw-creative-works%2Fweb-manager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/itw-creative-works","download_url":"https://codeload.github.com/itw-creative-works/web-manager/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itw-creative-works%2Fweb-manager/sbom","scorecard":{"id":356934,"data":{"date":"2025-08-11","repo":{"name":"github.com/itw-creative-works/web-manager","commit":"b1c26eaf329e57e2899f26616672df28970ef842"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.1,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"1 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Creative Commons Attribution 4.0 International: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":3,"reason":"7 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-7v5v-9h63-cj86","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3wf4-68gx-mph8","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-qrpm-p2h7-hrv2","Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-18T09:50:08.218Z","repository_id":34982042,"created_at":"2025-08-18T09:50:08.218Z","updated_at":"2025-08-18T09:50:08.218Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31667034,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-10T17:19:37.612Z","status":"online","status_checked_at":"2026-04-11T02:00:05.776Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css","frontend","html","js","web","webpack","website"],"created_at":"2024-11-17T03:18:44.379Z","updated_at":"2026-04-11T02:57:51.618Z","avatar_url":"https://github.com/itw-creative-works.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://itwcreativeworks.com\"\u003e\n    \u003cimg src=\"https://cdn.itwcreativeworks.com/assets/itw-creative-works/images/logo/itw-creative-works-brandmark-black-x.svg\" width=\"100px\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/github/package-json/v/itw-creative-works/web-manager.svg\"\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://img.shields.io/bundlephobia/min/web-manager.svg\"\u003e\n  \u003cimg src=\"https://img.shields.io/codeclimate/maintainability-percentage/itw-creative-works/web-manager.svg\"\u003e\n  \u003cimg src=\"https://img.shields.io/npm/dm/web-manager.svg\"\u003e\n  \u003cimg src=\"https://img.shields.io/node/v/web-manager.svg\"\u003e\n  \u003cimg src=\"https://img.shields.io/website/https/itwcreativeworks.com.svg\"\u003e\n  \u003cimg src=\"https://img.shields.io/github/license/itw-creative-works/web-manager.svg\"\u003e\n  \u003cimg src=\"https://img.shields.io/github/contributors/itw-creative-works/web-manager.svg\"\u003e\n  \u003cimg src=\"https://img.shields.io/github/last-commit/itw-creative-works/web-manager.svg\"\u003e\n  \u003cbr\u003e\n  \u003cbr\u003e\n  \u003ca href=\"https://itwcreativeworks.com\"\u003eSite\u003c/a\u003e | \u003ca href=\"https://www.npmjs.com/package/web-manager\"\u003eNPM Module\u003c/a\u003e | \u003ca href=\"https://github.com/itw-creative-works/web-manager\"\u003eGitHub Repo\u003c/a\u003e\n  \u003cbr\u003e\n  \u003cbr\u003e\n  \u003cstrong\u003eWeb Manager\u003c/strong\u003e 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.\n  \u003cbr\u003e\n  \u003cbr\u003e\n  Optimized for use with \u003ca href=\"https://www.npmjs.com/package/webpack\"\u003ewebpack\u003c/a\u003e but works standalone too.\n\u003c/p\u003e\n\n## Table of Contents\n- [Installation](#-installation)\n- [Requirements](#-requirements)\n- [Quick Start](#-quick-start)\n- [Supported Environments](#-supported-environments)\n- [Features](#-features)\n- [Configuration](#-configuration)\n- [API Reference](#-api-reference)\n  - [Manager Instance](#manager-instance)\n  - [Storage API](#storage-api)\n  - [Authentication](#authentication)\n  - [Data Binding System](#data-binding-system)\n  - [Firestore](#firestore)\n  - [Push Notifications](#push-notifications)\n  - [Service Worker](#service-worker)\n  - [Sentry Error Tracking](#sentry-error-tracking)\n  - [DOM Utilities](#dom-utilities)\n  - [Utility Functions](#utility-functions)\n- [HTML Data Attributes](#-html-data-attributes)\n- [Direct Module Imports](#-direct-module-imports)\n- [Browser Support](#-browser-support)\n- [Projects Using This Library](#-projects-using-this-library)\n- [Support](#-support)\n\n## Installation\n```shell\nnpm install web-manager\n```\n\n## Requirements\n- **Node.js**: \u003e= 12\n- **Browser**: Modern browsers (ES6+ support, transpiled to ES5 for older browsers)\n\n**Note**: This library does not include TypeScript definitions.\n\n## Quick Start\n\n```javascript\nimport Manager from 'web-manager';\n\n// Initialize with your configuration\nawait Manager.initialize({\n  environment: 'production',\n  buildTime: Date.now(),\n  brand: {\n    id: 'my-app',\n    name: 'My Application'\n  },\n  firebase: {\n    app: {\n      enabled: true,\n      config: {\n        apiKey: 'your-api-key',\n        authDomain: 'your-app.firebaseapp.com',\n        projectId: 'your-project-id',\n        storageBucket: 'your-app.appspot.com',\n        messagingSenderId: '123456789',\n        appId: '1:123456789:web:abcdef'\n      }\n    }\n  }\n});\n\nconsole.log('Web Manager initialized!');\n```\n\n## Supported Environments\n\nWeb Manager is designed to work in multiple environments:\n\n| Environment | Support | Notes |\n|-------------|---------|-------|\n| **Web** | Full | Primary target, works with webpack bundlers |\n| **Electron** | Full | Works in renderer process |\n| **Chrome Extension** | Full | Content scripts and popup pages |\n| **Firefox Extension** | Full | Content scripts and popup pages |\n| **Safari Extension** | Partial | Basic functionality supported |\n\n## Features\n- **Firebase v12 Integration**: Modern Firebase Auth, Firestore, and Cloud Messaging\n- **Data Binding System**: Reactive DOM updates with `data-wm-bind` attributes\n- **Storage API**: Enhanced localStorage/sessionStorage with path-based access and JSON serialization\n- **Utilities**: `clipboardCopy()`, `escapeHTML()`, `getContext()`, `showNotification()`, `getPlatform()`, `getBrowser()`, `getRuntime()`, `isMobile()`, `getDevice()`\n- **DOM Utilities**: Dynamic script loading with retry/timeout support\n- **Service Worker Management**: Registration, messaging, and state tracking\n- **Push Notifications**: Firebase Cloud Messaging with auto-subscription\n- **Error Tracking**: Sentry integration with session replay\n- **App Check**: Optional reCAPTCHA Enterprise protection\n- **Version Checking**: Auto-reload when new version is deployed\n- **HTML Data Attributes**: Automatic `data-platform`, `data-browser`, `data-runtime`, `data-device` on `\u003chtml\u003e`\n\n## Configuration\n\n### Full Configuration Reference\n\n```javascript\nawait Manager.initialize({\n  // Environment: 'development' or 'production'\n  environment: 'production',\n\n  // Build timestamp for version checking\n  buildTime: Date.now(),\n\n  // Brand information\n  brand: {\n    id: 'my-app',                    // Used for custom protocol URLs\n    name: 'My Application',\n    description: 'App description',\n    type: 'Organization',\n    images: {\n      brandmark: 'https://example.com/logo.png',\n      wordmark: 'https://example.com/wordmark.png',\n      combomark: 'https://example.com/combomark.png'\n    },\n    contact: {\n      email: 'support@example.com',\n      phone: '+1-555-0123'\n    }\n  },\n\n  // Firebase configuration\n  firebase: {\n    app: {\n      enabled: true,\n      config: {\n        apiKey: 'your-api-key',\n        authDomain: 'your-app.firebaseapp.com',\n        projectId: 'your-project-id',\n        storageBucket: 'your-app.appspot.com',\n        messagingSenderId: '123456789',\n        appId: '1:123456789:web:abcdef'\n      }\n    },\n    appCheck: {\n      enabled: false,\n      config: {\n        siteKey: 'your-recaptcha-enterprise-site-key'\n      }\n    }\n  },\n\n  // Authentication settings\n  auth: {\n    enabled: true,\n    config: {\n      redirects: {\n        authenticated: '/account',     // Redirect after login\n        unauthenticated: '/signup'     // Redirect when not logged in\n      }\n    }\n  },\n\n  // Sentry error tracking\n  sentry: {\n    enabled: true,\n    config: {\n      dsn: 'https://your-sentry-dsn',\n      release: '1.0.0',\n      replaysSessionSampleRate: 0.01,  // 1% of sessions\n      replaysOnErrorSampleRate: 0.01   // 1% of error sessions\n    }\n  },\n\n  // Push notifications\n  pushNotifications: {\n    enabled: true,\n    config: {\n      autoRequest: 60000,              // Auto-request after 60s of first click\n      vapidKey: 'your-vapid-key'       // Optional VAPID key\n    }\n  },\n\n  // Service worker\n  serviceWorker: {\n    enabled: true,\n    config: {\n      path: '/service-worker.js'\n    }\n  },\n\n  // Version checking (auto-reload on new version)\n  refreshNewVersion: {\n    enabled: true,\n    config: {\n      interval: 3600000                // Check every hour (1000 * 60 * 60)\n    }\n  },\n\n  // Valid hosts for auth redirects (security)\n  validRedirectHosts: ['example.com', 'app.example.com']\n});\n```\n\n### Configuration Notes\n\n- **Timeout values** can be specified as strings with math expressions: `'1000 * 60 * 60'` (evaluated safely)\n- **Deep merge**: Your config is deep-merged with defaults, so you only need to specify what you want to change\n- **Firebase required**: Most features require Firebase to be configured and enabled\n\n## API Reference\n\n### Manager Instance\n\nThe Manager is a singleton that provides access to all modules:\n\n```javascript\nimport Manager from 'web-manager';\n\n// Module getters\nManager.storage();        // Storage API\nManager.auth();           // Firebase Auth wrapper\nManager.bindings();       // Data binding system\nManager.firestore();      // Firestore wrapper\nManager.notifications();  // Push notifications\nManager.serviceWorker();  // Service worker management\nManager.sentry();         // Error tracking\nManager.dom();            // DOM utilities\nManager.utilities();      // Utility functions\n\n// Helper methods\nManager.isDevelopment();                        // Check if in development mode\nManager.getFunctionsUrl();                      // Get Firebase Functions URL\nManager.getFunctionsUrl('development');         // Force development URL\nManager.getApiUrl();                            // Get API URL (derived from firebase authDomain)\nManager.isValidRedirectUrl('https://...');      // Validate redirect URL\n\n// Firebase instances (after initialization)\nManager.firebaseApp;       // Firebase App instance\nManager.firebaseAuth;      // Firebase Auth instance\nManager.firebaseFirestore; // Firestore instance\nManager.firebaseMessaging; // FCM instance\n\n// Configuration\nManager.config;            // Access full configuration\n```\n\n### Storage API\n\nEnhanced localStorage and sessionStorage with path-based access:\n\n```javascript\nconst storage = Manager.storage();\n\n// LocalStorage (persists across browser sessions)\nstorage.set('user.name', 'John');\nstorage.set('user.preferences', { theme: 'dark', lang: 'en' });\n\nconst name = storage.get('user.name');                    // 'John'\nconst theme = storage.get('user.preferences.theme');      // 'dark'\nconst all = storage.get();                                // Entire storage object\nconst fallback = storage.get('missing.path', 'default');  // 'default'\n\nstorage.remove('user.name');\nstorage.clear();\n\n// SessionStorage (cleared when browser closes)\nstorage.session.set('temp.token', 'abc123');\nstorage.session.get('temp.token');\nstorage.session.remove('temp.token');\nstorage.session.clear();\n```\n\n**Features**:\n- Automatic JSON serialization/deserialization\n- Nested path access using dot notation\n- Fallback to in-memory storage if localStorage unavailable\n- Uses lodash `get`/`set` for reliable path access\n\n### Authentication\n\nFirebase Authentication wrapper with a promise-based auth settler:\n\n```javascript\nconst auth = Manager.auth();\n\n// Listen once — waits for auth to settle, fires exactly once\nauth.listen({ once: true }, (state) =\u003e {\n  if (state.user) {\n    console.log('Logged in:', state.user.email);\n    console.log('Account:', state.account);\n  } else {\n    console.log('Not logged in');\n  }\n});\n\n// Persistent listener — fires on initial settle + every future auth change\nconst unsubscribe = auth.listen({}, (state) =\u003e {\n  console.log('Auth changed:', state.user?.email || 'signed out');\n});\n\n// Check authentication status\nif (auth.isAuthenticated()) {\n  const user = auth.getUser();\n  console.log('Logged in as:', user.email);\n}\n\n// Sign in\nawait auth.signInWithEmailAndPassword('user@example.com', 'password');\n\n// Sign in with custom token (from backend)\nawait auth.signInWithCustomToken('custom-jwt-token');\n\n// Get ID token for API calls\nconst idToken = await auth.getIdToken();\nconst freshToken = await auth.getIdToken(true); // Force refresh\n\n// Sign out\nawait auth.signOut();\n\n// Stop persistent listener\nunsubscribe();\n```\n\n**Auth Settler Design**:\n\nOn page load, Firebase Auth takes time to restore the user session. The auth settler (`Manager._authReady`) is a promise that resolves once Firebase determines the auth state (user or null). All `listen()` callbacks wait for this settler before firing — consumers never see an intermediate/unknown state.\n\n- `{ once: true }` — Waits for the settler promise, calls the callback once, done. No cleanup needed.\n- `{}` (persistent) — Gets the initial settled state via `_handleAuthStateChange`, then fires again on every future sign-in/sign-out.\n\n**getUser() returns enhanced user object**:\n```javascript\n{\n  uid: 'abc123',\n  email: 'user@example.com',\n  displayName: 'John Doe',        // Falls back to email or 'User'\n  photoURL: 'https://...',        // Falls back to ui-avatars.com\n  emailVerified: true\n}\n```\n\n**HTML Auth Classes**:\n- `.auth-signout-btn` - Sign out button (shows confirmation dialog)\n\n**Resolve Subscription State**:\n\nDerives calculated subscription fields from raw account data. Returns only fields that require logic — raw data is on `account.subscription` directly.\n\n```javascript\nconst resolved = auth.resolveSubscription(account);\n// Or without an argument (falls back to stored auth state):\nconst resolved = auth.resolveSubscription();\n```\n\nWhen called without an argument, reads the account from `localStorage` (the last auth state saved by `listen()`). Pass an explicit account when you have one to avoid stale data.\n\nReturns:\n```javascript\n{\n  plan: 'basic',       // Effective plan ID right now ('basic' if cancelled/suspended)\n  active: true,        // Has active access (active, trialing, or cancelling)\n  trialing: false,     // In an active trial (status 'active' + unexpired trial)\n  cancelling: false,   // Cancellation pending (status 'active' + cancellation.pending)\n}\n```\n\nUsage:\n```javascript\nauth.listen({ once: true }, (state) =\u003e {\n  const resolved = auth.resolveSubscription(state.account);\n\n  if (!resolved.active) {\n    // User is on free plan or subscription ended\n  }\n\n  if (resolved.trialing) {\n    // Show trial banner\n  }\n\n  if (resolved.cancelling) {\n    // Show \"your plan will cancel at end of period\" notice\n  }\n\n  // Use resolved.plan for effective plan ID\n  const product = products.find(p =\u003e p.id === resolved.plan);\n});\n```\n\n**⚠️ Auth State Timing**:\n\nMethods like `auth.isAuthenticated()`, `auth.getUser()`, and `auth.getIdToken()` read the current state directly — they may return `null` before auth settles.\n\n```javascript\n// ❌ May fail on page load - auth state not yet determined\nconst token = await auth.getIdToken();\n\n// ✅ Wait for auth to settle first\nauth.listen({ once: true }, async (state) =\u003e {\n  if (state.user) {\n    const token = await auth.getIdToken(); // Safe\n  }\n});\n```\n\n### Data Binding System\n\nReactive DOM updates with `data-wm-bind` attributes:\n\n#### Basic Text Binding\n```html\n\u003c!-- Display text content (default action) --\u003e\n\u003cspan data-wm-bind=\"auth.user.email\"\u003e\u003c/span\u003e\n\u003cspan data-wm-bind=\"@text auth.user.displayName\"\u003e\u003c/span\u003e\n```\n\n#### Input/Textarea Value Binding\n```html\n\u003cinput data-wm-bind=\"@value settings.email\" /\u003e\n\u003ctextarea data-wm-bind=\"@value user.bio\"\u003e\u003c/textarea\u003e\n```\n\n#### Conditional Visibility\n```html\n\u003c!-- Show when truthy --\u003e\n\u003cdiv data-wm-bind=\"@show auth.user\"\u003eWelcome back!\u003c/div\u003e\n\n\u003c!-- Hide when truthy --\u003e\n\u003cdiv data-wm-bind=\"@hide auth.user\"\u003ePlease log in\u003c/div\u003e\n\n\u003c!-- Negation --\u003e\n\u003cdiv data-wm-bind=\"@show !auth.user\"\u003eNot logged in\u003c/div\u003e\n\n\u003c!-- Comparisons --\u003e\n\u003cdiv data-wm-bind=\"@show auth.account.plan === 'premium'\"\u003ePremium content\u003c/div\u003e\n\u003cdiv data-wm-bind=\"@hide settings.notifications === false\"\u003eNotifications on\u003c/div\u003e\n```\n\n#### Attribute Binding\n```html\n\u003cimg data-wm-bind=\"@attr src auth.user.photoURL\" /\u003e\n\u003ca data-wm-bind=\"@attr href settings.profileUrl\"\u003eProfile\u003c/a\u003e\n\u003cinput data-wm-bind=\"@attr disabled auth.loading\" /\u003e\n```\n\n#### Style Binding\n```html\n\u003c!-- CSS custom properties --\u003e\n\u003cdiv data-wm-bind=\"@style --rating-width ratings.percent\"\u003e\u003c/div\u003e\n\n\u003c!-- Inline styles --\u003e\n\u003cdiv data-wm-bind=\"@style background-color theme.primary\"\u003e\u003c/div\u003e\n```\n\n#### Multiple Actions\nCombine actions with commas:\n```html\n\u003cimg data-wm-bind=\"@show auth.user, @attr src auth.user.photoURL, @attr alt auth.user.displayName\" /\u003e\n```\n\n#### JavaScript API\n```javascript\nconst bindings = Manager.bindings();\n\n// Update context data\nbindings.update({\n  settings: { theme: 'dark', email: 'user@example.com' },\n  custom: { value: 123 }\n});\n\n// Get current context\nconst context = bindings.getContext();\n\n// Clear all bindings\nbindings.clear();\n```\n\n#### Skeleton Loaders\n```html\n\u003c!-- Shows shimmer animation until bound --\u003e\n\u003cspan data-wm-bind=\"auth.user.name\" class=\"wm-binding-skeleton\"\u003e\u003c/span\u003e\n```\n\nThe skeleton automatically:\n- Displays shimmer animation while loading\n- Fades in smoothly when data arrives\n- Adds `wm-bound` class when complete\n- Respects `prefers-reduced-motion`\n\n#### Supported Actions\n\n| Action | Syntax | Description |\n|--------|--------|-------------|\n| `@text` | `@text path` | Set text content (default) |\n| `@value` | `@value path` | Set input/textarea value |\n| `@show` | `@show condition` | Show element if truthy |\n| `@hide` | `@hide condition` | Hide element if truthy |\n| `@attr` | `@attr name path` | Set attribute value |\n| `@style` | `@style prop path` | Set CSS property or variable |\n\n### Firestore\n\nSimplified Firestore wrapper with chainable queries:\n\n```javascript\nconst db = Manager.firestore();\n\n// Document operations - two syntax options\nawait db.doc('users/user123').set({ name: 'John', age: 30 });\nawait db.doc('users', 'user123').update({ age: 31 });\n\nconst docSnap = await db.doc('users/user123').get();\nif (docSnap.exists()) {\n  console.log('Data:', docSnap.data());\n  console.log('ID:', docSnap.id);\n}\n\nawait db.doc('users/user123').delete();\n\n// Collection queries\nconst snapshot = await db.collection('users').get();\nconsole.log('Count:', snapshot.size);\nconsole.log('Empty:', snapshot.empty);\nsnapshot.docs.forEach(doc =\u003e {\n  console.log(doc.id, doc.data());\n});\n\n// Query with filters (chainable)\nconst results = await db.collection('users')\n  .where('age', '\u003e=', 18)\n  .where('active', '==', true)\n  .orderBy('createdAt', 'desc')\n  .limit(20)\n  .get();\n\n// Pagination\nconst page2 = await db.collection('users')\n  .orderBy('name')\n  .startAt('M')\n  .endAt('N')\n  .get();\n```\n\n**Where Operators**: `\u003c`, `\u003c=`, `==`, `!=`, `\u003e=`, `\u003e`, `array-contains`, `in`, `array-contains-any`, `not-in`\n\n### Push Notifications\n\nFirebase Cloud Messaging integration:\n\n```javascript\nconst notifications = Manager.notifications();\n\n// Check support\nif (notifications.isSupported()) {\n  console.log('Push notifications available');\n}\n\n// Check subscription status\nconst isSubscribed = await notifications.isSubscribed();\n\n// Subscribe\ntry {\n  const result = await notifications.subscribe({\n    vapidKey: 'your-vapid-key' // Optional\n  });\n  console.log('Token:', result.token);\n} catch (error) {\n  if (error.message.includes('permission')) {\n    console.log('User denied permission');\n  }\n}\n\n// Unsubscribe\nawait notifications.unsubscribe();\n\n// Get current token\nconst token = await notifications.getToken();\n\n// Listen for foreground messages\nconst unsubscribe = await notifications.onMessage((payload) =\u003e {\n  console.log('Received:', payload);\n});\n\n// Sync subscription with auth state\nawait notifications.syncSubscription();\n```\n\n**Features**:\n- Stores subscription in localStorage and Firestore\n- Tracks device context (platform, runtime, device)\n- Auto-requests after configurable delay post-click\n- Syncs with user authentication state\n\n### Service Worker\n\nService worker registration and messaging:\n\n```javascript\nconst sw = Manager.serviceWorker();\n\n// Check support\nif (sw.isSupported()) {\n  console.log('Service workers available');\n}\n\n// Register (done automatically during init if enabled)\nconst registration = await sw.register({\n  path: '/service-worker.js',\n  scope: '/'\n});\n\n// Wait for ready state\nawait sw.ready();\n\n// Get registration\nconst reg = sw.getRegistration();\n\n// Post message with response\ntry {\n  const response = await sw.postMessage({\n    command: 'cache-clear',\n    payload: { pattern: '*.js' }\n  }, { timeout: 5000 });\n  console.log('Response:', response);\n} catch (error) {\n  console.error('Timeout or error:', error);\n}\n\n// Listen for messages from service worker\nconst unsubscribe = sw.onMessage('notification-click', (data, event) =\u003e {\n  console.log('Clicked:', data);\n});\n\n// Get current state\nconst state = sw.getState(); // 'none', 'installing', 'waiting', 'active', 'unknown'\n```\n\n### Sentry Error Tracking\n\nAutomatic error tracking with Sentry:\n\n```javascript\nconst sentry = Manager.sentry();\n\n// Capture an exception\ntry {\n  throw new Error('Something went wrong');\n} catch (error) {\n  sentry.captureException(error, {\n    tags: { feature: 'checkout' },\n    extra: { orderId: '12345' }\n  });\n}\n```\n\n**Automatic Features**:\n- Environment and release tracking from config\n- User context from auth state (uid, email)\n- Session duration tracking\n- Filters out Lighthouse and automated browsers (Selenium, Puppeteer)\n- Blocks sending in development mode\n- Dynamic import to reduce bundle size\n\n### DOM Utilities\n\n```javascript\nimport { loadScript, ready } from 'web-manager/modules/dom';\n// Or: const { loadScript, ready } = Manager.dom();\n\n// Wait for DOM ready\nawait ready();\n\n// Load external script\nawait loadScript({\n  src: 'https://example.com/script.js',\n  async: true,\n  defer: false,\n  crossorigin: 'anonymous',\n  integrity: 'sha384-...',\n  timeout: 30000,\n  retries: 2,\n  parent: document.head,\n  attributes: { 'data-custom': 'value' }\n});\n\n// Simple string syntax\nawait loadScript('https://example.com/script.js');\n```\n\n**loadScript Options**:\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `src` | string | required | Script URL |\n| `async` | boolean | `true` | Load asynchronously |\n| `defer` | boolean | `false` | Defer execution |\n| `crossorigin` | boolean/string | `false` | CORS setting |\n| `integrity` | string | `null` | SRI hash |\n| `timeout` | number | `60000` | Timeout in ms |\n| `retries` | number | `0` | Retry attempts |\n| `parent` | Element | `document.head` | Parent element |\n| `attributes` | object | `{}` | Custom attributes |\n\n### Utility Functions\n\n```javascript\nimport {\n  clipboardCopy,\n  escapeHTML,\n  showNotification,\n  getPlatform,\n  getBrowser,\n  getRuntime,\n  isMobile,\n  getDevice,\n  getContext\n} from 'web-manager/modules/utilities';\n// Or: const utils = Manager.utilities();\n\n// Copy to clipboard\nawait clipboardCopy('Text to copy');\nawait clipboardCopy(document.querySelector('#input')); // From element\n\n// Escape HTML (XSS prevention)\nconst safe = escapeHTML('\u003cscript\u003ealert(\"xss\")\u003c/script\u003e');\n// '\u0026lt;script\u0026gt;alert(\u0026quot;xss\u0026quot;)\u0026lt;/script\u0026gt;'\n\n// Show notification (Bootstrap-styled)\nshowNotification('Success!', { type: 'success', timeout: 5000 });\nshowNotification('Error!', 'danger');\nshowNotification(new Error('Failed'), { timeout: 0 }); // No auto-dismiss\n\n// Platform detection\ngetPlatform(); // 'windows', 'mac', 'linux', 'ios', 'android', 'chromeos', 'unknown'\n\n// Browser detection\ngetBrowser(); // 'chrome', 'firefox', 'safari', 'edge', 'opera', 'brave', null\n\n// Runtime detection\ngetRuntime(); // 'web', 'browser-extension'\n\n// Device detection\nisMobile();   // true/false\ngetDevice();  // 'mobile' (\u003c768px), 'tablet' (768-1199px), 'desktop' (\u003e=1200px)\n\n// Full context\ngetContext();\n// {\n//   client: { language, mobile, device, platform, browser, vendor, runtime, userAgent, url },\n//   geolocation: { ip, country, region, city, latitude, longitude }\n// }\n```\n\n**showNotification Options**:\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `type` | string | `'info'` | `'info'`, `'success'`, `'warning'`, `'danger'` |\n| `timeout` | number | `5000` | Auto-dismiss after ms (0 = never) |\n\n## HTML Data Attributes\n\nWeb Manager automatically sets these attributes on the `\u003chtml\u003e` element during initialization:\n\n```html\n\u003chtml data-platform=\"mac\" data-browser=\"chrome\" data-runtime=\"web\" data-device=\"desktop\"\u003e\n```\n\n| Attribute | Values | Description |\n|-----------|--------|-------------|\n| `data-platform` | `windows`, `mac`, `linux`, `ios`, `android`, `chromeos`, `unknown` | Operating system |\n| `data-browser` | `chrome`, `firefox`, `safari`, `edge`, `opera`, `brave` | Browser name |\n| `data-runtime` | `web`, `browser-extension` | Runtime environment |\n| `data-device` | `mobile`, `tablet`, `desktop` | Device type by screen width |\n\n**CSS Usage**:\n```css\n/* Platform-specific styles */\n[data-platform=\"ios\"] .download-btn { display: none; }\n[data-platform=\"windows\"] .app-icon { content: url('windows-icon.png'); }\n\n/* Browser-specific styles */\n[data-browser=\"safari\"] .webkit-fix { -webkit-transform: translateZ(0); }\n[data-browser=\"firefox\"] .gecko-fix { overflow: hidden; }\n\n/* Device-responsive styles */\n[data-device=\"mobile\"] .sidebar { display: none; }\n[data-device=\"desktop\"] .mobile-menu { display: none; }\n```\n\n## Direct Module Imports\n\nImport individual modules to reduce bundle size:\n\n```javascript\n// Storage only\nimport Storage from 'web-manager/modules/storage';\nconst storage = new Storage();\n\n// Utilities only\nimport { clipboardCopy, escapeHTML } from 'web-manager/modules/utilities';\n\n// DOM utilities only\nimport { loadScript, ready } from 'web-manager/modules/dom';\n\n// Full manager (default)\nimport Manager from 'web-manager';\n```\n\n**Available Modules**:\n- `web-manager/modules/storage` - Storage class\n- `web-manager/modules/utilities` - Utility functions\n- `web-manager/modules/dom` - DOM utilities\n- `web-manager/modules/auth` - Auth class (requires Manager)\n- `web-manager/modules/bindings` - Bindings class (requires Manager)\n- `web-manager/modules/firestore` - Firestore class (requires Manager)\n- `web-manager/modules/notifications` - Notifications class (requires Manager)\n- `web-manager/modules/service-worker` - ServiceWorker class (requires Manager)\n- `web-manager/modules/sentry` - Sentry class (requires Manager)\n\n## Browser Support\n\nWeb Manager is transpiled to ES5 for broad browser support:\n\n| Browser | Version | Support |\n|---------|---------|---------|\n| Chrome | 60+ | Full |\n| Firefox | 55+ | Full |\n| Safari | 11+ | Full |\n| Edge | 79+ | Full |\n| IE | 11 | Not supported |\n\n**Notes**:\n- Service Workers require HTTPS (except localhost)\n- Push Notifications require Service Worker support\n- Some features use modern APIs with fallbacks\n\n## Projects Using This Library\n\n- [Somiibo](https://somiibo.com/): A Social Media Bot with an open-source module library\n- [JekyllUp](https://jekyllup.com/): A website devoted to sharing the best Jekyll themes\n- [Slapform](https://slapform.com/): A backend processor for HTML forms on static sites\n- [SoundGrail Music App](https://app.soundgrail.com/): A resource for producers, musicians, and DJs\n- [Hammock Report](https://hammockreport.com/): An API for exploring and listing backyard products\n\n*Want your project listed? [Open an issue](https://github.com/itw-creative-works/web-manager/issues)!*\n\n## Support\n\nIf you're having issues or have questions:\n- [Open an issue](https://github.com/itw-creative-works/web-manager/issues) on GitHub\n- Include code samples and relevant files to help us help you faster\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fitw-creative-works%2Fweb-manager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fitw-creative-works%2Fweb-manager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fitw-creative-works%2Fweb-manager/lists"}