{"version":3,"file":"posthog-Bz9XTdX5.cjs","names":["EventSubscriber"],"sources":["../src/posthog-error-formatter.ts","../src/posthog.ts"],"sourcesContent":["/**\n * Format errors for PostHog's $exception event format.\n * Compatible with autotel-web's ExceptionList type.\n */\n\ntype StringRedactor = (value: string) => string;\n\ninterface StackFrame {\n  filename?: string;\n  function?: string;\n  lineno?: number;\n  colno?: number;\n  abs_path?: string;\n  in_app?: boolean;\n  platform?: string;\n}\n\ninterface ExceptionRecord {\n  type: string;\n  value: string;\n  mechanism: { type: string; handled: boolean };\n  stacktrace?: { frames: StackFrame[] };\n}\n\ninterface PostHogExceptionProperties {\n  $exception_list: Array<{\n    type: string;\n    value: string;\n    mechanism: { type: string; handled: boolean };\n    stacktrace: { frames: Array<StackFrame & { platform: string }> };\n  }>;\n}\n\nconst MAX_CAUSE_DEPTH = 5;\n\nexport function formatExceptionForPostHog(\n  exceptionList: ExceptionRecord[],\n  platform: string = 'web:javascript',\n  redactor?: StringRedactor,\n): PostHogExceptionProperties {\n  return {\n    $exception_list: exceptionList.map((ex) => ({\n      type: ex.type,\n      value: redactor ? redactor(ex.value) : ex.value,\n      mechanism: ex.mechanism,\n      stacktrace: {\n        frames: (ex.stacktrace?.frames || []).map((frame) => ({\n          ...frame,\n          abs_path: frame.abs_path && redactor ? redactor(frame.abs_path) : frame.abs_path,\n          platform,\n        })),\n      },\n    })),\n  };\n}\n\nexport function errorToExceptionList(\n  input: unknown,\n  redactor?: StringRedactor,\n): ExceptionRecord[] {\n  const error = input instanceof Error ? input : new Error(\n    input === null || input === undefined ? 'Unknown error' : String(input),\n  );\n\n  const records: ExceptionRecord[] = [];\n  let current: Error | undefined = error;\n  let depth = 0;\n\n  while (current && depth < MAX_CAUSE_DEPTH) {\n    const value = current.message || 'Unknown error';\n    const frames = current.stack ? parseStackBasic(current.stack) : undefined;\n    records.push({\n      type: current.name || 'Error',\n      value: redactor ? redactor(value) : value,\n      mechanism: { type: 'manual', handled: true },\n      stacktrace: frames ? {\n        frames: redactor\n          ? frames.map((f) => ({\n              ...f,\n              abs_path: f.abs_path ? redactor(f.abs_path) : f.abs_path,\n            }))\n          : frames,\n      } : undefined,\n    });\n    current = current.cause instanceof Error ? current.cause : undefined;\n    depth++;\n  }\n\n  return records.toReversed();\n}\n\nfunction parseStackBasic(stack: string): StackFrame[] {\n  const lines = stack.split('\\n');\n  const frames: StackFrame[] = [];\n  const re = /^\\s*at\\s+(?:(.+?)\\s+\\()?(.+?):(\\d+):(\\d+)\\)?$/;\n\n  for (const line of lines) {\n    const match = line.trim().match(re);\n    if (match) {\n      const [, fn, absPath, lineStr, colStr] = match;\n      frames.push({\n        function: fn || undefined,\n        abs_path: absPath,\n        filename: absPath.split('/').pop() || absPath,\n        lineno: Number.parseInt(lineStr, 10),\n        colno: Number.parseInt(colStr, 10),\n        in_app: !absPath.includes('node_modules'),\n      });\n    }\n  }\n\n  return frames;\n}\n","/**\n * PostHog Subscriber for autotel\n *\n * Send events to PostHog for product events, feature flags, and A/B testing.\n *\n * @example Basic usage\n * ```typescript\n * import { Events } from 'autotel/events';\n * import { PostHogSubscriber } from 'autotel-subscribers/posthog';\n *\n * const events = new Events('checkout', {\n *   subscribers: [\n *     new PostHogSubscriber({\n *       apiKey: process.env.POSTHOG_API_KEY!,\n *       host: 'https://us.i.posthog.com' // optional, defaults to US cloud\n *     })\n *   ]\n * });\n *\n * // Events go to both OpenTelemetry AND PostHog\n * events.trackEvent('order.completed', { userId: '123', amount: 99.99 });\n * ```\n *\n * @example Feature flags\n * ```typescript\n * const subscriber = new PostHogSubscriber({ apiKey: 'phc_...' });\n *\n * // Check if feature is enabled\n * const isEnabled = await subscriber.isFeatureEnabled('new-checkout', 'user-123');\n *\n * // Get feature flag value (string, boolean, number)\n * const variant = await subscriber.getFeatureFlag('experiment-variant', 'user-123');\n *\n * // Get all flags for a user\n * const allFlags = await subscriber.getAllFlags('user-123');\n * ```\n *\n * @example Person and group events\n * ```typescript\n * // Identify user and set properties\n * await subscriber.identify('user-123', {\n *   email: 'user@example.com',\n *   plan: 'premium'\n * });\n *\n * // Identify a group (e.g., organization)\n * await subscriber.groupIdentify('company', 'acme-corp', {\n *   industry: 'saas',\n *   employees: 500\n * });\n * ```\n *\n * @example Serverless configuration\n * ```typescript\n * // Optimized for AWS Lambda / Vercel Functions\n * const subscriber = new PostHogSubscriber({\n *   apiKey: 'phc_...',\n *   flushAt: 1,        // Send immediately (don't batch)\n *   flushInterval: 0,  // Disable interval-based flushing\n * });\n * ```\n *\n * @example Custom PostHog client\n * ```typescript\n * import { PostHog } from 'posthog-node';\n *\n * const customClient = new PostHog('phc_...', {\n *   host: 'https://eu.i.posthog.com',\n *   // ... other PostHog options\n * });\n *\n * const subscriber = new PostHogSubscriber({\n *   client: customClient\n * });\n * ```\n *\n * @example Error handling\n * ```typescript\n * const subscriber = new PostHogSubscriber({\n *   apiKey: 'phc_...',\n *   onError: (error) => {\n *     console.error('PostHog error:', error);\n *     // Send to error tracking service\n *   }\n * });\n * ```\n */\n\nimport type { EventAttributes, EventAttributesInput } from 'autotel/event-subscriber';\nimport { EventSubscriber, type EventPayload } from './event-subscriber-base';\nimport { formatExceptionForPostHog, errorToExceptionList } from './posthog-error-formatter';\nimport slowRedact from 'slow-redact';\n\n// Type-only import to avoid runtime dependency\nimport type { PostHog } from 'posthog-node';\n\n/**\n * Error context for enhanced error handling\n *\n * Provides detailed context about the event that caused an error.\n */\nexport interface ErrorContext {\n  /** The error that occurred */\n  error: Error;\n\n  /** Event name (if applicable) */\n  eventName?: string;\n\n  /** Event type (event, funnel, outcome, value) */\n  eventType?: 'event' | 'funnel' | 'outcome' | 'value';\n\n  /** Event attributes (filtered) */\n  attributes?: EventAttributes;\n\n  /** Subscriber name */\n  subscriberName: string;\n}\n\ntype StringRedactor = (value: string) => string;\n\nexport interface PostHogConfig {\n  /** PostHog API key (starts with phc_) - required if not providing custom client */\n  apiKey?: string;\n\n  /** PostHog host (defaults to US cloud) */\n  host?: string;\n\n  /** Enable/disable the subscriber */\n  enabled?: boolean;\n\n  /** Custom PostHog client instance (bypasses apiKey/host) */\n  client?: PostHog;\n\n  /**\n   * Use global browser client (window.posthog)\n   *\n   * When true, uses the PostHog client already loaded on the page via script tag.\n   * This is useful for Next.js apps that initialize PostHog in _app.tsx.\n   *\n   * @example\n   * ```typescript\n   * // Browser - uses window.posthog\n   * const subscriber = new PostHogSubscriber({\n   *   useGlobalClient: true,\n   * });\n   * ```\n   */\n  useGlobalClient?: boolean;\n\n  /**\n   * Serverless mode preset (AWS Lambda, Vercel Functions, Next.js API routes)\n   *\n   * When true, auto-configures for serverless environments:\n   * - flushAt: 1 (send immediately, don't batch)\n   * - flushInterval: 0 (disable interval-based flushing)\n   * - requestTimeout: 3000 (shorter timeout for fast responses)\n   *\n   * @example\n   * ```typescript\n   * // Vercel / Next.js API route\n   * const subscriber = new PostHogSubscriber({\n   *   apiKey: 'phc_...',\n   *   serverless: true,\n   * });\n   * ```\n   */\n  serverless?: boolean;\n\n  // Serverless optimizations\n  /** Flush batch when it reaches this size (default: 20, set to 1 for immediate send) */\n  flushAt?: number;\n\n  /** Flush interval in milliseconds (default: 10000, set to 0 to disable) */\n  flushInterval?: number;\n\n  // Performance tuning\n  /** Disable geoip lookup to reduce request size (default: false) */\n  disableGeoip?: boolean;\n\n  /** Request timeout in milliseconds (default: 10000) */\n  requestTimeout?: number;\n\n  /** Send feature flag evaluation events (default: true) */\n  sendFeatureFlags?: boolean;\n\n  /**\n   * Automatically filter out undefined and null values from attributes\n   *\n   * When true (default), undefined and null values are removed before sending.\n   * This improves DX when passing objects with optional properties.\n   *\n   * @default true\n   *\n   * @example\n   * ```typescript\n   * // With filterUndefinedValues: true (default)\n   * subscriber.trackEvent('user.action', {\n   *   userId: user.id,\n   *   email: user.email,        // might be undefined - will be filtered\n   *   plan: user.subscription,  // might be null - will be filtered\n   * });\n   * ```\n   */\n  filterUndefinedValues?: boolean;\n\n  // Error handling\n  /** Error callback for debugging and monitoring */\n  onError?: (error: Error) => void;\n\n  /**\n   * Enhanced error callback with event context\n   *\n   * Provides detailed context about the event that caused the error.\n   * If both onError and onErrorWithContext are provided, both are called.\n   *\n   * @example\n   * ```typescript\n   * const subscriber = new PostHogSubscriber({\n   *   apiKey: 'phc_...',\n   *   onErrorWithContext: (ctx) => {\n   *     console.error(`Failed to track ${ctx.eventType}: ${ctx.eventName}`, ctx.error);\n   *     Sentry.captureException(ctx.error, { extra: ctx });\n   *   }\n   * });\n   * ```\n   */\n  onErrorWithContext?: (context: ErrorContext) => void;\n\n  /** Known attribute paths to redact using slow-redact (path-based, immutable). */\n  redactPaths?: string[];\n\n  /** String redactor for value-based PII scanning. Applied after path-based redaction. */\n  stringRedactor?: StringRedactor;\n\n  /** Enable debug logging (default: false) */\n  debug?: boolean;\n}\n\n/**\n * PostHog feature flag options\n */\nexport interface FeatureFlagOptions {\n  /** Group context for group-based feature flags */\n  groups?: Record<string, string | number>;\n\n  /** Group properties for feature flag evaluation */\n  groupProperties?: Record<string, Record<string, any>>;\n\n  /** Person properties for feature flag evaluation */\n  personProperties?: Record<string, any>;\n\n  /** Only evaluate locally, don't send $feature_flag_called event */\n  onlyEvaluateLocally?: boolean;\n\n  /** Send feature flag events even if disabled globally */\n  sendFeatureFlagEvents?: boolean;\n}\n\n/**\n * Person properties for identify calls\n */\nexport interface PersonProperties {\n  /** Set properties (will update existing values) */\n  $set?: Record<string, any>;\n\n  /** Set properties only if they don't exist */\n  $set_once?: Record<string, any>;\n\n  /** Any custom properties */\n  [key: string]: any;\n}\n\nexport class PostHogSubscriber extends EventSubscriber {\n  readonly name = 'PostHogSubscriber';\n  readonly version = '2.0.0';\n\n  private posthog: PostHog | null = null;\n  private config: PostHogConfig;\n  private initPromise: Promise<void> | null = null;\n  /** True when using browser's window.posthog (different API signature) */\n  private isBrowserClient = false;\n  private pathRedactor: ((obj: Record<string, unknown>) => Record<string, unknown>) | null = null;\n  private stringRedactor: StringRedactor | null = null;\n\n  constructor(config: PostHogConfig) {\n    super();\n\n    // Apply serverless preset first (can be overridden by explicit config)\n    if (config.serverless) {\n      config = {\n        flushAt: 1,\n        flushInterval: 0,\n        requestTimeout: 3000,\n        ...config, // User config overrides serverless defaults\n      };\n    }\n\n    // Validate: need either apiKey, client, or useGlobalClient\n    if (!config.apiKey && !config.client && !config.useGlobalClient) {\n      throw new Error(\n        'PostHogSubscriber requires either apiKey, client, or useGlobalClient to be provided',\n      );\n    }\n\n    this.enabled = config.enabled ?? true;\n    // Default filterUndefinedValues to true\n    this.config = {\n      filterUndefinedValues: true,\n      ...config,\n    };\n\n    if (this.config.redactPaths && this.config.redactPaths.length > 0) {\n      this.pathRedactor = slowRedact({\n        paths: this.config.redactPaths,\n        serialize: false,\n      }) as any;\n    }\n    if (this.config.stringRedactor) {\n      this.stringRedactor = this.config.stringRedactor;\n    }\n\n    if (this.enabled) {\n      // Start initialization immediately but don't block constructor\n      this.initPromise = this.initialize();\n    }\n  }\n\n  private async initialize(): Promise<void> {\n    try {\n      // Option 1: Use global browser client (window.posthog)\n      if (this.config.useGlobalClient) {\n        const globalWindow = typeof globalThis === 'undefined' ? undefined : (globalThis as Record<string, unknown>);\n        if (globalWindow?.posthog) {\n          this.posthog = globalWindow.posthog as PostHog;\n          this.isBrowserClient = true;\n          this.setupErrorHandling();\n          return;\n        }\n        throw new Error(\n          'useGlobalClient enabled but window.posthog not found. ' +\n            'Ensure PostHog script is loaded before initializing the subscriber.',\n        );\n      }\n\n      // Option 2: Use custom client if provided\n      if (this.config.client) {\n        this.posthog = this.config.client;\n        this.setupErrorHandling();\n        return;\n      }\n\n      // Option 3: Create new PostHog client with dynamic import\n      const { PostHog } = await import('posthog-node');\n\n      this.posthog = new PostHog(this.config.apiKey!, {\n        host: this.config.host || 'https://us.i.posthog.com',\n        flushAt: this.config.flushAt,\n        flushInterval: this.config.flushInterval,\n        requestTimeout: this.config.requestTimeout,\n        disableGeoip: this.config.disableGeoip,\n        sendFeatureFlagEvent: this.config.sendFeatureFlags,\n      });\n\n      this.setupErrorHandling();\n    } catch (error) {\n      console.error(\n        'PostHog subscriber failed to initialize. Install posthog-node: pnpm add posthog-node',\n        error,\n      );\n      this.enabled = false;\n      this.config.onError?.(error as Error);\n    }\n  }\n\n  private setupErrorHandling(): void {\n    if (this.config.debug) {\n      this.posthog?.debug();\n    }\n\n    if (this.config.onError && this.posthog?.on) {\n      this.posthog.on('error', this.config.onError);\n    }\n  }\n\n  private async ensureInitialized(): Promise<void> {\n    if (this.initPromise) {\n      await this.initPromise;\n      this.initPromise = null;\n    }\n  }\n\n  private extractDistinctId(attributes?: EventAttributes): string {\n    return (attributes?.userId || attributes?.user_id || 'anonymous') as string;\n  }\n\n  private redactProperties(properties: Record<string, unknown>): Record<string, unknown> {\n    let result = properties;\n    if (this.pathRedactor) {\n      result = this.pathRedactor(properties) as Record<string, unknown>;\n    }\n    if (this.stringRedactor) {\n      result = this.redactStringValues(result);\n    }\n    return result;\n  }\n\n  private redactStringValues(obj: Record<string, unknown>): Record<string, unknown> {\n    const result: Record<string, unknown> = {};\n    for (const [key, value] of Object.entries(obj)) {\n      if (typeof value === 'string') {\n        result[key] = this.stringRedactor!(value);\n      } else if (Array.isArray(value)) {\n        result[key] = this.redactArray(value);\n      } else if (value !== null && typeof value === 'object') {\n        result[key] = this.redactStringValues(value as Record<string, unknown>);\n      } else {\n        result[key] = value;\n      }\n    }\n    return result;\n  }\n\n  private redactArray(arr: unknown[]): unknown[] {\n    return arr.map((item) => {\n      if (typeof item === 'string') {\n        return this.stringRedactor!(item);\n      } else if (Array.isArray(item)) {\n        return this.redactArray(item);\n      } else if (item !== null && typeof item === 'object') {\n        return this.redactStringValues(item as Record<string, unknown>);\n      }\n      return item;\n    });\n  }\n\n  /**\n   * Set the string redactor. Called by autotel init() when attributeRedactor is configured.\n   * Can also be called manually.\n   */\n  setStringRedactor(redactor: StringRedactor): void {\n    this.stringRedactor = redactor;\n  }\n\n  /**\n   * Send payload to PostHog\n   *\n   * Maps autotel context to PostHog-specific field names:\n   * - autotel.trace_id → $trace_id\n   * - autotel.span_id → $span_id\n   * - autotel.correlation_id → $correlation_id\n   * - autotel.trace_url → $trace_url\n   */\n  protected async sendToDestination(payload: EventPayload): Promise<void> {\n    await this.ensureInitialized();\n\n    // Filter attributes if enabled (default: true)\n    const filteredAttributes =\n      this.config.filterUndefinedValues === false\n        ? payload.attributes\n        : this.filterAttributes(payload.attributes as EventAttributesInput);\n\n    // Build properties object, including value and funnel metadata if present\n    const properties: Record<string, unknown> = { ...filteredAttributes };\n    if (payload.value !== undefined) {\n      properties.value = payload.value;\n    }\n    // Add funnel progression metadata\n    if (payload.stepNumber !== undefined) {\n      properties.step_number = payload.stepNumber;\n    }\n    if (payload.stepName !== undefined) {\n      properties.step_name = payload.stepName;\n    }\n\n    // Map autotel context to PostHog-specific field names\n    if (payload.autotel) {\n      if (payload.autotel.trace_id) {\n        properties.$trace_id = payload.autotel.trace_id;\n      }\n      if (payload.autotel.span_id) {\n        properties.$span_id = payload.autotel.span_id;\n      }\n      if (payload.autotel.correlation_id) {\n        properties.$correlation_id = payload.autotel.correlation_id;\n      }\n      if (payload.autotel.trace_flags) {\n        properties.$trace_flags = payload.autotel.trace_flags;\n      }\n      if (payload.autotel.trace_state) {\n        properties.$trace_state = payload.autotel.trace_state;\n      }\n      if (payload.autotel.trace_url) {\n        properties.$trace_url = payload.autotel.trace_url;\n      }\n      // Batch/fan-in context\n      if (payload.autotel.linked_trace_id_count !== undefined) {\n        properties.$linked_trace_id_count = payload.autotel.linked_trace_id_count;\n      }\n      if (payload.autotel.linked_trace_id_hash) {\n        properties.$linked_trace_id_hash = payload.autotel.linked_trace_id_hash;\n      }\n      if (payload.autotel.linked_trace_ids) {\n        properties.$linked_trace_ids = payload.autotel.linked_trace_ids;\n      }\n    }\n\n    const redactedProperties = this.redactProperties(properties);\n\n    if (payload.attributes?.['exception.list']) {\n      try {\n        const exceptionList = JSON.parse(payload.attributes['exception.list'] as string);\n        const formatted = formatExceptionForPostHog(\n          exceptionList,\n          undefined,\n          this.stringRedactor ?? undefined,\n        );\n        const exceptionProperties = {\n          ...redactedProperties,\n          ...formatted,\n        };\n\n        if (this.isBrowserClient) {\n          (this.posthog as any)?.capture('$exception', exceptionProperties);\n        } else {\n          this.posthog?.capture({\n            distinctId: this.extractDistinctId(filteredAttributes),\n            event: '$exception',\n            properties: exceptionProperties,\n          });\n        }\n      } catch {\n        // exception.list parsing failed, continue with normal event\n      }\n    }\n\n    const distinctId = this.extractDistinctId(filteredAttributes);\n\n    // Browser client has different API signature\n    if (this.isBrowserClient) {\n      // Browser API: capture(eventName, properties)\n      (this.posthog as any)?.capture(payload.name, redactedProperties);\n    } else {\n      // Server API: capture({ distinctId, event, properties, groups })\n      const capturePayload: any = {\n        distinctId,\n        event: payload.name,\n        properties: redactedProperties,\n      };\n\n      // Add groups if present in attributes\n      if (filteredAttributes?.groups) {\n        capturePayload.groups = filteredAttributes.groups;\n      }\n\n      this.posthog?.capture(capturePayload);\n    }\n  }\n\n  // Feature Flag Methods\n\n  /**\n   * Check if a feature flag is enabled for a user\n   *\n   * @param flagKey - Feature flag key\n   * @param distinctId - User ID or anonymous ID\n   * @param options - Feature flag evaluation options\n   * @returns true if enabled, false otherwise\n   *\n   * @example\n   * ```typescript\n   * const isEnabled = await subscriber.isFeatureEnabled('new-checkout', 'user-123');\n   *\n   * // With groups\n   * const isEnabled = await subscriber.isFeatureEnabled('beta-features', 'user-123', {\n   *   groups: { company: 'acme-corp' }\n   * });\n   * ```\n   */\n  async isFeatureEnabled(\n    flagKey: string,\n    distinctId: string,\n    options?: FeatureFlagOptions,\n  ): Promise<boolean> {\n    if (!this.enabled) return false;\n    await this.ensureInitialized();\n\n    try {\n      return await this.posthog?.isFeatureEnabled(flagKey, distinctId, options as any) ?? false;\n    } catch (error) {\n      this.config.onError?.(error as Error);\n      return false;\n    }\n  }\n\n  /**\n   * Get feature flag value for a user\n   *\n   * @param flagKey - Feature flag key\n   * @param distinctId - User ID or anonymous ID\n   * @param options - Feature flag evaluation options\n   * @returns Flag value (string, boolean, or undefined)\n   *\n   * @example\n   * ```typescript\n   * const variant = await subscriber.getFeatureFlag('experiment-variant', 'user-123');\n   * // Returns: 'control' | 'test' | 'test-2' | undefined\n   *\n   * // With person properties\n   * const variant = await subscriber.getFeatureFlag('premium-feature', 'user-123', {\n   *   personProperties: { plan: 'premium' }\n   * });\n   * ```\n   */\n  async getFeatureFlag(\n    flagKey: string,\n    distinctId: string,\n    options?: FeatureFlagOptions,\n  ): Promise<string | boolean | undefined> {\n    if (!this.enabled) return undefined;\n    await this.ensureInitialized();\n\n    try {\n      return await this.posthog?.getFeatureFlag(flagKey, distinctId, options as any);\n    } catch (error) {\n      this.config.onError?.(error as Error);\n      return undefined;\n    }\n  }\n\n  /**\n   * Get all feature flags for a user\n   *\n   * @param distinctId - User ID or anonymous ID\n   * @param options - Feature flag evaluation options\n   * @returns Object mapping flag keys to their values\n   *\n   * @example\n   * ```typescript\n   * const flags = await subscriber.getAllFlags('user-123');\n   * // Returns: { 'new-checkout': true, 'experiment-variant': 'test', ... }\n   * ```\n   */\n  async getAllFlags(\n    distinctId: string,\n    options?: FeatureFlagOptions,\n  ): Promise<Record<string, string | number | boolean>> {\n    if (!this.enabled) return {};\n    await this.ensureInitialized();\n\n    try {\n      const flags = await this.posthog?.getAllFlags(distinctId, options as any);\n      return flags ?? {};\n    } catch (error) {\n      this.config.onError?.(error as Error);\n      return {};\n    }\n  }\n\n  /**\n   * Reload feature flags from PostHog server\n   *\n   * Call this to refresh feature flag definitions without restarting.\n   *\n   * @example\n   * ```typescript\n   * await subscriber.reloadFeatureFlags();\n   * ```\n   */\n  async reloadFeatureFlags(): Promise<void> {\n    if (!this.enabled) return;\n    await this.ensureInitialized();\n\n    try {\n      await this.posthog?.reloadFeatureFlags();\n    } catch (error) {\n      this.config.onError?.(error as Error);\n    }\n  }\n\n  // Person and Group Events\n\n  /**\n   * Identify a user and set their properties\n   *\n   * @param distinctId - User ID\n   * @param properties - Person properties ($set, $set_once, or custom properties)\n   *\n   * @example\n   * ```typescript\n   * // Set properties (will update existing values)\n   * await subscriber.identify('user-123', {\n   *   $set: {\n   *     email: 'user@example.com',\n   *     plan: 'premium'\n   *   }\n   * });\n   *\n   * // Set properties only once (won't update if already exists)\n   * await subscriber.identify('user-123', {\n   *   $set_once: {\n   *     signup_date: '2025-01-17'\n   *   }\n   * });\n   * ```\n   */\n  async identify(distinctId: string, properties?: PersonProperties): Promise<void> {\n    if (!this.enabled) return;\n    await this.ensureInitialized();\n\n    try {\n      this.posthog?.identify({\n        distinctId,\n        properties,\n      });\n    } catch (error) {\n      this.config.onError?.(error as Error);\n    }\n  }\n\n  /**\n   * Identify a group and set its properties\n   *\n   * Groups are useful for B2B SaaS to track organizations, teams, or accounts.\n   *\n   * @param groupType - Type of group (e.g., 'company', 'organization', 'team')\n   * @param groupKey - Unique identifier for the group\n   * @param properties - Group properties\n   *\n   * @example\n   * ```typescript\n   * await subscriber.groupIdentify('company', 'acme-corp', {\n   *   $set: {\n   *     name: 'Acme Corporation',\n   *     industry: 'saas',\n   *     employees: 500,\n   *     plan: 'enterprise'\n   *   }\n   * });\n   * ```\n   */\n  async groupIdentify(\n    groupType: string,\n    groupKey: string | number,\n    properties?: Record<string, any>,\n  ): Promise<void> {\n    if (!this.enabled) return;\n    await this.ensureInitialized();\n\n    try {\n      this.posthog?.groupIdentify({\n        groupType,\n        groupKey: String(groupKey), // Convert to string for PostHog SDK\n        properties,\n      });\n    } catch (error) {\n      this.config.onError?.(error as Error);\n    }\n  }\n\n  /**\n   * Track an event with group context\n   *\n   * Use this to associate events with groups (e.g., organizations).\n   *\n   * @param name - Event name\n   * @param attributes - Event attributes\n   * @param groups - Group context (e.g., { company: 'acme-corp' })\n   *\n   * @example\n   * ```typescript\n   * await subscriber.trackEventWithGroups('feature.used', {\n   *   userId: 'user-123',\n   *   feature: 'advanced-events'\n   * }, {\n   *   company: 'acme-corp'\n   * });\n   * ```\n   */\n  async trackEventWithGroups(\n    name: string,\n    attributes?: EventAttributes,\n    groups?: Record<string, string | number>,\n  ): Promise<void> {\n    if (!this.enabled) return;\n    await this.ensureInitialized();\n\n    const eventAttributes: EventAttributes = { ...attributes } as EventAttributes;\n    if (groups) {\n      (eventAttributes as any).groups = groups;\n    }\n\n    await this.trackEvent(name, eventAttributes);\n  }\n\n  /**\n   * Capture an exception and send to PostHog error tracking.\n   *\n   * If using browser client (window.posthog), delegates to its captureException.\n   * Otherwise, formats and sends via posthog-node capture API.\n   */\n  async captureException(\n    error: unknown,\n    options?: {\n      distinctId?: string;\n      additionalProperties?: Record<string, unknown>;\n    },\n  ): Promise<void> {\n    if (!this.enabled) return;\n    await this.ensureInitialized();\n\n    try {\n      if (this.isBrowserClient) {\n        const browserProps = options?.additionalProperties\n          ? this.redactProperties(options.additionalProperties)\n          : undefined;\n        (this.posthog as any)?.captureException?.(error, browserProps);\n        return;\n      }\n\n      const exceptionList = errorToExceptionList(error, this.stringRedactor ?? undefined);\n      const formatted = formatExceptionForPostHog(exceptionList, 'node:javascript', this.stringRedactor ?? undefined);\n\n      const properties = {\n        ...formatted,\n        ...options?.additionalProperties,\n      };\n\n      this.posthog?.capture({\n        distinctId: options?.distinctId || 'anonymous',\n        event: '$exception',\n        properties: this.redactProperties(properties),\n      });\n    } catch (error_) {\n      this.config.onError?.(error_ as Error);\n    }\n  }\n\n  /**\n   * Flush pending events and clean up resources\n   */\n  async shutdown(): Promise<void> {\n    await super.shutdown(); // Drain pending requests first\n    await this.ensureInitialized();\n\n    if (this.posthog) {\n      try {\n        await this.posthog.shutdown();\n      } catch (error) {\n        this.config.onError?.(error as Error);\n      }\n    }\n  }\n\n  /**\n   * Handle errors with custom error handler\n   */\n  protected handleError(error: Error, payload: EventPayload): void {\n    // Call basic onError if provided\n    this.config.onError?.(error);\n\n    // Call enhanced onErrorWithContext if provided\n    if (this.config.onErrorWithContext) {\n      this.config.onErrorWithContext({\n        error,\n        eventName: payload.name,\n        eventType: payload.type,\n        attributes: payload.attributes,\n        subscriberName: this.name,\n      });\n    }\n\n    super.handleError(error, payload);\n  }\n}\n"],"mappings":"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"}