@sylphx/sdk 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -260
- package/dist/index.js +61 -65
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +61 -65
- package/dist/index.mjs.map +1 -1
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/index.mjs.map +1 -1
- package/dist/react/index.js +3668 -12695
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +3671 -12698
- package/dist/react/index.mjs.map +1 -1
- package/dist/server/index.js +40 -39
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +39 -39
- package/dist/server/index.mjs.map +1 -1
- package/dist/web-analytics.js +1 -1
- package/dist/web-analytics.js.map +1 -1
- package/dist/web-analytics.mjs +1 -1
- package/dist/web-analytics.mjs.map +1 -1
- package/package.json +5 -6
- package/dist/index.d.cts +0 -13938
- package/dist/index.d.ts +0 -13938
- package/dist/nextjs/index.d.cts +0 -2089
- package/dist/nextjs/index.d.ts +0 -2089
- package/dist/react/index.d.cts +0 -14894
- package/dist/react/index.d.ts +0 -14894
- package/dist/server/index.d.cts +0 -9908
- package/dist/server/index.d.ts +0 -9908
- package/dist/web-analytics.d.cts +0 -90
- package/dist/web-analytics.d.ts +0 -90
package/dist/web-analytics.js
CHANGED
|
@@ -27,7 +27,7 @@ __export(web_analytics_exports, {
|
|
|
27
27
|
module.exports = __toCommonJS(web_analytics_exports);
|
|
28
28
|
|
|
29
29
|
// src/constants.ts
|
|
30
|
-
var SDK_API_PATH = `/api/
|
|
30
|
+
var SDK_API_PATH = `/api/v1`;
|
|
31
31
|
var SDK_PLATFORM = typeof window !== "undefined" ? "browser" : typeof process !== "undefined" && process.versions?.node ? "node" : "unknown";
|
|
32
32
|
var SESSION_TOKEN_LIFETIME_SECONDS = 5 * 60;
|
|
33
33
|
var SESSION_TOKEN_LIFETIME_MS = SESSION_TOKEN_LIFETIME_SECONDS * 1e3;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/web-analytics.ts","../src/constants.ts","../src/lib/analytics/web-analytics.ts"],"sourcesContent":["/**\n * @sylphx/platform-sdk/web-analytics\n *\n * Web Analytics entry point.\n * Exports the tracker, hook, and types for web page-view analytics.\n *\n * @example\n * ```typescript\n * import { initWebAnalytics } from '@sylphx/platform-sdk/web-analytics'\n *\n * initWebAnalytics({\n * appKey: process.env.NEXT_PUBLIC_SYLPHX_APP_KEY!,\n * endpoint: '', // empty = same origin\n * })\n * ```\n */\n\nexport {\n\tWebAnalyticsTracker,\n\tgetWebAnalyticsTracker,\n\tinitWebAnalytics,\n\ttype WebAnalyticsOptions,\n\ttype PageViewPayload,\n\ttype IdentifyPayload,\n} from \"./lib/analytics/web-analytics\";\n","/**\n * SDK Constants — Single Source of Truth\n *\n * Shared constants used across the SDK. Centralizing these\n * prevents magic number duplication and makes changes easier.\n *\n * IMPORTANT: All time-based constants should be used consistently\n * across the SDK. Never hardcode magic numbers like 30000, 5 * 60 * 1000, etc.\n */\n\n// =============================================================================\n// API Configuration\n// =============================================================================\n\n/** Default platform URL */\nexport const DEFAULT_PLATFORM_URL = 'https://sylphx.com'\n\n/**\n * Canonical environment variable name for the platform URL.\n *\n * SDK modules MUST read from this env var (with SYLPHX_URL as legacy fallback).\n * Centralizing the name prevents the same env var being spelled differently\n * across kv.ts, streams.ts, ai.ts, middleware.ts, and server.ts.\n */\nexport const ENV_PLATFORM_URL = 'SYLPHX_PLATFORM_URL'\n\n/**\n * Legacy environment variable name for the platform URL.\n *\n * Supported as a fallback for older deployments that set SYLPHX_URL.\n * New projects should use SYLPHX_PLATFORM_URL.\n */\nexport const ENV_PLATFORM_URL_LEGACY = 'SYLPHX_URL'\n\n/**\n * Canonical environment variable name for the secret key.\n *\n * All server-side SDK modules read from this env var by default.\n */\nexport const ENV_SECRET_KEY = 'SYLPHX_SECRET_KEY'\n\n/**\n * Resolve the platform URL from environment variables.\n *\n * Priority: explicit value > SYLPHX_PLATFORM_URL > SYLPHX_URL (legacy) > default\n */\nexport function resolvePlatformUrl(explicit?: string): string {\n\treturn (\n\t\texplicit ||\n\t\tprocess.env[ENV_PLATFORM_URL] ||\n\t\tprocess.env[ENV_PLATFORM_URL_LEGACY] ||\n\t\tDEFAULT_PLATFORM_URL\n\t).trim()\n}\n\n/**\n * Resolve the secret key from environment variables.\n *\n * Returns the raw value before validation. Callers should pass the result\n * through `validateAndSanitizeSecretKey()`.\n */\nexport function resolveSecretKey(explicit?: string): string | undefined {\n\treturn explicit || process.env[ENV_SECRET_KEY]\n}\n\n/** @deprecated No longer used for path construction. Use SDK_API_PATH directly. */\nexport const SDK_API_VERSION = 'v1'\n\n/**\n * SDK API base path — legacy path served from main Next.js app.\n *\n * Used when `platformUrl` is set to `https://sylphx.com` (default).\n * Points to the runtime Hono app mounted at /api/app/v1 (via Next.js catch-all).\n *\n * @deprecated Prefer project `ref`-based URL: https://{ref}.api.sylphx.com/v1\n */\nexport const SDK_API_PATH = `/api/app/v1`\n\n/**\n * SDK API path for new subdomain-based SDK server.\n *\n * Used when `ref` is provided to `createConfig`.\n * The full base becomes: https://{ref}.api.sylphx.com + SDK_API_PATH_NEW\n */\nexport const SDK_API_PATH_NEW = `/v1`\n\n/**\n * Default SDK API host for new subdomain-based SDK server.\n * The full URL is built as: https://{ref}.api.sylphx.com/v1\n */\nexport const DEFAULT_SDK_API_HOST = 'api.sylphx.com'\n\n/**\n * Default auth route prefix\n *\n * Used for OAuth callbacks and signout routes.\n * Must match the middleware's authPrefix config.\n */\nexport const DEFAULT_AUTH_PREFIX = '/auth'\n\n/**\n * SDK package version\n *\n * Sent in X-SDK-Version header for debugging and analytics.\n * Update this when releasing new SDK versions.\n */\nexport const SDK_VERSION = '0.1.0'\n\n/**\n * SDK platform identifier\n *\n * Sent in X-SDK-Platform header to identify the runtime environment.\n */\nexport const SDK_PLATFORM =\n\ttypeof window !== 'undefined'\n\t\t? 'browser'\n\t\t: typeof process !== 'undefined' && process.versions?.node\n\t\t\t? 'node'\n\t\t\t: 'unknown'\n\n// =============================================================================\n// Timeouts & Durations\n// =============================================================================\n\n/** Default request timeout in milliseconds (30 seconds) */\nexport const DEFAULT_TIMEOUT_MS = 30_000\n\n/**\n * Token expiry buffer in milliseconds (30 seconds)\n *\n * Refresh tokens this many milliseconds BEFORE they expire\n * to account for network latency and clock skew.\n */\nexport const TOKEN_EXPIRY_BUFFER_MS = 30_000\n\n/**\n * Session token lifetime in seconds (5 minutes)\n *\n * Matches Clerk's short-lived access token pattern.\n * Used for cookie maxAge and React Query staleTime.\n */\nexport const SESSION_TOKEN_LIFETIME_SECONDS = 5 * 60\n\n/** Session token lifetime in milliseconds (for React Query staleTime) */\nexport const SESSION_TOKEN_LIFETIME_MS = SESSION_TOKEN_LIFETIME_SECONDS * 1000\n\n/**\n * Refresh token lifetime in seconds (30 days)\n *\n * Long-lived token for silent refresh.\n */\nexport const REFRESH_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60\n\n// =============================================================================\n// Feature Flags Cache\n// =============================================================================\n\n/**\n * Feature flags cache TTL in milliseconds (5 minutes)\n *\n * How long to cache flags before fetching fresh values.\n * Matches LaunchDarkly's default streaming connection behavior.\n */\nexport const FLAGS_CACHE_TTL_MS = 5 * 60 * 1000\n\n/**\n * Feature flags stale-while-revalidate window in milliseconds (1 minute)\n *\n * Allow serving stale flags while fetching fresh values.\n */\nexport const FLAGS_STALE_WHILE_REVALIDATE_MS = 60 * 1000\n\n// =============================================================================\n// Retry & Backoff\n// =============================================================================\n\n/** Maximum retry delay for exponential backoff (30 seconds) */\nexport const MAX_RETRY_DELAY_MS = 30_000\n\n/** Base retry delay for exponential backoff (1 second) */\nexport const BASE_RETRY_DELAY_MS = 1_000\n\n/** Maximum number of retries for network requests */\nexport const MAX_RETRIES = 3\n\n// =============================================================================\n// Analytics\n// =============================================================================\n\n/**\n * Analytics session timeout in milliseconds (30 minutes)\n *\n * After this much inactivity, a new session is started.\n */\nexport const ANALYTICS_SESSION_TIMEOUT_MS = 30 * 60 * 1000\n\n// =============================================================================\n// Webhooks\n// =============================================================================\n\n/**\n * Maximum age for webhook signature validation (5 minutes)\n *\n * Reject webhooks with timestamps older than this.\n */\nexport const WEBHOOK_MAX_AGE_MS = 5 * 60 * 1000\n\n/**\n * Clock skew allowance for webhook validation (30 seconds)\n *\n * Allow timestamps this far in the future.\n */\nexport const WEBHOOK_CLOCK_SKEW_MS = 30 * 1000\n\n// =============================================================================\n// PKCE (OAuth)\n// =============================================================================\n\n/**\n * PKCE code verifier TTL in milliseconds (10 minutes)\n *\n * How long the code verifier is stored during OAuth flow.\n */\nexport const PKCE_CODE_TTL_MS = 10 * 60 * 1000\n\n// =============================================================================\n// Jobs\n// =============================================================================\n\n/**\n * Job dead-letter queue retention in milliseconds (7 days)\n */\nexport const JOBS_DLQ_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000\n\n// =============================================================================\n// Session Replay\n// =============================================================================\n\n/**\n * Maximum session replay recording duration in milliseconds (60 minutes)\n */\nexport const SESSION_REPLAY_MAX_DURATION_MS = 60 * 60 * 1000\n\n/**\n * Session replay upload interval in milliseconds (5 seconds)\n */\nexport const SESSION_REPLAY_UPLOAD_INTERVAL_MS = 5_000\n\n/**\n * Session replay scroll event throttle interval (150 ms)\n */\nexport const SESSION_REPLAY_SCROLL_THROTTLE_MS = 150\n\n/**\n * Session replay media time update throttle interval (800 ms)\n */\nexport const SESSION_REPLAY_MEDIA_THROTTLE_MS = 800\n\n/**\n * Session replay rage click detection window (1 second)\n */\nexport const SESSION_REPLAY_RAGE_CLICK_WINDOW_MS = 1_000\n\n/**\n * Session replay dead click detection timeout (500 ms)\n */\nexport const SESSION_REPLAY_DEAD_CLICK_TIMEOUT_MS = 500\n\n/**\n * Session replay scroll heat detection window (2 seconds)\n */\nexport const SESSION_REPLAY_SCROLL_HEAT_WINDOW_MS = 2_000\n\n/**\n * Session replay status check interval (5 seconds)\n */\nexport const SESSION_REPLAY_STATUS_CHECK_MS = 5_000\n\n// =============================================================================\n// Analytics (Extended)\n// =============================================================================\n\n/**\n * Analytics event flush interval in milliseconds (5 seconds)\n */\nexport const ANALYTICS_FLUSH_INTERVAL_MS = 5_000\n\n/**\n * Analytics maximum text length for autocapture (100 characters)\n */\nexport const ANALYTICS_MAX_TEXT_LENGTH = 100\n\n/**\n * Analytics flush timeout in milliseconds (1 second)\n */\nexport const ANALYTICS_FLUSH_TIMEOUT_MS = 1_000\n\n/**\n * Analytics interval check in milliseconds (1 second)\n */\nexport const ANALYTICS_INTERVAL_CHECK_MS = 1_000\n\n/**\n * Analytics retry base delay in milliseconds (1 second)\n * Exponential backoff: delay = base * 2^retries (with jitter)\n */\nexport const ANALYTICS_RETRY_BASE_DELAY_MS = 1_000\n\n/**\n * Analytics retry max delay in milliseconds (30 seconds)\n */\nexport const ANALYTICS_RETRY_MAX_DELAY_MS = 30_000\n\n/**\n * Analytics retry jitter factor (±20%)\n * Prevents thundering herd when multiple clients retry simultaneously\n */\nexport const ANALYTICS_RETRY_JITTER = 0.2\n\n/**\n * Analytics maximum retries before dropping event (Segment pattern: 10)\n */\nexport const ANALYTICS_MAX_RETRIES = 10\n\n// =============================================================================\n// Feature Flags (Extended)\n// =============================================================================\n\n/**\n * Feature flags exposure deduplication window (1 hour)\n *\n * Prevents duplicate exposure events for A/B tests within this window.\n */\nexport const FLAGS_EXPOSURE_DEDUPE_WINDOW_MS = 60 * 60 * 1000\n\n/**\n * Flag stream initial reconnection delay (1 second)\n */\nexport const FLAGS_STREAM_INITIAL_RECONNECT_MS = 1_000\n\n/**\n * Flag stream maximum reconnection delay (30 seconds)\n */\nexport const FLAGS_STREAM_MAX_RECONNECT_MS = 30_000\n\n/**\n * Flag stream heartbeat timeout (45 seconds)\n */\nexport const FLAGS_STREAM_HEARTBEAT_TIMEOUT_MS = 45_000\n\n/**\n * Flag HTTP polling interval fallback (60 seconds)\n */\nexport const FLAGS_HTTP_POLLING_INTERVAL_MS = 60_000\n\n// =============================================================================\n// Jobs (Extended)\n// =============================================================================\n\n/**\n * Default retry delay sequence for exponential backoff (ms)\n */\nexport const DEFAULT_RETRY_DELAYS_MS = [1_000, 5_000, 15_000, 30_000, 60_000] as const\n\n/**\n * Default job timeout in milliseconds (60 seconds)\n */\nexport const JOB_DEFAULT_TIMEOUT_MS = 60_000\n\n/**\n * Default job status polling interval (2 seconds)\n */\nexport const JOB_POLL_INTERVAL_MS = 2_000\n\n// =============================================================================\n// Storage Keys & Prefixes\n// =============================================================================\n\n/**\n * Storage key prefix for SDK data\n */\nexport const STORAGE_KEY_PREFIX = 'sylphx_'\n\n/**\n * localStorage key for cached feature flags\n */\nexport const FLAGS_CACHE_KEY = 'sylphx_feature_flags'\n\n/**\n * localStorage key for feature flags cache timestamp\n */\nexport const FLAGS_CACHE_TIMESTAMP_KEY = 'sylphx_feature_flags_ts'\n\n/**\n * localStorage key for feature flags overrides\n */\nexport const FLAGS_OVERRIDES_KEY = 'sylphx_feature_flags_overrides'\n\n/**\n * localStorage key for active organization\n */\nexport const ORG_STORAGE_KEY = 'sylphx_active_org'\n\n/**\n * BroadcastChannel name for cross-tab org sync\n */\nexport const ORG_BROADCAST_CHANNEL = 'sylphx_org_sync'\n\n/**\n * Storage prefix for PKCE verifiers\n */\nexport const PKCE_STORAGE_PREFIX = 'sylphx_pkce_'\n\n/**\n * Test key for checking storage availability\n */\nexport const STORAGE_TEST_KEY = '__sylphx_test__'\n\n/**\n * Cookie/storage name for analytics sessions\n */\nexport const ANALYTICS_SESSION_KEY = 'sylphx_session'\n\n/**\n * Default storage key for flags persistence\n */\nexport const FLAGS_STORAGE_KEY = 'sylphx_flags'\n\n// =============================================================================\n// Click ID & Attribution\n// =============================================================================\n\n/**\n * Click ID attribution window in milliseconds (90 days)\n *\n * How long click IDs are stored for conversion attribution.\n */\nexport const CLICK_ID_EXPIRY_MS = 90 * 24 * 60 * 60 * 1000\n\n// =============================================================================\n// React Query Stale Times\n// =============================================================================\n\n/**\n * React Query staleTime for frequently-changing data (1 minute)\n *\n * Use for: real-time metrics, live feeds, active sessions\n */\nexport const STALE_TIME_FREQUENT_MS = 60 * 1_000\n\n/**\n * React Query staleTime for moderately-changing data (2 minutes)\n *\n * Use for: subscriptions, user profiles, preferences\n */\nexport const STALE_TIME_MODERATE_MS = 2 * 60 * 1_000\n\n/**\n * React Query staleTime for stable/config data (5 minutes)\n *\n * Use for: plans, feature flags, app config\n */\nexport const STALE_TIME_STABLE_MS = 5 * 60 * 1_000\n\n/**\n * React Query staleTime for webhook stats (30 seconds)\n */\nexport const STALE_TIME_STATS_MS = 30 * 1_000\n\n// =============================================================================\n// UI Component Timeouts\n// =============================================================================\n\n/**\n * Copy-to-clipboard feedback display duration (2 seconds)\n */\nexport const UI_COPY_FEEDBACK_MS = 2_000\n\n/**\n * Form success message display duration (3 seconds)\n */\nexport const UI_FORM_SUCCESS_MS = 3_000\n\n/**\n * General notification display duration (5 seconds)\n */\nexport const UI_NOTIFICATION_MS = 5_000\n\n/**\n * Prompt auto-show delay (3 seconds)\n */\nexport const UI_PROMPT_DELAY_MS = 3_000\n\n/**\n * Redirect delay after action (3 seconds)\n */\nexport const UI_REDIRECT_DELAY_MS = 3_000\n\n/**\n * Animation out duration (200 ms)\n */\nexport const UI_ANIMATION_OUT_MS = 200\n\n/**\n * Animation in duration (300 ms)\n */\nexport const UI_ANIMATION_IN_MS = 300\n\n// =============================================================================\n// Email & Verification\n// =============================================================================\n\n/**\n * Email resend cooldown tick interval (1 second)\n */\nexport const EMAIL_RESEND_COOLDOWN_TICK_MS = 1_000\n\n/**\n * New user detection threshold (1 minute)\n *\n * Users created within this window are considered \"new\" for signup tracking.\n */\nexport const NEW_USER_THRESHOLD_MS = 60 * 1_000\n\n// =============================================================================\n// Web Vitals Thresholds\n// =============================================================================\n\n/**\n * FCP (First Contentful Paint) \"good\" threshold (1800 ms)\n */\nexport const WEB_VITALS_FCP_GOOD_MS = 1_800\n\n/**\n * FCP (First Contentful Paint) \"poor\" threshold (3000 ms)\n */\nexport const WEB_VITALS_FCP_POOR_MS = 3_000\n\n// =============================================================================\n// Storage Sizes\n// =============================================================================\n\n/**\n * Multipart upload threshold (5 MB)\n *\n * Files larger than this use multipart upload for better reliability.\n */\nexport const STORAGE_MULTIPART_THRESHOLD_BYTES = 5 * 1024 * 1024\n\n/**\n * Default max file size for uploads (5 MB)\n */\nexport const STORAGE_DEFAULT_MAX_SIZE_BYTES = 5 * 1024 * 1024\n\n/**\n * Avatar max file size (2 MB)\n */\nexport const STORAGE_AVATAR_MAX_SIZE_BYTES = 2 * 1024 * 1024\n\n/**\n * Large file max size for file uploads (10 MB)\n */\nexport const STORAGE_LARGE_MAX_SIZE_BYTES = 10 * 1024 * 1024\n\n// =============================================================================\n// Cache TTLs\n// =============================================================================\n\n/**\n * JWK cache TTL (1 hour)\n */\nexport const JWK_CACHE_TTL_MS = 60 * 60 * 1000\n\n// =============================================================================\n// Analytics Event Tracking\n// =============================================================================\n\n/**\n * Max tracked event IDs to keep in memory\n */\nexport const ANALYTICS_MAX_TRACKED_EVENT_IDS = 1000\n\n/**\n * Number of event IDs to keep after cleanup\n */\nexport const ANALYTICS_TRACKED_IDS_KEEP = 500\n\n/**\n * Analytics queue limit before force flush\n */\nexport const ANALYTICS_QUEUE_LIMIT = 100\n\n// =============================================================================\n// Session Replay (Extended)\n// =============================================================================\n\n/**\n * Session replay check interval (1 second)\n */\nexport const SESSION_REPLAY_CHECK_INTERVAL_MS = 1_000\n\n/**\n * Success feedback delay for invite/account actions (1.5 seconds)\n */\nexport const UI_SUCCESS_REDIRECT_MS = 1_500\n\n// =============================================================================\n// String Truncation Limits\n// =============================================================================\n\n/**\n * Max message length for logging (1000 chars)\n */\nexport const LOG_MESSAGE_MAX_LENGTH = 1_000\n\n/**\n * Max DOM snapshot length for debugging (1000 chars)\n */\nexport const DOM_SNAPSHOT_MAX_LENGTH = 1_000\n\n/**\n * Max stack trace length for error tracking (500 chars)\n */\nexport const STACK_TRACE_MAX_LENGTH = 500\n\n/**\n * Google Consent Mode wait for update timeout (500 ms)\n */\nexport const CONSENT_WAIT_FOR_UPDATE_MS = 500\n\n// =============================================================================\n// Time Unit Conversions\n// =============================================================================\n\n/** Milliseconds per minute (60,000) */\nexport const MS_PER_MINUTE = 60_000\n\n/** Milliseconds per hour (3,600,000) */\nexport const MS_PER_HOUR = 3_600_000\n\n/** Milliseconds per day (86,400,000) */\nexport const MS_PER_DAY = 86_400_000\n\n/** Seconds per minute (60) */\nexport const SECONDS_PER_MINUTE = 60\n\n/** Seconds per hour (3,600) */\nexport const SECONDS_PER_HOUR = 3_600\n\n// =============================================================================\n// Z-Index Values\n// =============================================================================\n\n/** Z-index for modal overlays (9999) */\nexport const Z_INDEX_OVERLAY = 9999\n\n/** Z-index for critical overlays like feature gates (99999) */\nexport const Z_INDEX_CRITICAL_OVERLAY = 99999\n\n// =============================================================================\n// API Key Expiry (seconds)\n// =============================================================================\n\n/** API key expiry: 1 day (86,400 seconds) */\nexport const API_KEY_EXPIRY_1_DAY = 86_400\n\n/** API key expiry: 7 days (604,800 seconds) */\nexport const API_KEY_EXPIRY_7_DAYS = 604_800\n\n/** API key expiry: 30 days (2,592,000 seconds) */\nexport const API_KEY_EXPIRY_30_DAYS = 2_592_000\n\n/** API key expiry: 90 days (7,776,000 seconds) */\nexport const API_KEY_EXPIRY_90_DAYS = 7_776_000\n\n/** API key expiry: 1 year (31,536,000 seconds) */\nexport const API_KEY_EXPIRY_1_YEAR = 31_536_000\n\n// =============================================================================\n// Web Vitals Thresholds (Google standards)\n// =============================================================================\n\n/** LCP (Largest Contentful Paint) \"good\" threshold (2500 ms) */\nexport const WEB_VITALS_LCP_GOOD_MS = 2_500\n\n/** LCP (Largest Contentful Paint) \"poor\" threshold (4000 ms) */\nexport const WEB_VITALS_LCP_POOR_MS = 4_000\n\n/** INP (Interaction to Next Paint) \"good\" threshold (200 ms) */\nexport const WEB_VITALS_INP_GOOD_MS = 200\n\n/** INP (Interaction to Next Paint) \"poor\" threshold (500 ms) */\nexport const WEB_VITALS_INP_POOR_MS = 500\n\n/** TTFB (Time to First Byte) \"good\" threshold (800 ms) */\nexport const WEB_VITALS_TTFB_GOOD_MS = 800\n\n/** TTFB (Time to First Byte) \"poor\" threshold (1800 ms) */\nexport const WEB_VITALS_TTFB_POOR_MS = 1_800\n\n// =============================================================================\n// Security\n// =============================================================================\n\n/** Minimum password length (NIST SP 800-63B recommends 12+) */\nexport const MIN_PASSWORD_LENGTH = 12\n\n// =============================================================================\n// AI\n// =============================================================================\n\n/** Default context window for AI models (4096 tokens) */\nexport const DEFAULT_CONTEXT_WINDOW = 4_096\n\n// =============================================================================\n// Circuit Breaker (AWS/Resilience4j pattern)\n// =============================================================================\n\n/**\n * Circuit breaker failure threshold\n *\n * Number of failures in the window before circuit opens.\n */\nexport const CIRCUIT_BREAKER_FAILURE_THRESHOLD = 5\n\n/**\n * Circuit breaker failure window in milliseconds (10 seconds)\n *\n * Time window for counting failures.\n */\nexport const CIRCUIT_BREAKER_WINDOW_MS = 10_000\n\n/**\n * Circuit breaker open duration in milliseconds (30 seconds)\n *\n * How long the circuit stays open before allowing a test request.\n */\nexport const CIRCUIT_BREAKER_OPEN_DURATION_MS = 30_000\n\n// =============================================================================\n// ETag Cache (HTTP conditional requests)\n// =============================================================================\n\n/**\n * Maximum ETag cache entries\n *\n * LRU eviction when exceeded.\n */\nexport const ETAG_CACHE_MAX_ENTRIES = 100\n\n/**\n * ETag cache TTL in milliseconds (5 minutes)\n *\n * How long cached responses are valid.\n */\nexport const ETAG_CACHE_TTL_MS = 5 * 60 * 1000\n","/**\n * Web Analytics Tracker\n *\n * Lightweight page-view analytics tracker for Sylphx Platform SDK.\n * Automatically tracks page views, bounce rate, and session data.\n *\n * @example\n * ```typescript\n * import { SDK_API_PATH } from \"../../constants\";\nimport { WebAnalyticsTracker } from '@sylphx/platform-sdk/web-analytics'\n *\n * const tracker = new WebAnalyticsTracker()\n * tracker.init({\n * appKey: 'app_prod_xxx',\n * endpoint: 'https://your-app.com',\n * })\n * ```\n */\n\nimport { SDK_API_PATH } from '../../constants'\n\n// ============================================\n// Types\n// ============================================\n\nexport interface WebAnalyticsOptions {\n\t/** App key for authentication */\n\tappKey: string\n\t/** Base endpoint URL (without trailing slash) */\n\tendpoint: string\n\t/** Auto-track page views (default: true) */\n\ttrackPageViews?: boolean\n\t/** Track bounce rate (sessions with only 1 page view) (default: true) */\n\ttrackBounce?: boolean\n\t/** SPA hash routing mode (tracks hash changes) (default: false) */\n\thashMode?: boolean\n\t/** Debug logging (default: false) */\n\tdebug?: boolean\n}\n\nexport interface PageViewPayload {\n\t/** URL path (e.g. /about) */\n\tpath: string\n\t/** Document referrer */\n\treferrer: string\n\t/** Navigator user agent */\n\tuserAgent: string\n\t/** Screen width in pixels */\n\tscreenWidth: number\n\t/** Session ID (UUID stored in sessionStorage) */\n\tsessionId: string\n\t/** Unix timestamp (ms) */\n\ttimestamp: number\n}\n\nexport interface IdentifyPayload {\n\t/** User ID */\n\tuserId: string\n\t/** User traits/properties */\n\ttraits?: Record<string, unknown>\n\t/** Session ID */\n\tsessionId: string\n}\n\n// ============================================\n// Utilities\n// ============================================\n\nfunction generateSessionId(): string {\n\tif (typeof crypto !== 'undefined' && crypto.randomUUID) {\n\t\treturn crypto.randomUUID()\n\t}\n\t// Fallback for older environments\n\treturn 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n\t\tconst r = (Math.random() * 16) | 0\n\t\tconst v = c === 'x' ? r : (r & 0x3) | 0x8\n\t\treturn v.toString(16)\n\t})\n}\n\nfunction getOrCreateSessionId(): string {\n\tif (typeof sessionStorage === 'undefined') return generateSessionId()\n\tconst key = '_sylphx_sid'\n\tlet id = sessionStorage.getItem(key)\n\tif (!id) {\n\t\tid = generateSessionId()\n\t\tsessionStorage.setItem(key, id)\n\t}\n\treturn id\n}\n\nfunction getCurrentPath(hashMode: boolean): string {\n\tif (typeof window === 'undefined' || !window.location) return '/'\n\tif (hashMode) {\n\t\treturn window.location.hash.replace(/^#/, '') || '/'\n\t}\n\treturn window.location.pathname + window.location.search\n}\n\n// ============================================\n// WebAnalyticsTracker\n// ============================================\n\nexport class WebAnalyticsTracker {\n\tprivate options: Required<WebAnalyticsOptions> | null = null\n\tprivate initialized = false\n\tprivate lastPath: string | null = null\n\tprivate pageViewCount = 0\n\tprivate cleanupFns: Array<() => void> = []\n\n\t/**\n\t * Initialize the tracker and start auto-tracking page views\n\t */\n\tinit(options: WebAnalyticsOptions): void {\n\t\tif (typeof window === 'undefined') return\n\t\tif (this.initialized) return\n\n\t\tthis.options = {\n\t\t\ttrackPageViews: true,\n\t\t\ttrackBounce: true,\n\t\t\thashMode: false,\n\t\t\tdebug: false,\n\t\t\t...options,\n\t\t}\n\n\t\tthis.initialized = true\n\n\t\tif (this.options.trackPageViews) {\n\t\t\t// Track current page view\n\t\t\tthis.trackPageView()\n\n\t\t\t// Track Next.js router events (soft navigation)\n\t\t\tthis._hookNextRouter()\n\n\t\t\t// Track hash changes (for hash-mode SPAs)\n\t\t\tif (this.options.hashMode) {\n\t\t\t\tconst onHashChange = () => this.trackPageView()\n\t\t\t\twindow.addEventListener('hashchange', onHashChange)\n\t\t\t\tthis.cleanupFns.push(() => window.removeEventListener('hashchange', onHashChange))\n\t\t\t}\n\n\t\t\t// Track History API (pushState / replaceState)\n\t\t\tthis._hookHistoryApi()\n\t\t}\n\n\t\tif (this.options.debug) {\n\t\t\tconsole.log('[WebAnalytics] Initialized', this.options)\n\t\t}\n\t}\n\n\t/**\n\t * Manually track a page view\n\t */\n\ttrackPageView(path?: string): void {\n\t\tif (!this.options) return\n\t\tif (typeof window === 'undefined') return\n\n\t\tconst currentPath = path ?? getCurrentPath(this.options.hashMode)\n\n\t\t// Avoid duplicate tracking on same path\n\t\tif (currentPath === this.lastPath) return\n\t\tthis.lastPath = currentPath\n\t\tthis.pageViewCount++\n\n\t\tconst payload: PageViewPayload = {\n\t\t\tpath: currentPath,\n\t\t\treferrer: typeof document !== 'undefined' ? document.referrer || '' : '',\n\t\t\tuserAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',\n\t\t\tscreenWidth: window.screen?.width,\n\t\t\tsessionId: getOrCreateSessionId(),\n\t\t\ttimestamp: Date.now(),\n\t\t}\n\n\t\tif (this.options.debug) {\n\t\t\tconsole.log('[WebAnalytics] Page view:', payload)\n\t\t}\n\n\t\tthis._send(`${SDK_API_PATH}/analytics/pageview`, payload)\n\t}\n\n\t/**\n\t * Identify a user\n\t */\n\tidentify(userId: string, traits?: Record<string, unknown>): void {\n\t\tif (!this.options) return\n\t\tif (typeof window === 'undefined') return\n\n\t\tconst payload: IdentifyPayload = {\n\t\t\tuserId,\n\t\t\ttraits,\n\t\t\tsessionId: getOrCreateSessionId(),\n\t\t}\n\n\t\tif (this.options.debug) {\n\t\t\tconsole.log('[WebAnalytics] Identify:', payload)\n\t\t}\n\n\t\tthis._send(`${SDK_API_PATH}/analytics/identify`, payload)\n\t}\n\n\t/**\n\t * Destroy the tracker and remove event listeners\n\t */\n\tdestroy(): void {\n\t\tfor (const fn of this.cleanupFns) {\n\t\t\tfn()\n\t\t}\n\t\tthis.cleanupFns = []\n\t\tthis.initialized = false\n\t\tthis.options = null\n\t\tthis.lastPath = null\n\t\tthis.pageViewCount = 0\n\t}\n\n\t// ==========================================\n\t// Private helpers\n\t// ==========================================\n\n\tprivate _send(path: string, payload: unknown): void {\n\t\tif (!this.options) return\n\n\t\tconst url = `${this.options.endpoint}${path}`\n\t\tconst data = JSON.stringify(payload)\n\n\t\ttry {\n\t\t\tif (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n\t\t\t\tconst blob = new Blob([data], { type: 'application/json' })\n\t\t\t\tconst sent = navigator.sendBeacon(url, blob)\n\t\t\t\tif (!sent && this.options.debug) {\n\t\t\t\t\tconsole.warn('[WebAnalytics] sendBeacon failed, falling back to fetch')\n\t\t\t\t}\n\t\t\t\tif (sent) return\n\t\t\t}\n\n\t\t\t// Fallback: fetch with keepalive\n\t\t\tfetch(url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t'x-app-key': this.options.appKey,\n\t\t\t\t},\n\t\t\t\tbody: data,\n\t\t\t\tkeepalive: true,\n\t\t\t}).catch((err) => {\n\t\t\t\tif (this.options?.debug) {\n\t\t\t\t\tconsole.error('[WebAnalytics] Failed to send:', err)\n\t\t\t\t}\n\t\t\t})\n\t\t} catch (err) {\n\t\t\tif (this.options?.debug) {\n\t\t\t\tconsole.error('[WebAnalytics] Send error:', err)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _hookHistoryApi(): void {\n\t\tif (typeof window === 'undefined') return\n\n\t\tconst originalPush = window.history.pushState.bind(window.history)\n\t\tconst originalReplace = window.history.replaceState.bind(window.history)\n\n\t\twindow.history.pushState = (...args) => {\n\t\t\toriginalPush(...args)\n\t\t\t// Small delay to allow URL to update\n\t\t\tsetTimeout(() => this.trackPageView(), 0)\n\t\t}\n\n\t\twindow.history.replaceState = (...args) => {\n\t\t\toriginalReplace(...args)\n\t\t\t// Don't track replaceState (usually internal SPA navigation)\n\t\t}\n\n\t\tconst onPopState = () => setTimeout(() => this.trackPageView(), 0)\n\t\twindow.addEventListener('popstate', onPopState)\n\n\t\tthis.cleanupFns.push(() => {\n\t\t\twindow.history.pushState = originalPush\n\t\t\twindow.history.replaceState = originalReplace\n\t\t\twindow.removeEventListener('popstate', onPopState)\n\t\t})\n\t}\n\n\tprivate _hookNextRouter(): void {\n\t\t// Next.js App Router: listen to route-change events dispatched on document\n\t\tif (typeof document === 'undefined') return\n\n\t\t// Next.js 13+ soft navigation events\n\t\tconst onRouteAnnounced = () => setTimeout(() => this.trackPageView(), 0)\n\t\tdocument.addEventListener('nextjs:route-announced', onRouteAnnounced as EventListener)\n\n\t\tthis.cleanupFns.push(() => {\n\t\t\tdocument.removeEventListener('nextjs:route-announced', onRouteAnnounced as EventListener)\n\t\t})\n\t}\n}\n\n// ============================================\n// Singleton instance\n// ============================================\n\nlet _tracker: WebAnalyticsTracker | null = null\n\n/**\n * Get or create the global WebAnalyticsTracker singleton\n */\nexport function getWebAnalyticsTracker(): WebAnalyticsTracker {\n\tif (!_tracker) {\n\t\t_tracker = new WebAnalyticsTracker()\n\t}\n\treturn _tracker\n}\n\n/**\n * Initialize global web analytics tracker\n */\nexport function initWebAnalytics(options: WebAnalyticsOptions): void {\n\tgetWebAnalyticsTracker().init(options)\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/web-analytics.ts","../src/constants.ts","../src/lib/analytics/web-analytics.ts"],"sourcesContent":["/**\n * @sylphx/platform-sdk/web-analytics\n *\n * Web Analytics entry point.\n * Exports the tracker, hook, and types for web page-view analytics.\n *\n * @example\n * ```typescript\n * import { initWebAnalytics } from '@sylphx/platform-sdk/web-analytics'\n *\n * initWebAnalytics({\n * appKey: process.env.NEXT_PUBLIC_SYLPHX_APP_KEY!,\n * endpoint: '', // empty = same origin\n * })\n * ```\n */\n\nexport {\n\tWebAnalyticsTracker,\n\tgetWebAnalyticsTracker,\n\tinitWebAnalytics,\n\ttype WebAnalyticsOptions,\n\ttype PageViewPayload,\n\ttype IdentifyPayload,\n} from \"./lib/analytics/web-analytics\";\n","/**\n * SDK Constants — Single Source of Truth\n *\n * Shared constants used across the SDK. Centralizing these\n * prevents magic number duplication and makes changes easier.\n *\n * IMPORTANT: All time-based constants should be used consistently\n * across the SDK. Never hardcode magic numbers like 30000, 5 * 60 * 1000, etc.\n */\n\n// =============================================================================\n// API Configuration\n// =============================================================================\n\n/** Default platform URL */\nexport const DEFAULT_PLATFORM_URL = 'https://sylphx.com'\n\n/**\n * Canonical environment variable name for the platform URL.\n *\n * SDK modules MUST read from this env var (with SYLPHX_URL as legacy fallback).\n * Centralizing the name prevents the same env var being spelled differently\n * across kv.ts, streams.ts, ai.ts, middleware.ts, and server.ts.\n */\nexport const ENV_PLATFORM_URL = 'SYLPHX_PLATFORM_URL'\n\n/**\n * Legacy environment variable name for the platform URL.\n *\n * Supported as a fallback for older deployments that set SYLPHX_URL.\n * New projects should use SYLPHX_PLATFORM_URL.\n */\nexport const ENV_PLATFORM_URL_LEGACY = 'SYLPHX_URL'\n\n/**\n * Canonical environment variable name for the secret key.\n *\n * All server-side SDK modules read from this env var by default.\n */\nexport const ENV_SECRET_KEY = 'SYLPHX_SECRET_KEY'\n\n/**\n * Resolve the platform URL from environment variables.\n *\n * Priority: explicit value > SYLPHX_PLATFORM_URL > SYLPHX_URL (legacy) > default\n */\nexport function resolvePlatformUrl(explicit?: string): string {\n\treturn (\n\t\texplicit ||\n\t\tprocess.env[ENV_PLATFORM_URL] ||\n\t\tprocess.env[ENV_PLATFORM_URL_LEGACY] ||\n\t\tDEFAULT_PLATFORM_URL\n\t).trim()\n}\n\n/**\n * Resolve the secret key from environment variables.\n *\n * Returns the raw value before validation. Callers should pass the result\n * through `validateAndSanitizeSecretKey()`.\n */\nexport function resolveSecretKey(explicit?: string): string | undefined {\n\treturn explicit || process.env[ENV_SECRET_KEY]\n}\n\n/** @deprecated No longer used for path construction. Use SDK_API_PATH directly. */\nexport const SDK_API_VERSION = 'v1'\n\n/**\n * SDK API base path — served from the unified platform Hono app.\n *\n * Used when `platformUrl` is set (e.g. `https://sylphx.com`).\n * BaaS routes are mounted at /api/v1/projects/:slug/* on the platform app.\n *\n * @deprecated Prefer project `ref`-based URL: https://{ref}.api.sylphx.com/v1\n */\nexport const SDK_API_PATH = `/api/v1`\n\n/**\n * SDK API path for new subdomain-based SDK server.\n *\n * Used when `ref` is provided to `createConfig`.\n * The full base becomes: https://{ref}.api.sylphx.com + SDK_API_PATH_NEW\n */\nexport const SDK_API_PATH_NEW = `/v1`\n\n/**\n * Default SDK API host for new subdomain-based SDK server.\n * The full URL is built as: https://{ref}.api.sylphx.com/v1\n */\nexport const DEFAULT_SDK_API_HOST = 'api.sylphx.com'\n\n/**\n * Default auth route prefix\n *\n * Used for OAuth callbacks and signout routes.\n * Must match the middleware's authPrefix config.\n */\nexport const DEFAULT_AUTH_PREFIX = '/auth'\n\n/**\n * SDK package version\n *\n * Sent in X-SDK-Version header for debugging and analytics.\n * Update this when releasing new SDK versions.\n */\nexport const SDK_VERSION = '0.1.0'\n\n/**\n * SDK platform identifier\n *\n * Sent in X-SDK-Platform header to identify the runtime environment.\n */\nexport const SDK_PLATFORM =\n\ttypeof window !== 'undefined'\n\t\t? 'browser'\n\t\t: typeof process !== 'undefined' && process.versions?.node\n\t\t\t? 'node'\n\t\t\t: 'unknown'\n\n// =============================================================================\n// Timeouts & Durations\n// =============================================================================\n\n/** Default request timeout in milliseconds (30 seconds) */\nexport const DEFAULT_TIMEOUT_MS = 30_000\n\n/**\n * Token expiry buffer in milliseconds (30 seconds)\n *\n * Refresh tokens this many milliseconds BEFORE they expire\n * to account for network latency and clock skew.\n */\nexport const TOKEN_EXPIRY_BUFFER_MS = 30_000\n\n/**\n * Session token lifetime in seconds (5 minutes)\n *\n * Matches Clerk's short-lived access token pattern.\n * Used for cookie maxAge and React Query staleTime.\n */\nexport const SESSION_TOKEN_LIFETIME_SECONDS = 5 * 60\n\n/** Session token lifetime in milliseconds (for React Query staleTime) */\nexport const SESSION_TOKEN_LIFETIME_MS = SESSION_TOKEN_LIFETIME_SECONDS * 1000\n\n/**\n * Refresh token lifetime in seconds (30 days)\n *\n * Long-lived token for silent refresh.\n */\nexport const REFRESH_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60\n\n// =============================================================================\n// Feature Flags Cache\n// =============================================================================\n\n/**\n * Feature flags cache TTL in milliseconds (5 minutes)\n *\n * How long to cache flags before fetching fresh values.\n * Matches LaunchDarkly's default streaming connection behavior.\n */\nexport const FLAGS_CACHE_TTL_MS = 5 * 60 * 1000\n\n/**\n * Feature flags stale-while-revalidate window in milliseconds (1 minute)\n *\n * Allow serving stale flags while fetching fresh values.\n */\nexport const FLAGS_STALE_WHILE_REVALIDATE_MS = 60 * 1000\n\n// =============================================================================\n// Retry & Backoff\n// =============================================================================\n\n/** Maximum retry delay for exponential backoff (30 seconds) */\nexport const MAX_RETRY_DELAY_MS = 30_000\n\n/** Base retry delay for exponential backoff (1 second) */\nexport const BASE_RETRY_DELAY_MS = 1_000\n\n/** Maximum number of retries for network requests */\nexport const MAX_RETRIES = 3\n\n// =============================================================================\n// Analytics\n// =============================================================================\n\n/**\n * Analytics session timeout in milliseconds (30 minutes)\n *\n * After this much inactivity, a new session is started.\n */\nexport const ANALYTICS_SESSION_TIMEOUT_MS = 30 * 60 * 1000\n\n// =============================================================================\n// Webhooks\n// =============================================================================\n\n/**\n * Maximum age for webhook signature validation (5 minutes)\n *\n * Reject webhooks with timestamps older than this.\n */\nexport const WEBHOOK_MAX_AGE_MS = 5 * 60 * 1000\n\n/**\n * Clock skew allowance for webhook validation (30 seconds)\n *\n * Allow timestamps this far in the future.\n */\nexport const WEBHOOK_CLOCK_SKEW_MS = 30 * 1000\n\n// =============================================================================\n// PKCE (OAuth)\n// =============================================================================\n\n/**\n * PKCE code verifier TTL in milliseconds (10 minutes)\n *\n * How long the code verifier is stored during OAuth flow.\n */\nexport const PKCE_CODE_TTL_MS = 10 * 60 * 1000\n\n// =============================================================================\n// Jobs\n// =============================================================================\n\n/**\n * Job dead-letter queue retention in milliseconds (7 days)\n */\nexport const JOBS_DLQ_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000\n\n// =============================================================================\n// Session Replay\n// =============================================================================\n\n/**\n * Maximum session replay recording duration in milliseconds (60 minutes)\n */\nexport const SESSION_REPLAY_MAX_DURATION_MS = 60 * 60 * 1000\n\n/**\n * Session replay upload interval in milliseconds (5 seconds)\n */\nexport const SESSION_REPLAY_UPLOAD_INTERVAL_MS = 5_000\n\n/**\n * Session replay scroll event throttle interval (150 ms)\n */\nexport const SESSION_REPLAY_SCROLL_THROTTLE_MS = 150\n\n/**\n * Session replay media time update throttle interval (800 ms)\n */\nexport const SESSION_REPLAY_MEDIA_THROTTLE_MS = 800\n\n/**\n * Session replay rage click detection window (1 second)\n */\nexport const SESSION_REPLAY_RAGE_CLICK_WINDOW_MS = 1_000\n\n/**\n * Session replay dead click detection timeout (500 ms)\n */\nexport const SESSION_REPLAY_DEAD_CLICK_TIMEOUT_MS = 500\n\n/**\n * Session replay scroll heat detection window (2 seconds)\n */\nexport const SESSION_REPLAY_SCROLL_HEAT_WINDOW_MS = 2_000\n\n/**\n * Session replay status check interval (5 seconds)\n */\nexport const SESSION_REPLAY_STATUS_CHECK_MS = 5_000\n\n// =============================================================================\n// Analytics (Extended)\n// =============================================================================\n\n/**\n * Analytics event flush interval in milliseconds (5 seconds)\n */\nexport const ANALYTICS_FLUSH_INTERVAL_MS = 5_000\n\n/**\n * Analytics maximum text length for autocapture (100 characters)\n */\nexport const ANALYTICS_MAX_TEXT_LENGTH = 100\n\n/**\n * Analytics flush timeout in milliseconds (1 second)\n */\nexport const ANALYTICS_FLUSH_TIMEOUT_MS = 1_000\n\n/**\n * Analytics interval check in milliseconds (1 second)\n */\nexport const ANALYTICS_INTERVAL_CHECK_MS = 1_000\n\n/**\n * Analytics retry base delay in milliseconds (1 second)\n * Exponential backoff: delay = base * 2^retries (with jitter)\n */\nexport const ANALYTICS_RETRY_BASE_DELAY_MS = 1_000\n\n/**\n * Analytics retry max delay in milliseconds (30 seconds)\n */\nexport const ANALYTICS_RETRY_MAX_DELAY_MS = 30_000\n\n/**\n * Analytics retry jitter factor (±20%)\n * Prevents thundering herd when multiple clients retry simultaneously\n */\nexport const ANALYTICS_RETRY_JITTER = 0.2\n\n/**\n * Analytics maximum retries before dropping event (Segment pattern: 10)\n */\nexport const ANALYTICS_MAX_RETRIES = 10\n\n// =============================================================================\n// Feature Flags (Extended)\n// =============================================================================\n\n/**\n * Feature flags exposure deduplication window (1 hour)\n *\n * Prevents duplicate exposure events for A/B tests within this window.\n */\nexport const FLAGS_EXPOSURE_DEDUPE_WINDOW_MS = 60 * 60 * 1000\n\n/**\n * Flag stream initial reconnection delay (1 second)\n */\nexport const FLAGS_STREAM_INITIAL_RECONNECT_MS = 1_000\n\n/**\n * Flag stream maximum reconnection delay (30 seconds)\n */\nexport const FLAGS_STREAM_MAX_RECONNECT_MS = 30_000\n\n/**\n * Flag stream heartbeat timeout (45 seconds)\n */\nexport const FLAGS_STREAM_HEARTBEAT_TIMEOUT_MS = 45_000\n\n/**\n * Flag HTTP polling interval fallback (60 seconds)\n */\nexport const FLAGS_HTTP_POLLING_INTERVAL_MS = 60_000\n\n// =============================================================================\n// Jobs (Extended)\n// =============================================================================\n\n/**\n * Default retry delay sequence for exponential backoff (ms)\n */\nexport const DEFAULT_RETRY_DELAYS_MS = [1_000, 5_000, 15_000, 30_000, 60_000] as const\n\n/**\n * Default job timeout in milliseconds (60 seconds)\n */\nexport const JOB_DEFAULT_TIMEOUT_MS = 60_000\n\n/**\n * Default job status polling interval (2 seconds)\n */\nexport const JOB_POLL_INTERVAL_MS = 2_000\n\n// =============================================================================\n// Storage Keys & Prefixes\n// =============================================================================\n\n/**\n * Storage key prefix for SDK data\n */\nexport const STORAGE_KEY_PREFIX = 'sylphx_'\n\n/**\n * localStorage key for cached feature flags\n */\nexport const FLAGS_CACHE_KEY = 'sylphx_feature_flags'\n\n/**\n * localStorage key for feature flags cache timestamp\n */\nexport const FLAGS_CACHE_TIMESTAMP_KEY = 'sylphx_feature_flags_ts'\n\n/**\n * localStorage key for feature flags overrides\n */\nexport const FLAGS_OVERRIDES_KEY = 'sylphx_feature_flags_overrides'\n\n/**\n * localStorage key for active organization\n */\nexport const ORG_STORAGE_KEY = 'sylphx_active_org'\n\n/**\n * BroadcastChannel name for cross-tab org sync\n */\nexport const ORG_BROADCAST_CHANNEL = 'sylphx_org_sync'\n\n/**\n * Storage prefix for PKCE verifiers\n */\nexport const PKCE_STORAGE_PREFIX = 'sylphx_pkce_'\n\n/**\n * Test key for checking storage availability\n */\nexport const STORAGE_TEST_KEY = '__sylphx_test__'\n\n/**\n * Cookie/storage name for analytics sessions\n */\nexport const ANALYTICS_SESSION_KEY = 'sylphx_session'\n\n/**\n * Default storage key for flags persistence\n */\nexport const FLAGS_STORAGE_KEY = 'sylphx_flags'\n\n// =============================================================================\n// Click ID & Attribution\n// =============================================================================\n\n/**\n * Click ID attribution window in milliseconds (90 days)\n *\n * How long click IDs are stored for conversion attribution.\n */\nexport const CLICK_ID_EXPIRY_MS = 90 * 24 * 60 * 60 * 1000\n\n// =============================================================================\n// React Query Stale Times\n// =============================================================================\n\n/**\n * React Query staleTime for frequently-changing data (1 minute)\n *\n * Use for: real-time metrics, live feeds, active sessions\n */\nexport const STALE_TIME_FREQUENT_MS = 60 * 1_000\n\n/**\n * React Query staleTime for moderately-changing data (2 minutes)\n *\n * Use for: subscriptions, user profiles, preferences\n */\nexport const STALE_TIME_MODERATE_MS = 2 * 60 * 1_000\n\n/**\n * React Query staleTime for stable/config data (5 minutes)\n *\n * Use for: plans, feature flags, app config\n */\nexport const STALE_TIME_STABLE_MS = 5 * 60 * 1_000\n\n/**\n * React Query staleTime for webhook stats (30 seconds)\n */\nexport const STALE_TIME_STATS_MS = 30 * 1_000\n\n// =============================================================================\n// UI Component Timeouts\n// =============================================================================\n\n/**\n * Copy-to-clipboard feedback display duration (2 seconds)\n */\nexport const UI_COPY_FEEDBACK_MS = 2_000\n\n/**\n * Form success message display duration (3 seconds)\n */\nexport const UI_FORM_SUCCESS_MS = 3_000\n\n/**\n * General notification display duration (5 seconds)\n */\nexport const UI_NOTIFICATION_MS = 5_000\n\n/**\n * Prompt auto-show delay (3 seconds)\n */\nexport const UI_PROMPT_DELAY_MS = 3_000\n\n/**\n * Redirect delay after action (3 seconds)\n */\nexport const UI_REDIRECT_DELAY_MS = 3_000\n\n/**\n * Animation out duration (200 ms)\n */\nexport const UI_ANIMATION_OUT_MS = 200\n\n/**\n * Animation in duration (300 ms)\n */\nexport const UI_ANIMATION_IN_MS = 300\n\n// =============================================================================\n// Email & Verification\n// =============================================================================\n\n/**\n * Email resend cooldown tick interval (1 second)\n */\nexport const EMAIL_RESEND_COOLDOWN_TICK_MS = 1_000\n\n/**\n * New user detection threshold (1 minute)\n *\n * Users created within this window are considered \"new\" for signup tracking.\n */\nexport const NEW_USER_THRESHOLD_MS = 60 * 1_000\n\n// =============================================================================\n// Web Vitals Thresholds\n// =============================================================================\n\n/**\n * FCP (First Contentful Paint) \"good\" threshold (1800 ms)\n */\nexport const WEB_VITALS_FCP_GOOD_MS = 1_800\n\n/**\n * FCP (First Contentful Paint) \"poor\" threshold (3000 ms)\n */\nexport const WEB_VITALS_FCP_POOR_MS = 3_000\n\n// =============================================================================\n// Storage Sizes\n// =============================================================================\n\n/**\n * Multipart upload threshold (5 MB)\n *\n * Files larger than this use multipart upload for better reliability.\n */\nexport const STORAGE_MULTIPART_THRESHOLD_BYTES = 5 * 1024 * 1024\n\n/**\n * Default max file size for uploads (5 MB)\n */\nexport const STORAGE_DEFAULT_MAX_SIZE_BYTES = 5 * 1024 * 1024\n\n/**\n * Avatar max file size (2 MB)\n */\nexport const STORAGE_AVATAR_MAX_SIZE_BYTES = 2 * 1024 * 1024\n\n/**\n * Large file max size for file uploads (10 MB)\n */\nexport const STORAGE_LARGE_MAX_SIZE_BYTES = 10 * 1024 * 1024\n\n// =============================================================================\n// Cache TTLs\n// =============================================================================\n\n/**\n * JWK cache TTL (1 hour)\n */\nexport const JWK_CACHE_TTL_MS = 60 * 60 * 1000\n\n// =============================================================================\n// Analytics Event Tracking\n// =============================================================================\n\n/**\n * Max tracked event IDs to keep in memory\n */\nexport const ANALYTICS_MAX_TRACKED_EVENT_IDS = 1000\n\n/**\n * Number of event IDs to keep after cleanup\n */\nexport const ANALYTICS_TRACKED_IDS_KEEP = 500\n\n/**\n * Analytics queue limit before force flush\n */\nexport const ANALYTICS_QUEUE_LIMIT = 100\n\n// =============================================================================\n// Session Replay (Extended)\n// =============================================================================\n\n/**\n * Session replay check interval (1 second)\n */\nexport const SESSION_REPLAY_CHECK_INTERVAL_MS = 1_000\n\n/**\n * Success feedback delay for invite/account actions (1.5 seconds)\n */\nexport const UI_SUCCESS_REDIRECT_MS = 1_500\n\n// =============================================================================\n// String Truncation Limits\n// =============================================================================\n\n/**\n * Max message length for logging (1000 chars)\n */\nexport const LOG_MESSAGE_MAX_LENGTH = 1_000\n\n/**\n * Max DOM snapshot length for debugging (1000 chars)\n */\nexport const DOM_SNAPSHOT_MAX_LENGTH = 1_000\n\n/**\n * Max stack trace length for error tracking (500 chars)\n */\nexport const STACK_TRACE_MAX_LENGTH = 500\n\n/**\n * Google Consent Mode wait for update timeout (500 ms)\n */\nexport const CONSENT_WAIT_FOR_UPDATE_MS = 500\n\n// =============================================================================\n// Time Unit Conversions\n// =============================================================================\n\n/** Milliseconds per minute (60,000) */\nexport const MS_PER_MINUTE = 60_000\n\n/** Milliseconds per hour (3,600,000) */\nexport const MS_PER_HOUR = 3_600_000\n\n/** Milliseconds per day (86,400,000) */\nexport const MS_PER_DAY = 86_400_000\n\n/** Seconds per minute (60) */\nexport const SECONDS_PER_MINUTE = 60\n\n/** Seconds per hour (3,600) */\nexport const SECONDS_PER_HOUR = 3_600\n\n// =============================================================================\n// Z-Index Values\n// =============================================================================\n\n/** Z-index for modal overlays (9999) */\nexport const Z_INDEX_OVERLAY = 9999\n\n/** Z-index for critical overlays like feature gates (99999) */\nexport const Z_INDEX_CRITICAL_OVERLAY = 99999\n\n// =============================================================================\n// API Key Expiry (seconds)\n// =============================================================================\n\n/** API key expiry: 1 day (86,400 seconds) */\nexport const API_KEY_EXPIRY_1_DAY = 86_400\n\n/** API key expiry: 7 days (604,800 seconds) */\nexport const API_KEY_EXPIRY_7_DAYS = 604_800\n\n/** API key expiry: 30 days (2,592,000 seconds) */\nexport const API_KEY_EXPIRY_30_DAYS = 2_592_000\n\n/** API key expiry: 90 days (7,776,000 seconds) */\nexport const API_KEY_EXPIRY_90_DAYS = 7_776_000\n\n/** API key expiry: 1 year (31,536,000 seconds) */\nexport const API_KEY_EXPIRY_1_YEAR = 31_536_000\n\n// =============================================================================\n// Web Vitals Thresholds (Google standards)\n// =============================================================================\n\n/** LCP (Largest Contentful Paint) \"good\" threshold (2500 ms) */\nexport const WEB_VITALS_LCP_GOOD_MS = 2_500\n\n/** LCP (Largest Contentful Paint) \"poor\" threshold (4000 ms) */\nexport const WEB_VITALS_LCP_POOR_MS = 4_000\n\n/** INP (Interaction to Next Paint) \"good\" threshold (200 ms) */\nexport const WEB_VITALS_INP_GOOD_MS = 200\n\n/** INP (Interaction to Next Paint) \"poor\" threshold (500 ms) */\nexport const WEB_VITALS_INP_POOR_MS = 500\n\n/** TTFB (Time to First Byte) \"good\" threshold (800 ms) */\nexport const WEB_VITALS_TTFB_GOOD_MS = 800\n\n/** TTFB (Time to First Byte) \"poor\" threshold (1800 ms) */\nexport const WEB_VITALS_TTFB_POOR_MS = 1_800\n\n// =============================================================================\n// Security\n// =============================================================================\n\n/** Minimum password length (NIST SP 800-63B recommends 12+) */\nexport const MIN_PASSWORD_LENGTH = 12\n\n// =============================================================================\n// AI\n// =============================================================================\n\n/** Default context window for AI models (4096 tokens) */\nexport const DEFAULT_CONTEXT_WINDOW = 4_096\n\n// =============================================================================\n// Circuit Breaker (AWS/Resilience4j pattern)\n// =============================================================================\n\n/**\n * Circuit breaker failure threshold\n *\n * Number of failures in the window before circuit opens.\n */\nexport const CIRCUIT_BREAKER_FAILURE_THRESHOLD = 5\n\n/**\n * Circuit breaker failure window in milliseconds (10 seconds)\n *\n * Time window for counting failures.\n */\nexport const CIRCUIT_BREAKER_WINDOW_MS = 10_000\n\n/**\n * Circuit breaker open duration in milliseconds (30 seconds)\n *\n * How long the circuit stays open before allowing a test request.\n */\nexport const CIRCUIT_BREAKER_OPEN_DURATION_MS = 30_000\n\n// =============================================================================\n// ETag Cache (HTTP conditional requests)\n// =============================================================================\n\n/**\n * Maximum ETag cache entries\n *\n * LRU eviction when exceeded.\n */\nexport const ETAG_CACHE_MAX_ENTRIES = 100\n\n/**\n * ETag cache TTL in milliseconds (5 minutes)\n *\n * How long cached responses are valid.\n */\nexport const ETAG_CACHE_TTL_MS = 5 * 60 * 1000\n","/**\n * Web Analytics Tracker\n *\n * Lightweight page-view analytics tracker for Sylphx Platform SDK.\n * Automatically tracks page views, bounce rate, and session data.\n *\n * @example\n * ```typescript\n * import { SDK_API_PATH } from \"../../constants\";\nimport { WebAnalyticsTracker } from '@sylphx/platform-sdk/web-analytics'\n *\n * const tracker = new WebAnalyticsTracker()\n * tracker.init({\n * appKey: 'app_prod_xxx',\n * endpoint: 'https://your-app.com',\n * })\n * ```\n */\n\nimport { SDK_API_PATH } from '../../constants'\n\n// ============================================\n// Types\n// ============================================\n\nexport interface WebAnalyticsOptions {\n\t/** App key for authentication */\n\tappKey: string\n\t/** Base endpoint URL (without trailing slash) */\n\tendpoint: string\n\t/** Auto-track page views (default: true) */\n\ttrackPageViews?: boolean\n\t/** Track bounce rate (sessions with only 1 page view) (default: true) */\n\ttrackBounce?: boolean\n\t/** SPA hash routing mode (tracks hash changes) (default: false) */\n\thashMode?: boolean\n\t/** Debug logging (default: false) */\n\tdebug?: boolean\n}\n\nexport interface PageViewPayload {\n\t/** URL path (e.g. /about) */\n\tpath: string\n\t/** Document referrer */\n\treferrer: string\n\t/** Navigator user agent */\n\tuserAgent: string\n\t/** Screen width in pixels */\n\tscreenWidth: number\n\t/** Session ID (UUID stored in sessionStorage) */\n\tsessionId: string\n\t/** Unix timestamp (ms) */\n\ttimestamp: number\n}\n\nexport interface IdentifyPayload {\n\t/** User ID */\n\tuserId: string\n\t/** User traits/properties */\n\ttraits?: Record<string, unknown>\n\t/** Session ID */\n\tsessionId: string\n}\n\n// ============================================\n// Utilities\n// ============================================\n\nfunction generateSessionId(): string {\n\tif (typeof crypto !== 'undefined' && crypto.randomUUID) {\n\t\treturn crypto.randomUUID()\n\t}\n\t// Fallback for older environments\n\treturn 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n\t\tconst r = (Math.random() * 16) | 0\n\t\tconst v = c === 'x' ? r : (r & 0x3) | 0x8\n\t\treturn v.toString(16)\n\t})\n}\n\nfunction getOrCreateSessionId(): string {\n\tif (typeof sessionStorage === 'undefined') return generateSessionId()\n\tconst key = '_sylphx_sid'\n\tlet id = sessionStorage.getItem(key)\n\tif (!id) {\n\t\tid = generateSessionId()\n\t\tsessionStorage.setItem(key, id)\n\t}\n\treturn id\n}\n\nfunction getCurrentPath(hashMode: boolean): string {\n\tif (typeof window === 'undefined' || !window.location) return '/'\n\tif (hashMode) {\n\t\treturn window.location.hash.replace(/^#/, '') || '/'\n\t}\n\treturn window.location.pathname + window.location.search\n}\n\n// ============================================\n// WebAnalyticsTracker\n// ============================================\n\nexport class WebAnalyticsTracker {\n\tprivate options: Required<WebAnalyticsOptions> | null = null\n\tprivate initialized = false\n\tprivate lastPath: string | null = null\n\tprivate pageViewCount = 0\n\tprivate cleanupFns: Array<() => void> = []\n\n\t/**\n\t * Initialize the tracker and start auto-tracking page views\n\t */\n\tinit(options: WebAnalyticsOptions): void {\n\t\tif (typeof window === 'undefined') return\n\t\tif (this.initialized) return\n\n\t\tthis.options = {\n\t\t\ttrackPageViews: true,\n\t\t\ttrackBounce: true,\n\t\t\thashMode: false,\n\t\t\tdebug: false,\n\t\t\t...options,\n\t\t}\n\n\t\tthis.initialized = true\n\n\t\tif (this.options.trackPageViews) {\n\t\t\t// Track current page view\n\t\t\tthis.trackPageView()\n\n\t\t\t// Track Next.js router events (soft navigation)\n\t\t\tthis._hookNextRouter()\n\n\t\t\t// Track hash changes (for hash-mode SPAs)\n\t\t\tif (this.options.hashMode) {\n\t\t\t\tconst onHashChange = () => this.trackPageView()\n\t\t\t\twindow.addEventListener('hashchange', onHashChange)\n\t\t\t\tthis.cleanupFns.push(() => window.removeEventListener('hashchange', onHashChange))\n\t\t\t}\n\n\t\t\t// Track History API (pushState / replaceState)\n\t\t\tthis._hookHistoryApi()\n\t\t}\n\n\t\tif (this.options.debug) {\n\t\t\tconsole.log('[WebAnalytics] Initialized', this.options)\n\t\t}\n\t}\n\n\t/**\n\t * Manually track a page view\n\t */\n\ttrackPageView(path?: string): void {\n\t\tif (!this.options) return\n\t\tif (typeof window === 'undefined') return\n\n\t\tconst currentPath = path ?? getCurrentPath(this.options.hashMode)\n\n\t\t// Avoid duplicate tracking on same path\n\t\tif (currentPath === this.lastPath) return\n\t\tthis.lastPath = currentPath\n\t\tthis.pageViewCount++\n\n\t\tconst payload: PageViewPayload = {\n\t\t\tpath: currentPath,\n\t\t\treferrer: typeof document !== 'undefined' ? document.referrer || '' : '',\n\t\t\tuserAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',\n\t\t\tscreenWidth: window.screen?.width,\n\t\t\tsessionId: getOrCreateSessionId(),\n\t\t\ttimestamp: Date.now(),\n\t\t}\n\n\t\tif (this.options.debug) {\n\t\t\tconsole.log('[WebAnalytics] Page view:', payload)\n\t\t}\n\n\t\tthis._send(`${SDK_API_PATH}/analytics/pageview`, payload)\n\t}\n\n\t/**\n\t * Identify a user\n\t */\n\tidentify(userId: string, traits?: Record<string, unknown>): void {\n\t\tif (!this.options) return\n\t\tif (typeof window === 'undefined') return\n\n\t\tconst payload: IdentifyPayload = {\n\t\t\tuserId,\n\t\t\ttraits,\n\t\t\tsessionId: getOrCreateSessionId(),\n\t\t}\n\n\t\tif (this.options.debug) {\n\t\t\tconsole.log('[WebAnalytics] Identify:', payload)\n\t\t}\n\n\t\tthis._send(`${SDK_API_PATH}/analytics/identify`, payload)\n\t}\n\n\t/**\n\t * Destroy the tracker and remove event listeners\n\t */\n\tdestroy(): void {\n\t\tfor (const fn of this.cleanupFns) {\n\t\t\tfn()\n\t\t}\n\t\tthis.cleanupFns = []\n\t\tthis.initialized = false\n\t\tthis.options = null\n\t\tthis.lastPath = null\n\t\tthis.pageViewCount = 0\n\t}\n\n\t// ==========================================\n\t// Private helpers\n\t// ==========================================\n\n\tprivate _send(path: string, payload: unknown): void {\n\t\tif (!this.options) return\n\n\t\tconst url = `${this.options.endpoint}${path}`\n\t\tconst data = JSON.stringify(payload)\n\n\t\ttry {\n\t\t\tif (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n\t\t\t\tconst blob = new Blob([data], { type: 'application/json' })\n\t\t\t\tconst sent = navigator.sendBeacon(url, blob)\n\t\t\t\tif (!sent && this.options.debug) {\n\t\t\t\t\tconsole.warn('[WebAnalytics] sendBeacon failed, falling back to fetch')\n\t\t\t\t}\n\t\t\t\tif (sent) return\n\t\t\t}\n\n\t\t\t// Fallback: fetch with keepalive\n\t\t\tfetch(url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t'x-app-key': this.options.appKey,\n\t\t\t\t},\n\t\t\t\tbody: data,\n\t\t\t\tkeepalive: true,\n\t\t\t}).catch((err) => {\n\t\t\t\tif (this.options?.debug) {\n\t\t\t\t\tconsole.error('[WebAnalytics] Failed to send:', err)\n\t\t\t\t}\n\t\t\t})\n\t\t} catch (err) {\n\t\t\tif (this.options?.debug) {\n\t\t\t\tconsole.error('[WebAnalytics] Send error:', err)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _hookHistoryApi(): void {\n\t\tif (typeof window === 'undefined') return\n\n\t\tconst originalPush = window.history.pushState.bind(window.history)\n\t\tconst originalReplace = window.history.replaceState.bind(window.history)\n\n\t\twindow.history.pushState = (...args) => {\n\t\t\toriginalPush(...args)\n\t\t\t// Small delay to allow URL to update\n\t\t\tsetTimeout(() => this.trackPageView(), 0)\n\t\t}\n\n\t\twindow.history.replaceState = (...args) => {\n\t\t\toriginalReplace(...args)\n\t\t\t// Don't track replaceState (usually internal SPA navigation)\n\t\t}\n\n\t\tconst onPopState = () => setTimeout(() => this.trackPageView(), 0)\n\t\twindow.addEventListener('popstate', onPopState)\n\n\t\tthis.cleanupFns.push(() => {\n\t\t\twindow.history.pushState = originalPush\n\t\t\twindow.history.replaceState = originalReplace\n\t\t\twindow.removeEventListener('popstate', onPopState)\n\t\t})\n\t}\n\n\tprivate _hookNextRouter(): void {\n\t\t// Next.js App Router: listen to route-change events dispatched on document\n\t\tif (typeof document === 'undefined') return\n\n\t\t// Next.js 13+ soft navigation events\n\t\tconst onRouteAnnounced = () => setTimeout(() => this.trackPageView(), 0)\n\t\tdocument.addEventListener('nextjs:route-announced', onRouteAnnounced as EventListener)\n\n\t\tthis.cleanupFns.push(() => {\n\t\t\tdocument.removeEventListener('nextjs:route-announced', onRouteAnnounced as EventListener)\n\t\t})\n\t}\n}\n\n// ============================================\n// Singleton instance\n// ============================================\n\nlet _tracker: WebAnalyticsTracker | null = null\n\n/**\n * Get or create the global WebAnalyticsTracker singleton\n */\nexport function getWebAnalyticsTracker(): WebAnalyticsTracker {\n\tif (!_tracker) {\n\t\t_tracker = new WebAnalyticsTracker()\n\t}\n\treturn _tracker\n}\n\n/**\n * Initialize global web analytics tracker\n */\nexport function initWebAnalytics(options: WebAnalyticsOptions): void {\n\tgetWebAnalyticsTracker().init(options)\n}\n"],"mappings":"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","names":[]}
|
package/dist/web-analytics.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/constants.ts
|
|
2
|
-
var SDK_API_PATH = `/api/
|
|
2
|
+
var SDK_API_PATH = `/api/v1`;
|
|
3
3
|
var SDK_PLATFORM = typeof window !== "undefined" ? "browser" : typeof process !== "undefined" && process.versions?.node ? "node" : "unknown";
|
|
4
4
|
var SESSION_TOKEN_LIFETIME_SECONDS = 5 * 60;
|
|
5
5
|
var SESSION_TOKEN_LIFETIME_MS = SESSION_TOKEN_LIFETIME_SECONDS * 1e3;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/constants.ts","../src/lib/analytics/web-analytics.ts"],"sourcesContent":["/**\n * SDK Constants — Single Source of Truth\n *\n * Shared constants used across the SDK. Centralizing these\n * prevents magic number duplication and makes changes easier.\n *\n * IMPORTANT: All time-based constants should be used consistently\n * across the SDK. Never hardcode magic numbers like 30000, 5 * 60 * 1000, etc.\n */\n\n// =============================================================================\n// API Configuration\n// =============================================================================\n\n/** Default platform URL */\nexport const DEFAULT_PLATFORM_URL = 'https://sylphx.com'\n\n/**\n * Canonical environment variable name for the platform URL.\n *\n * SDK modules MUST read from this env var (with SYLPHX_URL as legacy fallback).\n * Centralizing the name prevents the same env var being spelled differently\n * across kv.ts, streams.ts, ai.ts, middleware.ts, and server.ts.\n */\nexport const ENV_PLATFORM_URL = 'SYLPHX_PLATFORM_URL'\n\n/**\n * Legacy environment variable name for the platform URL.\n *\n * Supported as a fallback for older deployments that set SYLPHX_URL.\n * New projects should use SYLPHX_PLATFORM_URL.\n */\nexport const ENV_PLATFORM_URL_LEGACY = 'SYLPHX_URL'\n\n/**\n * Canonical environment variable name for the secret key.\n *\n * All server-side SDK modules read from this env var by default.\n */\nexport const ENV_SECRET_KEY = 'SYLPHX_SECRET_KEY'\n\n/**\n * Resolve the platform URL from environment variables.\n *\n * Priority: explicit value > SYLPHX_PLATFORM_URL > SYLPHX_URL (legacy) > default\n */\nexport function resolvePlatformUrl(explicit?: string): string {\n\treturn (\n\t\texplicit ||\n\t\tprocess.env[ENV_PLATFORM_URL] ||\n\t\tprocess.env[ENV_PLATFORM_URL_LEGACY] ||\n\t\tDEFAULT_PLATFORM_URL\n\t).trim()\n}\n\n/**\n * Resolve the secret key from environment variables.\n *\n * Returns the raw value before validation. Callers should pass the result\n * through `validateAndSanitizeSecretKey()`.\n */\nexport function resolveSecretKey(explicit?: string): string | undefined {\n\treturn explicit || process.env[ENV_SECRET_KEY]\n}\n\n/** @deprecated No longer used for path construction. Use SDK_API_PATH directly. */\nexport const SDK_API_VERSION = 'v1'\n\n/**\n * SDK API base path — legacy path served from main Next.js app.\n *\n * Used when `platformUrl` is set to `https://sylphx.com` (default).\n * Points to the runtime Hono app mounted at /api/app/v1 (via Next.js catch-all).\n *\n * @deprecated Prefer project `ref`-based URL: https://{ref}.api.sylphx.com/v1\n */\nexport const SDK_API_PATH = `/api/app/v1`\n\n/**\n * SDK API path for new subdomain-based SDK server.\n *\n * Used when `ref` is provided to `createConfig`.\n * The full base becomes: https://{ref}.api.sylphx.com + SDK_API_PATH_NEW\n */\nexport const SDK_API_PATH_NEW = `/v1`\n\n/**\n * Default SDK API host for new subdomain-based SDK server.\n * The full URL is built as: https://{ref}.api.sylphx.com/v1\n */\nexport const DEFAULT_SDK_API_HOST = 'api.sylphx.com'\n\n/**\n * Default auth route prefix\n *\n * Used for OAuth callbacks and signout routes.\n * Must match the middleware's authPrefix config.\n */\nexport const DEFAULT_AUTH_PREFIX = '/auth'\n\n/**\n * SDK package version\n *\n * Sent in X-SDK-Version header for debugging and analytics.\n * Update this when releasing new SDK versions.\n */\nexport const SDK_VERSION = '0.1.0'\n\n/**\n * SDK platform identifier\n *\n * Sent in X-SDK-Platform header to identify the runtime environment.\n */\nexport const SDK_PLATFORM =\n\ttypeof window !== 'undefined'\n\t\t? 'browser'\n\t\t: typeof process !== 'undefined' && process.versions?.node\n\t\t\t? 'node'\n\t\t\t: 'unknown'\n\n// =============================================================================\n// Timeouts & Durations\n// =============================================================================\n\n/** Default request timeout in milliseconds (30 seconds) */\nexport const DEFAULT_TIMEOUT_MS = 30_000\n\n/**\n * Token expiry buffer in milliseconds (30 seconds)\n *\n * Refresh tokens this many milliseconds BEFORE they expire\n * to account for network latency and clock skew.\n */\nexport const TOKEN_EXPIRY_BUFFER_MS = 30_000\n\n/**\n * Session token lifetime in seconds (5 minutes)\n *\n * Matches Clerk's short-lived access token pattern.\n * Used for cookie maxAge and React Query staleTime.\n */\nexport const SESSION_TOKEN_LIFETIME_SECONDS = 5 * 60\n\n/** Session token lifetime in milliseconds (for React Query staleTime) */\nexport const SESSION_TOKEN_LIFETIME_MS = SESSION_TOKEN_LIFETIME_SECONDS * 1000\n\n/**\n * Refresh token lifetime in seconds (30 days)\n *\n * Long-lived token for silent refresh.\n */\nexport const REFRESH_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60\n\n// =============================================================================\n// Feature Flags Cache\n// =============================================================================\n\n/**\n * Feature flags cache TTL in milliseconds (5 minutes)\n *\n * How long to cache flags before fetching fresh values.\n * Matches LaunchDarkly's default streaming connection behavior.\n */\nexport const FLAGS_CACHE_TTL_MS = 5 * 60 * 1000\n\n/**\n * Feature flags stale-while-revalidate window in milliseconds (1 minute)\n *\n * Allow serving stale flags while fetching fresh values.\n */\nexport const FLAGS_STALE_WHILE_REVALIDATE_MS = 60 * 1000\n\n// =============================================================================\n// Retry & Backoff\n// =============================================================================\n\n/** Maximum retry delay for exponential backoff (30 seconds) */\nexport const MAX_RETRY_DELAY_MS = 30_000\n\n/** Base retry delay for exponential backoff (1 second) */\nexport const BASE_RETRY_DELAY_MS = 1_000\n\n/** Maximum number of retries for network requests */\nexport const MAX_RETRIES = 3\n\n// =============================================================================\n// Analytics\n// =============================================================================\n\n/**\n * Analytics session timeout in milliseconds (30 minutes)\n *\n * After this much inactivity, a new session is started.\n */\nexport const ANALYTICS_SESSION_TIMEOUT_MS = 30 * 60 * 1000\n\n// =============================================================================\n// Webhooks\n// =============================================================================\n\n/**\n * Maximum age for webhook signature validation (5 minutes)\n *\n * Reject webhooks with timestamps older than this.\n */\nexport const WEBHOOK_MAX_AGE_MS = 5 * 60 * 1000\n\n/**\n * Clock skew allowance for webhook validation (30 seconds)\n *\n * Allow timestamps this far in the future.\n */\nexport const WEBHOOK_CLOCK_SKEW_MS = 30 * 1000\n\n// =============================================================================\n// PKCE (OAuth)\n// =============================================================================\n\n/**\n * PKCE code verifier TTL in milliseconds (10 minutes)\n *\n * How long the code verifier is stored during OAuth flow.\n */\nexport const PKCE_CODE_TTL_MS = 10 * 60 * 1000\n\n// =============================================================================\n// Jobs\n// =============================================================================\n\n/**\n * Job dead-letter queue retention in milliseconds (7 days)\n */\nexport const JOBS_DLQ_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000\n\n// =============================================================================\n// Session Replay\n// =============================================================================\n\n/**\n * Maximum session replay recording duration in milliseconds (60 minutes)\n */\nexport const SESSION_REPLAY_MAX_DURATION_MS = 60 * 60 * 1000\n\n/**\n * Session replay upload interval in milliseconds (5 seconds)\n */\nexport const SESSION_REPLAY_UPLOAD_INTERVAL_MS = 5_000\n\n/**\n * Session replay scroll event throttle interval (150 ms)\n */\nexport const SESSION_REPLAY_SCROLL_THROTTLE_MS = 150\n\n/**\n * Session replay media time update throttle interval (800 ms)\n */\nexport const SESSION_REPLAY_MEDIA_THROTTLE_MS = 800\n\n/**\n * Session replay rage click detection window (1 second)\n */\nexport const SESSION_REPLAY_RAGE_CLICK_WINDOW_MS = 1_000\n\n/**\n * Session replay dead click detection timeout (500 ms)\n */\nexport const SESSION_REPLAY_DEAD_CLICK_TIMEOUT_MS = 500\n\n/**\n * Session replay scroll heat detection window (2 seconds)\n */\nexport const SESSION_REPLAY_SCROLL_HEAT_WINDOW_MS = 2_000\n\n/**\n * Session replay status check interval (5 seconds)\n */\nexport const SESSION_REPLAY_STATUS_CHECK_MS = 5_000\n\n// =============================================================================\n// Analytics (Extended)\n// =============================================================================\n\n/**\n * Analytics event flush interval in milliseconds (5 seconds)\n */\nexport const ANALYTICS_FLUSH_INTERVAL_MS = 5_000\n\n/**\n * Analytics maximum text length for autocapture (100 characters)\n */\nexport const ANALYTICS_MAX_TEXT_LENGTH = 100\n\n/**\n * Analytics flush timeout in milliseconds (1 second)\n */\nexport const ANALYTICS_FLUSH_TIMEOUT_MS = 1_000\n\n/**\n * Analytics interval check in milliseconds (1 second)\n */\nexport const ANALYTICS_INTERVAL_CHECK_MS = 1_000\n\n/**\n * Analytics retry base delay in milliseconds (1 second)\n * Exponential backoff: delay = base * 2^retries (with jitter)\n */\nexport const ANALYTICS_RETRY_BASE_DELAY_MS = 1_000\n\n/**\n * Analytics retry max delay in milliseconds (30 seconds)\n */\nexport const ANALYTICS_RETRY_MAX_DELAY_MS = 30_000\n\n/**\n * Analytics retry jitter factor (±20%)\n * Prevents thundering herd when multiple clients retry simultaneously\n */\nexport const ANALYTICS_RETRY_JITTER = 0.2\n\n/**\n * Analytics maximum retries before dropping event (Segment pattern: 10)\n */\nexport const ANALYTICS_MAX_RETRIES = 10\n\n// =============================================================================\n// Feature Flags (Extended)\n// =============================================================================\n\n/**\n * Feature flags exposure deduplication window (1 hour)\n *\n * Prevents duplicate exposure events for A/B tests within this window.\n */\nexport const FLAGS_EXPOSURE_DEDUPE_WINDOW_MS = 60 * 60 * 1000\n\n/**\n * Flag stream initial reconnection delay (1 second)\n */\nexport const FLAGS_STREAM_INITIAL_RECONNECT_MS = 1_000\n\n/**\n * Flag stream maximum reconnection delay (30 seconds)\n */\nexport const FLAGS_STREAM_MAX_RECONNECT_MS = 30_000\n\n/**\n * Flag stream heartbeat timeout (45 seconds)\n */\nexport const FLAGS_STREAM_HEARTBEAT_TIMEOUT_MS = 45_000\n\n/**\n * Flag HTTP polling interval fallback (60 seconds)\n */\nexport const FLAGS_HTTP_POLLING_INTERVAL_MS = 60_000\n\n// =============================================================================\n// Jobs (Extended)\n// =============================================================================\n\n/**\n * Default retry delay sequence for exponential backoff (ms)\n */\nexport const DEFAULT_RETRY_DELAYS_MS = [1_000, 5_000, 15_000, 30_000, 60_000] as const\n\n/**\n * Default job timeout in milliseconds (60 seconds)\n */\nexport const JOB_DEFAULT_TIMEOUT_MS = 60_000\n\n/**\n * Default job status polling interval (2 seconds)\n */\nexport const JOB_POLL_INTERVAL_MS = 2_000\n\n// =============================================================================\n// Storage Keys & Prefixes\n// =============================================================================\n\n/**\n * Storage key prefix for SDK data\n */\nexport const STORAGE_KEY_PREFIX = 'sylphx_'\n\n/**\n * localStorage key for cached feature flags\n */\nexport const FLAGS_CACHE_KEY = 'sylphx_feature_flags'\n\n/**\n * localStorage key for feature flags cache timestamp\n */\nexport const FLAGS_CACHE_TIMESTAMP_KEY = 'sylphx_feature_flags_ts'\n\n/**\n * localStorage key for feature flags overrides\n */\nexport const FLAGS_OVERRIDES_KEY = 'sylphx_feature_flags_overrides'\n\n/**\n * localStorage key for active organization\n */\nexport const ORG_STORAGE_KEY = 'sylphx_active_org'\n\n/**\n * BroadcastChannel name for cross-tab org sync\n */\nexport const ORG_BROADCAST_CHANNEL = 'sylphx_org_sync'\n\n/**\n * Storage prefix for PKCE verifiers\n */\nexport const PKCE_STORAGE_PREFIX = 'sylphx_pkce_'\n\n/**\n * Test key for checking storage availability\n */\nexport const STORAGE_TEST_KEY = '__sylphx_test__'\n\n/**\n * Cookie/storage name for analytics sessions\n */\nexport const ANALYTICS_SESSION_KEY = 'sylphx_session'\n\n/**\n * Default storage key for flags persistence\n */\nexport const FLAGS_STORAGE_KEY = 'sylphx_flags'\n\n// =============================================================================\n// Click ID & Attribution\n// =============================================================================\n\n/**\n * Click ID attribution window in milliseconds (90 days)\n *\n * How long click IDs are stored for conversion attribution.\n */\nexport const CLICK_ID_EXPIRY_MS = 90 * 24 * 60 * 60 * 1000\n\n// =============================================================================\n// React Query Stale Times\n// =============================================================================\n\n/**\n * React Query staleTime for frequently-changing data (1 minute)\n *\n * Use for: real-time metrics, live feeds, active sessions\n */\nexport const STALE_TIME_FREQUENT_MS = 60 * 1_000\n\n/**\n * React Query staleTime for moderately-changing data (2 minutes)\n *\n * Use for: subscriptions, user profiles, preferences\n */\nexport const STALE_TIME_MODERATE_MS = 2 * 60 * 1_000\n\n/**\n * React Query staleTime for stable/config data (5 minutes)\n *\n * Use for: plans, feature flags, app config\n */\nexport const STALE_TIME_STABLE_MS = 5 * 60 * 1_000\n\n/**\n * React Query staleTime for webhook stats (30 seconds)\n */\nexport const STALE_TIME_STATS_MS = 30 * 1_000\n\n// =============================================================================\n// UI Component Timeouts\n// =============================================================================\n\n/**\n * Copy-to-clipboard feedback display duration (2 seconds)\n */\nexport const UI_COPY_FEEDBACK_MS = 2_000\n\n/**\n * Form success message display duration (3 seconds)\n */\nexport const UI_FORM_SUCCESS_MS = 3_000\n\n/**\n * General notification display duration (5 seconds)\n */\nexport const UI_NOTIFICATION_MS = 5_000\n\n/**\n * Prompt auto-show delay (3 seconds)\n */\nexport const UI_PROMPT_DELAY_MS = 3_000\n\n/**\n * Redirect delay after action (3 seconds)\n */\nexport const UI_REDIRECT_DELAY_MS = 3_000\n\n/**\n * Animation out duration (200 ms)\n */\nexport const UI_ANIMATION_OUT_MS = 200\n\n/**\n * Animation in duration (300 ms)\n */\nexport const UI_ANIMATION_IN_MS = 300\n\n// =============================================================================\n// Email & Verification\n// =============================================================================\n\n/**\n * Email resend cooldown tick interval (1 second)\n */\nexport const EMAIL_RESEND_COOLDOWN_TICK_MS = 1_000\n\n/**\n * New user detection threshold (1 minute)\n *\n * Users created within this window are considered \"new\" for signup tracking.\n */\nexport const NEW_USER_THRESHOLD_MS = 60 * 1_000\n\n// =============================================================================\n// Web Vitals Thresholds\n// =============================================================================\n\n/**\n * FCP (First Contentful Paint) \"good\" threshold (1800 ms)\n */\nexport const WEB_VITALS_FCP_GOOD_MS = 1_800\n\n/**\n * FCP (First Contentful Paint) \"poor\" threshold (3000 ms)\n */\nexport const WEB_VITALS_FCP_POOR_MS = 3_000\n\n// =============================================================================\n// Storage Sizes\n// =============================================================================\n\n/**\n * Multipart upload threshold (5 MB)\n *\n * Files larger than this use multipart upload for better reliability.\n */\nexport const STORAGE_MULTIPART_THRESHOLD_BYTES = 5 * 1024 * 1024\n\n/**\n * Default max file size for uploads (5 MB)\n */\nexport const STORAGE_DEFAULT_MAX_SIZE_BYTES = 5 * 1024 * 1024\n\n/**\n * Avatar max file size (2 MB)\n */\nexport const STORAGE_AVATAR_MAX_SIZE_BYTES = 2 * 1024 * 1024\n\n/**\n * Large file max size for file uploads (10 MB)\n */\nexport const STORAGE_LARGE_MAX_SIZE_BYTES = 10 * 1024 * 1024\n\n// =============================================================================\n// Cache TTLs\n// =============================================================================\n\n/**\n * JWK cache TTL (1 hour)\n */\nexport const JWK_CACHE_TTL_MS = 60 * 60 * 1000\n\n// =============================================================================\n// Analytics Event Tracking\n// =============================================================================\n\n/**\n * Max tracked event IDs to keep in memory\n */\nexport const ANALYTICS_MAX_TRACKED_EVENT_IDS = 1000\n\n/**\n * Number of event IDs to keep after cleanup\n */\nexport const ANALYTICS_TRACKED_IDS_KEEP = 500\n\n/**\n * Analytics queue limit before force flush\n */\nexport const ANALYTICS_QUEUE_LIMIT = 100\n\n// =============================================================================\n// Session Replay (Extended)\n// =============================================================================\n\n/**\n * Session replay check interval (1 second)\n */\nexport const SESSION_REPLAY_CHECK_INTERVAL_MS = 1_000\n\n/**\n * Success feedback delay for invite/account actions (1.5 seconds)\n */\nexport const UI_SUCCESS_REDIRECT_MS = 1_500\n\n// =============================================================================\n// String Truncation Limits\n// =============================================================================\n\n/**\n * Max message length for logging (1000 chars)\n */\nexport const LOG_MESSAGE_MAX_LENGTH = 1_000\n\n/**\n * Max DOM snapshot length for debugging (1000 chars)\n */\nexport const DOM_SNAPSHOT_MAX_LENGTH = 1_000\n\n/**\n * Max stack trace length for error tracking (500 chars)\n */\nexport const STACK_TRACE_MAX_LENGTH = 500\n\n/**\n * Google Consent Mode wait for update timeout (500 ms)\n */\nexport const CONSENT_WAIT_FOR_UPDATE_MS = 500\n\n// =============================================================================\n// Time Unit Conversions\n// =============================================================================\n\n/** Milliseconds per minute (60,000) */\nexport const MS_PER_MINUTE = 60_000\n\n/** Milliseconds per hour (3,600,000) */\nexport const MS_PER_HOUR = 3_600_000\n\n/** Milliseconds per day (86,400,000) */\nexport const MS_PER_DAY = 86_400_000\n\n/** Seconds per minute (60) */\nexport const SECONDS_PER_MINUTE = 60\n\n/** Seconds per hour (3,600) */\nexport const SECONDS_PER_HOUR = 3_600\n\n// =============================================================================\n// Z-Index Values\n// =============================================================================\n\n/** Z-index for modal overlays (9999) */\nexport const Z_INDEX_OVERLAY = 9999\n\n/** Z-index for critical overlays like feature gates (99999) */\nexport const Z_INDEX_CRITICAL_OVERLAY = 99999\n\n// =============================================================================\n// API Key Expiry (seconds)\n// =============================================================================\n\n/** API key expiry: 1 day (86,400 seconds) */\nexport const API_KEY_EXPIRY_1_DAY = 86_400\n\n/** API key expiry: 7 days (604,800 seconds) */\nexport const API_KEY_EXPIRY_7_DAYS = 604_800\n\n/** API key expiry: 30 days (2,592,000 seconds) */\nexport const API_KEY_EXPIRY_30_DAYS = 2_592_000\n\n/** API key expiry: 90 days (7,776,000 seconds) */\nexport const API_KEY_EXPIRY_90_DAYS = 7_776_000\n\n/** API key expiry: 1 year (31,536,000 seconds) */\nexport const API_KEY_EXPIRY_1_YEAR = 31_536_000\n\n// =============================================================================\n// Web Vitals Thresholds (Google standards)\n// =============================================================================\n\n/** LCP (Largest Contentful Paint) \"good\" threshold (2500 ms) */\nexport const WEB_VITALS_LCP_GOOD_MS = 2_500\n\n/** LCP (Largest Contentful Paint) \"poor\" threshold (4000 ms) */\nexport const WEB_VITALS_LCP_POOR_MS = 4_000\n\n/** INP (Interaction to Next Paint) \"good\" threshold (200 ms) */\nexport const WEB_VITALS_INP_GOOD_MS = 200\n\n/** INP (Interaction to Next Paint) \"poor\" threshold (500 ms) */\nexport const WEB_VITALS_INP_POOR_MS = 500\n\n/** TTFB (Time to First Byte) \"good\" threshold (800 ms) */\nexport const WEB_VITALS_TTFB_GOOD_MS = 800\n\n/** TTFB (Time to First Byte) \"poor\" threshold (1800 ms) */\nexport const WEB_VITALS_TTFB_POOR_MS = 1_800\n\n// =============================================================================\n// Security\n// =============================================================================\n\n/** Minimum password length (NIST SP 800-63B recommends 12+) */\nexport const MIN_PASSWORD_LENGTH = 12\n\n// =============================================================================\n// AI\n// =============================================================================\n\n/** Default context window for AI models (4096 tokens) */\nexport const DEFAULT_CONTEXT_WINDOW = 4_096\n\n// =============================================================================\n// Circuit Breaker (AWS/Resilience4j pattern)\n// =============================================================================\n\n/**\n * Circuit breaker failure threshold\n *\n * Number of failures in the window before circuit opens.\n */\nexport const CIRCUIT_BREAKER_FAILURE_THRESHOLD = 5\n\n/**\n * Circuit breaker failure window in milliseconds (10 seconds)\n *\n * Time window for counting failures.\n */\nexport const CIRCUIT_BREAKER_WINDOW_MS = 10_000\n\n/**\n * Circuit breaker open duration in milliseconds (30 seconds)\n *\n * How long the circuit stays open before allowing a test request.\n */\nexport const CIRCUIT_BREAKER_OPEN_DURATION_MS = 30_000\n\n// =============================================================================\n// ETag Cache (HTTP conditional requests)\n// =============================================================================\n\n/**\n * Maximum ETag cache entries\n *\n * LRU eviction when exceeded.\n */\nexport const ETAG_CACHE_MAX_ENTRIES = 100\n\n/**\n * ETag cache TTL in milliseconds (5 minutes)\n *\n * How long cached responses are valid.\n */\nexport const ETAG_CACHE_TTL_MS = 5 * 60 * 1000\n","/**\n * Web Analytics Tracker\n *\n * Lightweight page-view analytics tracker for Sylphx Platform SDK.\n * Automatically tracks page views, bounce rate, and session data.\n *\n * @example\n * ```typescript\n * import { SDK_API_PATH } from \"../../constants\";\nimport { WebAnalyticsTracker } from '@sylphx/platform-sdk/web-analytics'\n *\n * const tracker = new WebAnalyticsTracker()\n * tracker.init({\n * appKey: 'app_prod_xxx',\n * endpoint: 'https://your-app.com',\n * })\n * ```\n */\n\nimport { SDK_API_PATH } from '../../constants'\n\n// ============================================\n// Types\n// ============================================\n\nexport interface WebAnalyticsOptions {\n\t/** App key for authentication */\n\tappKey: string\n\t/** Base endpoint URL (without trailing slash) */\n\tendpoint: string\n\t/** Auto-track page views (default: true) */\n\ttrackPageViews?: boolean\n\t/** Track bounce rate (sessions with only 1 page view) (default: true) */\n\ttrackBounce?: boolean\n\t/** SPA hash routing mode (tracks hash changes) (default: false) */\n\thashMode?: boolean\n\t/** Debug logging (default: false) */\n\tdebug?: boolean\n}\n\nexport interface PageViewPayload {\n\t/** URL path (e.g. /about) */\n\tpath: string\n\t/** Document referrer */\n\treferrer: string\n\t/** Navigator user agent */\n\tuserAgent: string\n\t/** Screen width in pixels */\n\tscreenWidth: number\n\t/** Session ID (UUID stored in sessionStorage) */\n\tsessionId: string\n\t/** Unix timestamp (ms) */\n\ttimestamp: number\n}\n\nexport interface IdentifyPayload {\n\t/** User ID */\n\tuserId: string\n\t/** User traits/properties */\n\ttraits?: Record<string, unknown>\n\t/** Session ID */\n\tsessionId: string\n}\n\n// ============================================\n// Utilities\n// ============================================\n\nfunction generateSessionId(): string {\n\tif (typeof crypto !== 'undefined' && crypto.randomUUID) {\n\t\treturn crypto.randomUUID()\n\t}\n\t// Fallback for older environments\n\treturn 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n\t\tconst r = (Math.random() * 16) | 0\n\t\tconst v = c === 'x' ? r : (r & 0x3) | 0x8\n\t\treturn v.toString(16)\n\t})\n}\n\nfunction getOrCreateSessionId(): string {\n\tif (typeof sessionStorage === 'undefined') return generateSessionId()\n\tconst key = '_sylphx_sid'\n\tlet id = sessionStorage.getItem(key)\n\tif (!id) {\n\t\tid = generateSessionId()\n\t\tsessionStorage.setItem(key, id)\n\t}\n\treturn id\n}\n\nfunction getCurrentPath(hashMode: boolean): string {\n\tif (typeof window === 'undefined' || !window.location) return '/'\n\tif (hashMode) {\n\t\treturn window.location.hash.replace(/^#/, '') || '/'\n\t}\n\treturn window.location.pathname + window.location.search\n}\n\n// ============================================\n// WebAnalyticsTracker\n// ============================================\n\nexport class WebAnalyticsTracker {\n\tprivate options: Required<WebAnalyticsOptions> | null = null\n\tprivate initialized = false\n\tprivate lastPath: string | null = null\n\tprivate pageViewCount = 0\n\tprivate cleanupFns: Array<() => void> = []\n\n\t/**\n\t * Initialize the tracker and start auto-tracking page views\n\t */\n\tinit(options: WebAnalyticsOptions): void {\n\t\tif (typeof window === 'undefined') return\n\t\tif (this.initialized) return\n\n\t\tthis.options = {\n\t\t\ttrackPageViews: true,\n\t\t\ttrackBounce: true,\n\t\t\thashMode: false,\n\t\t\tdebug: false,\n\t\t\t...options,\n\t\t}\n\n\t\tthis.initialized = true\n\n\t\tif (this.options.trackPageViews) {\n\t\t\t// Track current page view\n\t\t\tthis.trackPageView()\n\n\t\t\t// Track Next.js router events (soft navigation)\n\t\t\tthis._hookNextRouter()\n\n\t\t\t// Track hash changes (for hash-mode SPAs)\n\t\t\tif (this.options.hashMode) {\n\t\t\t\tconst onHashChange = () => this.trackPageView()\n\t\t\t\twindow.addEventListener('hashchange', onHashChange)\n\t\t\t\tthis.cleanupFns.push(() => window.removeEventListener('hashchange', onHashChange))\n\t\t\t}\n\n\t\t\t// Track History API (pushState / replaceState)\n\t\t\tthis._hookHistoryApi()\n\t\t}\n\n\t\tif (this.options.debug) {\n\t\t\tconsole.log('[WebAnalytics] Initialized', this.options)\n\t\t}\n\t}\n\n\t/**\n\t * Manually track a page view\n\t */\n\ttrackPageView(path?: string): void {\n\t\tif (!this.options) return\n\t\tif (typeof window === 'undefined') return\n\n\t\tconst currentPath = path ?? getCurrentPath(this.options.hashMode)\n\n\t\t// Avoid duplicate tracking on same path\n\t\tif (currentPath === this.lastPath) return\n\t\tthis.lastPath = currentPath\n\t\tthis.pageViewCount++\n\n\t\tconst payload: PageViewPayload = {\n\t\t\tpath: currentPath,\n\t\t\treferrer: typeof document !== 'undefined' ? document.referrer || '' : '',\n\t\t\tuserAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',\n\t\t\tscreenWidth: window.screen?.width,\n\t\t\tsessionId: getOrCreateSessionId(),\n\t\t\ttimestamp: Date.now(),\n\t\t}\n\n\t\tif (this.options.debug) {\n\t\t\tconsole.log('[WebAnalytics] Page view:', payload)\n\t\t}\n\n\t\tthis._send(`${SDK_API_PATH}/analytics/pageview`, payload)\n\t}\n\n\t/**\n\t * Identify a user\n\t */\n\tidentify(userId: string, traits?: Record<string, unknown>): void {\n\t\tif (!this.options) return\n\t\tif (typeof window === 'undefined') return\n\n\t\tconst payload: IdentifyPayload = {\n\t\t\tuserId,\n\t\t\ttraits,\n\t\t\tsessionId: getOrCreateSessionId(),\n\t\t}\n\n\t\tif (this.options.debug) {\n\t\t\tconsole.log('[WebAnalytics] Identify:', payload)\n\t\t}\n\n\t\tthis._send(`${SDK_API_PATH}/analytics/identify`, payload)\n\t}\n\n\t/**\n\t * Destroy the tracker and remove event listeners\n\t */\n\tdestroy(): void {\n\t\tfor (const fn of this.cleanupFns) {\n\t\t\tfn()\n\t\t}\n\t\tthis.cleanupFns = []\n\t\tthis.initialized = false\n\t\tthis.options = null\n\t\tthis.lastPath = null\n\t\tthis.pageViewCount = 0\n\t}\n\n\t// ==========================================\n\t// Private helpers\n\t// ==========================================\n\n\tprivate _send(path: string, payload: unknown): void {\n\t\tif (!this.options) return\n\n\t\tconst url = `${this.options.endpoint}${path}`\n\t\tconst data = JSON.stringify(payload)\n\n\t\ttry {\n\t\t\tif (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n\t\t\t\tconst blob = new Blob([data], { type: 'application/json' })\n\t\t\t\tconst sent = navigator.sendBeacon(url, blob)\n\t\t\t\tif (!sent && this.options.debug) {\n\t\t\t\t\tconsole.warn('[WebAnalytics] sendBeacon failed, falling back to fetch')\n\t\t\t\t}\n\t\t\t\tif (sent) return\n\t\t\t}\n\n\t\t\t// Fallback: fetch with keepalive\n\t\t\tfetch(url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t'x-app-key': this.options.appKey,\n\t\t\t\t},\n\t\t\t\tbody: data,\n\t\t\t\tkeepalive: true,\n\t\t\t}).catch((err) => {\n\t\t\t\tif (this.options?.debug) {\n\t\t\t\t\tconsole.error('[WebAnalytics] Failed to send:', err)\n\t\t\t\t}\n\t\t\t})\n\t\t} catch (err) {\n\t\t\tif (this.options?.debug) {\n\t\t\t\tconsole.error('[WebAnalytics] Send error:', err)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _hookHistoryApi(): void {\n\t\tif (typeof window === 'undefined') return\n\n\t\tconst originalPush = window.history.pushState.bind(window.history)\n\t\tconst originalReplace = window.history.replaceState.bind(window.history)\n\n\t\twindow.history.pushState = (...args) => {\n\t\t\toriginalPush(...args)\n\t\t\t// Small delay to allow URL to update\n\t\t\tsetTimeout(() => this.trackPageView(), 0)\n\t\t}\n\n\t\twindow.history.replaceState = (...args) => {\n\t\t\toriginalReplace(...args)\n\t\t\t// Don't track replaceState (usually internal SPA navigation)\n\t\t}\n\n\t\tconst onPopState = () => setTimeout(() => this.trackPageView(), 0)\n\t\twindow.addEventListener('popstate', onPopState)\n\n\t\tthis.cleanupFns.push(() => {\n\t\t\twindow.history.pushState = originalPush\n\t\t\twindow.history.replaceState = originalReplace\n\t\t\twindow.removeEventListener('popstate', onPopState)\n\t\t})\n\t}\n\n\tprivate _hookNextRouter(): void {\n\t\t// Next.js App Router: listen to route-change events dispatched on document\n\t\tif (typeof document === 'undefined') return\n\n\t\t// Next.js 13+ soft navigation events\n\t\tconst onRouteAnnounced = () => setTimeout(() => this.trackPageView(), 0)\n\t\tdocument.addEventListener('nextjs:route-announced', onRouteAnnounced as EventListener)\n\n\t\tthis.cleanupFns.push(() => {\n\t\t\tdocument.removeEventListener('nextjs:route-announced', onRouteAnnounced as EventListener)\n\t\t})\n\t}\n}\n\n// ============================================\n// Singleton instance\n// ============================================\n\nlet _tracker: WebAnalyticsTracker | null = null\n\n/**\n * Get or create the global WebAnalyticsTracker singleton\n */\nexport function getWebAnalyticsTracker(): WebAnalyticsTracker {\n\tif (!_tracker) {\n\t\t_tracker = new WebAnalyticsTracker()\n\t}\n\treturn _tracker\n}\n\n/**\n * Initialize global web analytics tracker\n */\nexport function initWebAnalytics(options: WebAnalyticsOptions): void {\n\tgetWebAnalyticsTracker().init(options)\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/lib/analytics/web-analytics.ts"],"sourcesContent":["/**\n * SDK Constants — Single Source of Truth\n *\n * Shared constants used across the SDK. Centralizing these\n * prevents magic number duplication and makes changes easier.\n *\n * IMPORTANT: All time-based constants should be used consistently\n * across the SDK. Never hardcode magic numbers like 30000, 5 * 60 * 1000, etc.\n */\n\n// =============================================================================\n// API Configuration\n// =============================================================================\n\n/** Default platform URL */\nexport const DEFAULT_PLATFORM_URL = 'https://sylphx.com'\n\n/**\n * Canonical environment variable name for the platform URL.\n *\n * SDK modules MUST read from this env var (with SYLPHX_URL as legacy fallback).\n * Centralizing the name prevents the same env var being spelled differently\n * across kv.ts, streams.ts, ai.ts, middleware.ts, and server.ts.\n */\nexport const ENV_PLATFORM_URL = 'SYLPHX_PLATFORM_URL'\n\n/**\n * Legacy environment variable name for the platform URL.\n *\n * Supported as a fallback for older deployments that set SYLPHX_URL.\n * New projects should use SYLPHX_PLATFORM_URL.\n */\nexport const ENV_PLATFORM_URL_LEGACY = 'SYLPHX_URL'\n\n/**\n * Canonical environment variable name for the secret key.\n *\n * All server-side SDK modules read from this env var by default.\n */\nexport const ENV_SECRET_KEY = 'SYLPHX_SECRET_KEY'\n\n/**\n * Resolve the platform URL from environment variables.\n *\n * Priority: explicit value > SYLPHX_PLATFORM_URL > SYLPHX_URL (legacy) > default\n */\nexport function resolvePlatformUrl(explicit?: string): string {\n\treturn (\n\t\texplicit ||\n\t\tprocess.env[ENV_PLATFORM_URL] ||\n\t\tprocess.env[ENV_PLATFORM_URL_LEGACY] ||\n\t\tDEFAULT_PLATFORM_URL\n\t).trim()\n}\n\n/**\n * Resolve the secret key from environment variables.\n *\n * Returns the raw value before validation. Callers should pass the result\n * through `validateAndSanitizeSecretKey()`.\n */\nexport function resolveSecretKey(explicit?: string): string | undefined {\n\treturn explicit || process.env[ENV_SECRET_KEY]\n}\n\n/** @deprecated No longer used for path construction. Use SDK_API_PATH directly. */\nexport const SDK_API_VERSION = 'v1'\n\n/**\n * SDK API base path — served from the unified platform Hono app.\n *\n * Used when `platformUrl` is set (e.g. `https://sylphx.com`).\n * BaaS routes are mounted at /api/v1/projects/:slug/* on the platform app.\n *\n * @deprecated Prefer project `ref`-based URL: https://{ref}.api.sylphx.com/v1\n */\nexport const SDK_API_PATH = `/api/v1`\n\n/**\n * SDK API path for new subdomain-based SDK server.\n *\n * Used when `ref` is provided to `createConfig`.\n * The full base becomes: https://{ref}.api.sylphx.com + SDK_API_PATH_NEW\n */\nexport const SDK_API_PATH_NEW = `/v1`\n\n/**\n * Default SDK API host for new subdomain-based SDK server.\n * The full URL is built as: https://{ref}.api.sylphx.com/v1\n */\nexport const DEFAULT_SDK_API_HOST = 'api.sylphx.com'\n\n/**\n * Default auth route prefix\n *\n * Used for OAuth callbacks and signout routes.\n * Must match the middleware's authPrefix config.\n */\nexport const DEFAULT_AUTH_PREFIX = '/auth'\n\n/**\n * SDK package version\n *\n * Sent in X-SDK-Version header for debugging and analytics.\n * Update this when releasing new SDK versions.\n */\nexport const SDK_VERSION = '0.1.0'\n\n/**\n * SDK platform identifier\n *\n * Sent in X-SDK-Platform header to identify the runtime environment.\n */\nexport const SDK_PLATFORM =\n\ttypeof window !== 'undefined'\n\t\t? 'browser'\n\t\t: typeof process !== 'undefined' && process.versions?.node\n\t\t\t? 'node'\n\t\t\t: 'unknown'\n\n// =============================================================================\n// Timeouts & Durations\n// =============================================================================\n\n/** Default request timeout in milliseconds (30 seconds) */\nexport const DEFAULT_TIMEOUT_MS = 30_000\n\n/**\n * Token expiry buffer in milliseconds (30 seconds)\n *\n * Refresh tokens this many milliseconds BEFORE they expire\n * to account for network latency and clock skew.\n */\nexport const TOKEN_EXPIRY_BUFFER_MS = 30_000\n\n/**\n * Session token lifetime in seconds (5 minutes)\n *\n * Matches Clerk's short-lived access token pattern.\n * Used for cookie maxAge and React Query staleTime.\n */\nexport const SESSION_TOKEN_LIFETIME_SECONDS = 5 * 60\n\n/** Session token lifetime in milliseconds (for React Query staleTime) */\nexport const SESSION_TOKEN_LIFETIME_MS = SESSION_TOKEN_LIFETIME_SECONDS * 1000\n\n/**\n * Refresh token lifetime in seconds (30 days)\n *\n * Long-lived token for silent refresh.\n */\nexport const REFRESH_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60\n\n// =============================================================================\n// Feature Flags Cache\n// =============================================================================\n\n/**\n * Feature flags cache TTL in milliseconds (5 minutes)\n *\n * How long to cache flags before fetching fresh values.\n * Matches LaunchDarkly's default streaming connection behavior.\n */\nexport const FLAGS_CACHE_TTL_MS = 5 * 60 * 1000\n\n/**\n * Feature flags stale-while-revalidate window in milliseconds (1 minute)\n *\n * Allow serving stale flags while fetching fresh values.\n */\nexport const FLAGS_STALE_WHILE_REVALIDATE_MS = 60 * 1000\n\n// =============================================================================\n// Retry & Backoff\n// =============================================================================\n\n/** Maximum retry delay for exponential backoff (30 seconds) */\nexport const MAX_RETRY_DELAY_MS = 30_000\n\n/** Base retry delay for exponential backoff (1 second) */\nexport const BASE_RETRY_DELAY_MS = 1_000\n\n/** Maximum number of retries for network requests */\nexport const MAX_RETRIES = 3\n\n// =============================================================================\n// Analytics\n// =============================================================================\n\n/**\n * Analytics session timeout in milliseconds (30 minutes)\n *\n * After this much inactivity, a new session is started.\n */\nexport const ANALYTICS_SESSION_TIMEOUT_MS = 30 * 60 * 1000\n\n// =============================================================================\n// Webhooks\n// =============================================================================\n\n/**\n * Maximum age for webhook signature validation (5 minutes)\n *\n * Reject webhooks with timestamps older than this.\n */\nexport const WEBHOOK_MAX_AGE_MS = 5 * 60 * 1000\n\n/**\n * Clock skew allowance for webhook validation (30 seconds)\n *\n * Allow timestamps this far in the future.\n */\nexport const WEBHOOK_CLOCK_SKEW_MS = 30 * 1000\n\n// =============================================================================\n// PKCE (OAuth)\n// =============================================================================\n\n/**\n * PKCE code verifier TTL in milliseconds (10 minutes)\n *\n * How long the code verifier is stored during OAuth flow.\n */\nexport const PKCE_CODE_TTL_MS = 10 * 60 * 1000\n\n// =============================================================================\n// Jobs\n// =============================================================================\n\n/**\n * Job dead-letter queue retention in milliseconds (7 days)\n */\nexport const JOBS_DLQ_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000\n\n// =============================================================================\n// Session Replay\n// =============================================================================\n\n/**\n * Maximum session replay recording duration in milliseconds (60 minutes)\n */\nexport const SESSION_REPLAY_MAX_DURATION_MS = 60 * 60 * 1000\n\n/**\n * Session replay upload interval in milliseconds (5 seconds)\n */\nexport const SESSION_REPLAY_UPLOAD_INTERVAL_MS = 5_000\n\n/**\n * Session replay scroll event throttle interval (150 ms)\n */\nexport const SESSION_REPLAY_SCROLL_THROTTLE_MS = 150\n\n/**\n * Session replay media time update throttle interval (800 ms)\n */\nexport const SESSION_REPLAY_MEDIA_THROTTLE_MS = 800\n\n/**\n * Session replay rage click detection window (1 second)\n */\nexport const SESSION_REPLAY_RAGE_CLICK_WINDOW_MS = 1_000\n\n/**\n * Session replay dead click detection timeout (500 ms)\n */\nexport const SESSION_REPLAY_DEAD_CLICK_TIMEOUT_MS = 500\n\n/**\n * Session replay scroll heat detection window (2 seconds)\n */\nexport const SESSION_REPLAY_SCROLL_HEAT_WINDOW_MS = 2_000\n\n/**\n * Session replay status check interval (5 seconds)\n */\nexport const SESSION_REPLAY_STATUS_CHECK_MS = 5_000\n\n// =============================================================================\n// Analytics (Extended)\n// =============================================================================\n\n/**\n * Analytics event flush interval in milliseconds (5 seconds)\n */\nexport const ANALYTICS_FLUSH_INTERVAL_MS = 5_000\n\n/**\n * Analytics maximum text length for autocapture (100 characters)\n */\nexport const ANALYTICS_MAX_TEXT_LENGTH = 100\n\n/**\n * Analytics flush timeout in milliseconds (1 second)\n */\nexport const ANALYTICS_FLUSH_TIMEOUT_MS = 1_000\n\n/**\n * Analytics interval check in milliseconds (1 second)\n */\nexport const ANALYTICS_INTERVAL_CHECK_MS = 1_000\n\n/**\n * Analytics retry base delay in milliseconds (1 second)\n * Exponential backoff: delay = base * 2^retries (with jitter)\n */\nexport const ANALYTICS_RETRY_BASE_DELAY_MS = 1_000\n\n/**\n * Analytics retry max delay in milliseconds (30 seconds)\n */\nexport const ANALYTICS_RETRY_MAX_DELAY_MS = 30_000\n\n/**\n * Analytics retry jitter factor (±20%)\n * Prevents thundering herd when multiple clients retry simultaneously\n */\nexport const ANALYTICS_RETRY_JITTER = 0.2\n\n/**\n * Analytics maximum retries before dropping event (Segment pattern: 10)\n */\nexport const ANALYTICS_MAX_RETRIES = 10\n\n// =============================================================================\n// Feature Flags (Extended)\n// =============================================================================\n\n/**\n * Feature flags exposure deduplication window (1 hour)\n *\n * Prevents duplicate exposure events for A/B tests within this window.\n */\nexport const FLAGS_EXPOSURE_DEDUPE_WINDOW_MS = 60 * 60 * 1000\n\n/**\n * Flag stream initial reconnection delay (1 second)\n */\nexport const FLAGS_STREAM_INITIAL_RECONNECT_MS = 1_000\n\n/**\n * Flag stream maximum reconnection delay (30 seconds)\n */\nexport const FLAGS_STREAM_MAX_RECONNECT_MS = 30_000\n\n/**\n * Flag stream heartbeat timeout (45 seconds)\n */\nexport const FLAGS_STREAM_HEARTBEAT_TIMEOUT_MS = 45_000\n\n/**\n * Flag HTTP polling interval fallback (60 seconds)\n */\nexport const FLAGS_HTTP_POLLING_INTERVAL_MS = 60_000\n\n// =============================================================================\n// Jobs (Extended)\n// =============================================================================\n\n/**\n * Default retry delay sequence for exponential backoff (ms)\n */\nexport const DEFAULT_RETRY_DELAYS_MS = [1_000, 5_000, 15_000, 30_000, 60_000] as const\n\n/**\n * Default job timeout in milliseconds (60 seconds)\n */\nexport const JOB_DEFAULT_TIMEOUT_MS = 60_000\n\n/**\n * Default job status polling interval (2 seconds)\n */\nexport const JOB_POLL_INTERVAL_MS = 2_000\n\n// =============================================================================\n// Storage Keys & Prefixes\n// =============================================================================\n\n/**\n * Storage key prefix for SDK data\n */\nexport const STORAGE_KEY_PREFIX = 'sylphx_'\n\n/**\n * localStorage key for cached feature flags\n */\nexport const FLAGS_CACHE_KEY = 'sylphx_feature_flags'\n\n/**\n * localStorage key for feature flags cache timestamp\n */\nexport const FLAGS_CACHE_TIMESTAMP_KEY = 'sylphx_feature_flags_ts'\n\n/**\n * localStorage key for feature flags overrides\n */\nexport const FLAGS_OVERRIDES_KEY = 'sylphx_feature_flags_overrides'\n\n/**\n * localStorage key for active organization\n */\nexport const ORG_STORAGE_KEY = 'sylphx_active_org'\n\n/**\n * BroadcastChannel name for cross-tab org sync\n */\nexport const ORG_BROADCAST_CHANNEL = 'sylphx_org_sync'\n\n/**\n * Storage prefix for PKCE verifiers\n */\nexport const PKCE_STORAGE_PREFIX = 'sylphx_pkce_'\n\n/**\n * Test key for checking storage availability\n */\nexport const STORAGE_TEST_KEY = '__sylphx_test__'\n\n/**\n * Cookie/storage name for analytics sessions\n */\nexport const ANALYTICS_SESSION_KEY = 'sylphx_session'\n\n/**\n * Default storage key for flags persistence\n */\nexport const FLAGS_STORAGE_KEY = 'sylphx_flags'\n\n// =============================================================================\n// Click ID & Attribution\n// =============================================================================\n\n/**\n * Click ID attribution window in milliseconds (90 days)\n *\n * How long click IDs are stored for conversion attribution.\n */\nexport const CLICK_ID_EXPIRY_MS = 90 * 24 * 60 * 60 * 1000\n\n// =============================================================================\n// React Query Stale Times\n// =============================================================================\n\n/**\n * React Query staleTime for frequently-changing data (1 minute)\n *\n * Use for: real-time metrics, live feeds, active sessions\n */\nexport const STALE_TIME_FREQUENT_MS = 60 * 1_000\n\n/**\n * React Query staleTime for moderately-changing data (2 minutes)\n *\n * Use for: subscriptions, user profiles, preferences\n */\nexport const STALE_TIME_MODERATE_MS = 2 * 60 * 1_000\n\n/**\n * React Query staleTime for stable/config data (5 minutes)\n *\n * Use for: plans, feature flags, app config\n */\nexport const STALE_TIME_STABLE_MS = 5 * 60 * 1_000\n\n/**\n * React Query staleTime for webhook stats (30 seconds)\n */\nexport const STALE_TIME_STATS_MS = 30 * 1_000\n\n// =============================================================================\n// UI Component Timeouts\n// =============================================================================\n\n/**\n * Copy-to-clipboard feedback display duration (2 seconds)\n */\nexport const UI_COPY_FEEDBACK_MS = 2_000\n\n/**\n * Form success message display duration (3 seconds)\n */\nexport const UI_FORM_SUCCESS_MS = 3_000\n\n/**\n * General notification display duration (5 seconds)\n */\nexport const UI_NOTIFICATION_MS = 5_000\n\n/**\n * Prompt auto-show delay (3 seconds)\n */\nexport const UI_PROMPT_DELAY_MS = 3_000\n\n/**\n * Redirect delay after action (3 seconds)\n */\nexport const UI_REDIRECT_DELAY_MS = 3_000\n\n/**\n * Animation out duration (200 ms)\n */\nexport const UI_ANIMATION_OUT_MS = 200\n\n/**\n * Animation in duration (300 ms)\n */\nexport const UI_ANIMATION_IN_MS = 300\n\n// =============================================================================\n// Email & Verification\n// =============================================================================\n\n/**\n * Email resend cooldown tick interval (1 second)\n */\nexport const EMAIL_RESEND_COOLDOWN_TICK_MS = 1_000\n\n/**\n * New user detection threshold (1 minute)\n *\n * Users created within this window are considered \"new\" for signup tracking.\n */\nexport const NEW_USER_THRESHOLD_MS = 60 * 1_000\n\n// =============================================================================\n// Web Vitals Thresholds\n// =============================================================================\n\n/**\n * FCP (First Contentful Paint) \"good\" threshold (1800 ms)\n */\nexport const WEB_VITALS_FCP_GOOD_MS = 1_800\n\n/**\n * FCP (First Contentful Paint) \"poor\" threshold (3000 ms)\n */\nexport const WEB_VITALS_FCP_POOR_MS = 3_000\n\n// =============================================================================\n// Storage Sizes\n// =============================================================================\n\n/**\n * Multipart upload threshold (5 MB)\n *\n * Files larger than this use multipart upload for better reliability.\n */\nexport const STORAGE_MULTIPART_THRESHOLD_BYTES = 5 * 1024 * 1024\n\n/**\n * Default max file size for uploads (5 MB)\n */\nexport const STORAGE_DEFAULT_MAX_SIZE_BYTES = 5 * 1024 * 1024\n\n/**\n * Avatar max file size (2 MB)\n */\nexport const STORAGE_AVATAR_MAX_SIZE_BYTES = 2 * 1024 * 1024\n\n/**\n * Large file max size for file uploads (10 MB)\n */\nexport const STORAGE_LARGE_MAX_SIZE_BYTES = 10 * 1024 * 1024\n\n// =============================================================================\n// Cache TTLs\n// =============================================================================\n\n/**\n * JWK cache TTL (1 hour)\n */\nexport const JWK_CACHE_TTL_MS = 60 * 60 * 1000\n\n// =============================================================================\n// Analytics Event Tracking\n// =============================================================================\n\n/**\n * Max tracked event IDs to keep in memory\n */\nexport const ANALYTICS_MAX_TRACKED_EVENT_IDS = 1000\n\n/**\n * Number of event IDs to keep after cleanup\n */\nexport const ANALYTICS_TRACKED_IDS_KEEP = 500\n\n/**\n * Analytics queue limit before force flush\n */\nexport const ANALYTICS_QUEUE_LIMIT = 100\n\n// =============================================================================\n// Session Replay (Extended)\n// =============================================================================\n\n/**\n * Session replay check interval (1 second)\n */\nexport const SESSION_REPLAY_CHECK_INTERVAL_MS = 1_000\n\n/**\n * Success feedback delay for invite/account actions (1.5 seconds)\n */\nexport const UI_SUCCESS_REDIRECT_MS = 1_500\n\n// =============================================================================\n// String Truncation Limits\n// =============================================================================\n\n/**\n * Max message length for logging (1000 chars)\n */\nexport const LOG_MESSAGE_MAX_LENGTH = 1_000\n\n/**\n * Max DOM snapshot length for debugging (1000 chars)\n */\nexport const DOM_SNAPSHOT_MAX_LENGTH = 1_000\n\n/**\n * Max stack trace length for error tracking (500 chars)\n */\nexport const STACK_TRACE_MAX_LENGTH = 500\n\n/**\n * Google Consent Mode wait for update timeout (500 ms)\n */\nexport const CONSENT_WAIT_FOR_UPDATE_MS = 500\n\n// =============================================================================\n// Time Unit Conversions\n// =============================================================================\n\n/** Milliseconds per minute (60,000) */\nexport const MS_PER_MINUTE = 60_000\n\n/** Milliseconds per hour (3,600,000) */\nexport const MS_PER_HOUR = 3_600_000\n\n/** Milliseconds per day (86,400,000) */\nexport const MS_PER_DAY = 86_400_000\n\n/** Seconds per minute (60) */\nexport const SECONDS_PER_MINUTE = 60\n\n/** Seconds per hour (3,600) */\nexport const SECONDS_PER_HOUR = 3_600\n\n// =============================================================================\n// Z-Index Values\n// =============================================================================\n\n/** Z-index for modal overlays (9999) */\nexport const Z_INDEX_OVERLAY = 9999\n\n/** Z-index for critical overlays like feature gates (99999) */\nexport const Z_INDEX_CRITICAL_OVERLAY = 99999\n\n// =============================================================================\n// API Key Expiry (seconds)\n// =============================================================================\n\n/** API key expiry: 1 day (86,400 seconds) */\nexport const API_KEY_EXPIRY_1_DAY = 86_400\n\n/** API key expiry: 7 days (604,800 seconds) */\nexport const API_KEY_EXPIRY_7_DAYS = 604_800\n\n/** API key expiry: 30 days (2,592,000 seconds) */\nexport const API_KEY_EXPIRY_30_DAYS = 2_592_000\n\n/** API key expiry: 90 days (7,776,000 seconds) */\nexport const API_KEY_EXPIRY_90_DAYS = 7_776_000\n\n/** API key expiry: 1 year (31,536,000 seconds) */\nexport const API_KEY_EXPIRY_1_YEAR = 31_536_000\n\n// =============================================================================\n// Web Vitals Thresholds (Google standards)\n// =============================================================================\n\n/** LCP (Largest Contentful Paint) \"good\" threshold (2500 ms) */\nexport const WEB_VITALS_LCP_GOOD_MS = 2_500\n\n/** LCP (Largest Contentful Paint) \"poor\" threshold (4000 ms) */\nexport const WEB_VITALS_LCP_POOR_MS = 4_000\n\n/** INP (Interaction to Next Paint) \"good\" threshold (200 ms) */\nexport const WEB_VITALS_INP_GOOD_MS = 200\n\n/** INP (Interaction to Next Paint) \"poor\" threshold (500 ms) */\nexport const WEB_VITALS_INP_POOR_MS = 500\n\n/** TTFB (Time to First Byte) \"good\" threshold (800 ms) */\nexport const WEB_VITALS_TTFB_GOOD_MS = 800\n\n/** TTFB (Time to First Byte) \"poor\" threshold (1800 ms) */\nexport const WEB_VITALS_TTFB_POOR_MS = 1_800\n\n// =============================================================================\n// Security\n// =============================================================================\n\n/** Minimum password length (NIST SP 800-63B recommends 12+) */\nexport const MIN_PASSWORD_LENGTH = 12\n\n// =============================================================================\n// AI\n// =============================================================================\n\n/** Default context window for AI models (4096 tokens) */\nexport const DEFAULT_CONTEXT_WINDOW = 4_096\n\n// =============================================================================\n// Circuit Breaker (AWS/Resilience4j pattern)\n// =============================================================================\n\n/**\n * Circuit breaker failure threshold\n *\n * Number of failures in the window before circuit opens.\n */\nexport const CIRCUIT_BREAKER_FAILURE_THRESHOLD = 5\n\n/**\n * Circuit breaker failure window in milliseconds (10 seconds)\n *\n * Time window for counting failures.\n */\nexport const CIRCUIT_BREAKER_WINDOW_MS = 10_000\n\n/**\n * Circuit breaker open duration in milliseconds (30 seconds)\n *\n * How long the circuit stays open before allowing a test request.\n */\nexport const CIRCUIT_BREAKER_OPEN_DURATION_MS = 30_000\n\n// =============================================================================\n// ETag Cache (HTTP conditional requests)\n// =============================================================================\n\n/**\n * Maximum ETag cache entries\n *\n * LRU eviction when exceeded.\n */\nexport const ETAG_CACHE_MAX_ENTRIES = 100\n\n/**\n * ETag cache TTL in milliseconds (5 minutes)\n *\n * How long cached responses are valid.\n */\nexport const ETAG_CACHE_TTL_MS = 5 * 60 * 1000\n","/**\n * Web Analytics Tracker\n *\n * Lightweight page-view analytics tracker for Sylphx Platform SDK.\n * Automatically tracks page views, bounce rate, and session data.\n *\n * @example\n * ```typescript\n * import { SDK_API_PATH } from \"../../constants\";\nimport { WebAnalyticsTracker } from '@sylphx/platform-sdk/web-analytics'\n *\n * const tracker = new WebAnalyticsTracker()\n * tracker.init({\n * appKey: 'app_prod_xxx',\n * endpoint: 'https://your-app.com',\n * })\n * ```\n */\n\nimport { SDK_API_PATH } from '../../constants'\n\n// ============================================\n// Types\n// ============================================\n\nexport interface WebAnalyticsOptions {\n\t/** App key for authentication */\n\tappKey: string\n\t/** Base endpoint URL (without trailing slash) */\n\tendpoint: string\n\t/** Auto-track page views (default: true) */\n\ttrackPageViews?: boolean\n\t/** Track bounce rate (sessions with only 1 page view) (default: true) */\n\ttrackBounce?: boolean\n\t/** SPA hash routing mode (tracks hash changes) (default: false) */\n\thashMode?: boolean\n\t/** Debug logging (default: false) */\n\tdebug?: boolean\n}\n\nexport interface PageViewPayload {\n\t/** URL path (e.g. /about) */\n\tpath: string\n\t/** Document referrer */\n\treferrer: string\n\t/** Navigator user agent */\n\tuserAgent: string\n\t/** Screen width in pixels */\n\tscreenWidth: number\n\t/** Session ID (UUID stored in sessionStorage) */\n\tsessionId: string\n\t/** Unix timestamp (ms) */\n\ttimestamp: number\n}\n\nexport interface IdentifyPayload {\n\t/** User ID */\n\tuserId: string\n\t/** User traits/properties */\n\ttraits?: Record<string, unknown>\n\t/** Session ID */\n\tsessionId: string\n}\n\n// ============================================\n// Utilities\n// ============================================\n\nfunction generateSessionId(): string {\n\tif (typeof crypto !== 'undefined' && crypto.randomUUID) {\n\t\treturn crypto.randomUUID()\n\t}\n\t// Fallback for older environments\n\treturn 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n\t\tconst r = (Math.random() * 16) | 0\n\t\tconst v = c === 'x' ? r : (r & 0x3) | 0x8\n\t\treturn v.toString(16)\n\t})\n}\n\nfunction getOrCreateSessionId(): string {\n\tif (typeof sessionStorage === 'undefined') return generateSessionId()\n\tconst key = '_sylphx_sid'\n\tlet id = sessionStorage.getItem(key)\n\tif (!id) {\n\t\tid = generateSessionId()\n\t\tsessionStorage.setItem(key, id)\n\t}\n\treturn id\n}\n\nfunction getCurrentPath(hashMode: boolean): string {\n\tif (typeof window === 'undefined' || !window.location) return '/'\n\tif (hashMode) {\n\t\treturn window.location.hash.replace(/^#/, '') || '/'\n\t}\n\treturn window.location.pathname + window.location.search\n}\n\n// ============================================\n// WebAnalyticsTracker\n// ============================================\n\nexport class WebAnalyticsTracker {\n\tprivate options: Required<WebAnalyticsOptions> | null = null\n\tprivate initialized = false\n\tprivate lastPath: string | null = null\n\tprivate pageViewCount = 0\n\tprivate cleanupFns: Array<() => void> = []\n\n\t/**\n\t * Initialize the tracker and start auto-tracking page views\n\t */\n\tinit(options: WebAnalyticsOptions): void {\n\t\tif (typeof window === 'undefined') return\n\t\tif (this.initialized) return\n\n\t\tthis.options = {\n\t\t\ttrackPageViews: true,\n\t\t\ttrackBounce: true,\n\t\t\thashMode: false,\n\t\t\tdebug: false,\n\t\t\t...options,\n\t\t}\n\n\t\tthis.initialized = true\n\n\t\tif (this.options.trackPageViews) {\n\t\t\t// Track current page view\n\t\t\tthis.trackPageView()\n\n\t\t\t// Track Next.js router events (soft navigation)\n\t\t\tthis._hookNextRouter()\n\n\t\t\t// Track hash changes (for hash-mode SPAs)\n\t\t\tif (this.options.hashMode) {\n\t\t\t\tconst onHashChange = () => this.trackPageView()\n\t\t\t\twindow.addEventListener('hashchange', onHashChange)\n\t\t\t\tthis.cleanupFns.push(() => window.removeEventListener('hashchange', onHashChange))\n\t\t\t}\n\n\t\t\t// Track History API (pushState / replaceState)\n\t\t\tthis._hookHistoryApi()\n\t\t}\n\n\t\tif (this.options.debug) {\n\t\t\tconsole.log('[WebAnalytics] Initialized', this.options)\n\t\t}\n\t}\n\n\t/**\n\t * Manually track a page view\n\t */\n\ttrackPageView(path?: string): void {\n\t\tif (!this.options) return\n\t\tif (typeof window === 'undefined') return\n\n\t\tconst currentPath = path ?? getCurrentPath(this.options.hashMode)\n\n\t\t// Avoid duplicate tracking on same path\n\t\tif (currentPath === this.lastPath) return\n\t\tthis.lastPath = currentPath\n\t\tthis.pageViewCount++\n\n\t\tconst payload: PageViewPayload = {\n\t\t\tpath: currentPath,\n\t\t\treferrer: typeof document !== 'undefined' ? document.referrer || '' : '',\n\t\t\tuserAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',\n\t\t\tscreenWidth: window.screen?.width,\n\t\t\tsessionId: getOrCreateSessionId(),\n\t\t\ttimestamp: Date.now(),\n\t\t}\n\n\t\tif (this.options.debug) {\n\t\t\tconsole.log('[WebAnalytics] Page view:', payload)\n\t\t}\n\n\t\tthis._send(`${SDK_API_PATH}/analytics/pageview`, payload)\n\t}\n\n\t/**\n\t * Identify a user\n\t */\n\tidentify(userId: string, traits?: Record<string, unknown>): void {\n\t\tif (!this.options) return\n\t\tif (typeof window === 'undefined') return\n\n\t\tconst payload: IdentifyPayload = {\n\t\t\tuserId,\n\t\t\ttraits,\n\t\t\tsessionId: getOrCreateSessionId(),\n\t\t}\n\n\t\tif (this.options.debug) {\n\t\t\tconsole.log('[WebAnalytics] Identify:', payload)\n\t\t}\n\n\t\tthis._send(`${SDK_API_PATH}/analytics/identify`, payload)\n\t}\n\n\t/**\n\t * Destroy the tracker and remove event listeners\n\t */\n\tdestroy(): void {\n\t\tfor (const fn of this.cleanupFns) {\n\t\t\tfn()\n\t\t}\n\t\tthis.cleanupFns = []\n\t\tthis.initialized = false\n\t\tthis.options = null\n\t\tthis.lastPath = null\n\t\tthis.pageViewCount = 0\n\t}\n\n\t// ==========================================\n\t// Private helpers\n\t// ==========================================\n\n\tprivate _send(path: string, payload: unknown): void {\n\t\tif (!this.options) return\n\n\t\tconst url = `${this.options.endpoint}${path}`\n\t\tconst data = JSON.stringify(payload)\n\n\t\ttry {\n\t\t\tif (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n\t\t\t\tconst blob = new Blob([data], { type: 'application/json' })\n\t\t\t\tconst sent = navigator.sendBeacon(url, blob)\n\t\t\t\tif (!sent && this.options.debug) {\n\t\t\t\t\tconsole.warn('[WebAnalytics] sendBeacon failed, falling back to fetch')\n\t\t\t\t}\n\t\t\t\tif (sent) return\n\t\t\t}\n\n\t\t\t// Fallback: fetch with keepalive\n\t\t\tfetch(url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t'x-app-key': this.options.appKey,\n\t\t\t\t},\n\t\t\t\tbody: data,\n\t\t\t\tkeepalive: true,\n\t\t\t}).catch((err) => {\n\t\t\t\tif (this.options?.debug) {\n\t\t\t\t\tconsole.error('[WebAnalytics] Failed to send:', err)\n\t\t\t\t}\n\t\t\t})\n\t\t} catch (err) {\n\t\t\tif (this.options?.debug) {\n\t\t\t\tconsole.error('[WebAnalytics] Send error:', err)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _hookHistoryApi(): void {\n\t\tif (typeof window === 'undefined') return\n\n\t\tconst originalPush = window.history.pushState.bind(window.history)\n\t\tconst originalReplace = window.history.replaceState.bind(window.history)\n\n\t\twindow.history.pushState = (...args) => {\n\t\t\toriginalPush(...args)\n\t\t\t// Small delay to allow URL to update\n\t\t\tsetTimeout(() => this.trackPageView(), 0)\n\t\t}\n\n\t\twindow.history.replaceState = (...args) => {\n\t\t\toriginalReplace(...args)\n\t\t\t// Don't track replaceState (usually internal SPA navigation)\n\t\t}\n\n\t\tconst onPopState = () => setTimeout(() => this.trackPageView(), 0)\n\t\twindow.addEventListener('popstate', onPopState)\n\n\t\tthis.cleanupFns.push(() => {\n\t\t\twindow.history.pushState = originalPush\n\t\t\twindow.history.replaceState = originalReplace\n\t\t\twindow.removeEventListener('popstate', onPopState)\n\t\t})\n\t}\n\n\tprivate _hookNextRouter(): void {\n\t\t// Next.js App Router: listen to route-change events dispatched on document\n\t\tif (typeof document === 'undefined') return\n\n\t\t// Next.js 13+ soft navigation events\n\t\tconst onRouteAnnounced = () => setTimeout(() => this.trackPageView(), 0)\n\t\tdocument.addEventListener('nextjs:route-announced', onRouteAnnounced as EventListener)\n\n\t\tthis.cleanupFns.push(() => {\n\t\t\tdocument.removeEventListener('nextjs:route-announced', onRouteAnnounced as EventListener)\n\t\t})\n\t}\n}\n\n// ============================================\n// Singleton instance\n// ============================================\n\nlet _tracker: WebAnalyticsTracker | null = null\n\n/**\n * Get or create the global WebAnalyticsTracker singleton\n */\nexport function getWebAnalyticsTracker(): WebAnalyticsTracker {\n\tif (!_tracker) {\n\t\t_tracker = new WebAnalyticsTracker()\n\t}\n\treturn _tracker\n}\n\n/**\n * Initialize global web analytics tracker\n */\nexport function initWebAnalytics(options: WebAnalyticsOptions): void {\n\tgetWebAnalyticsTracker().init(options)\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylphx/sdk",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Sylphx SDK - State-of-the-art platform SDK with pure functions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"test": "bun test",
|
|
46
46
|
"test:watch": "bun test --watch",
|
|
47
47
|
"test:coverage": "bun test --coverage",
|
|
48
|
-
"prepare": "tsup",
|
|
48
|
+
"prepare": "tsup --no-dts",
|
|
49
49
|
"prepublishOnly": "tsup",
|
|
50
50
|
"generate:types": "openapi-typescript https://sylphx.com/api/openapi.json -o src/generated/api.d.ts",
|
|
51
51
|
"generate:types:local": "openapi-typescript http://localhost:3002/api/openapi.json -o src/generated/api.d.ts",
|
|
@@ -76,20 +76,19 @@
|
|
|
76
76
|
}
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@aws-sdk/client-s3": "^3.839.0",
|
|
80
|
-
"@aws-sdk/s3-request-presigner": "^3.839.0",
|
|
81
79
|
"jose": "^6.1.3",
|
|
82
80
|
"openapi-fetch": "0.15.0",
|
|
83
|
-
"rrweb": "^
|
|
81
|
+
"rrweb": "^1.1.3",
|
|
84
82
|
"web-vitals": "5.1.0"
|
|
85
83
|
},
|
|
86
84
|
"devDependencies": {
|
|
85
|
+
"@rrweb/types": "2.0.0-alpha.20",
|
|
87
86
|
"@sylphx/ui": "workspace:*",
|
|
88
87
|
"@tanstack/react-query": "^5",
|
|
89
88
|
"@types/node": "^25",
|
|
90
89
|
"@types/react": "^19",
|
|
91
90
|
"@types/react-dom": "^19",
|
|
92
|
-
"openapi-typescript": "7.
|
|
91
|
+
"openapi-typescript": "7.13.0",
|
|
93
92
|
"react": "^19",
|
|
94
93
|
"react-dom": "^19",
|
|
95
94
|
"tsup": "^8.5.1",
|