UNPKG

posthog-js

Version:

Posthog-js allows you to automatically capture usage and send events to PostHog.

274 lines (273 loc) • 9.91 kB
import { ConversationsRemoteConfig, UserProvidedTraits, SendMessageResponse, GetMessagesResponse, MarkAsReadResponse, GetTicketsOptions, GetTicketsResponse, RestoreFromTokenResponse, RequestRestoreLinkResponse } from '../../../posthog-conversations-types'; import { PostHog } from '../../../posthog-core'; import { ConversationsManager as ConversationsManagerInterface } from '../posthog-conversations'; export declare class ConversationsManager implements ConversationsManagerInterface { private readonly _posthog; private _config; private _persistence; private _widgetRef; private _containerElement; private _currentTicketId; private _pollTimeoutId; private _pollLoopRunning; private _pollGeneration; private _consecutivePollingRateLimitFailures; private _reattachIntervalId; private _lastMessageTimestamp; private _isPollingMessages; private _isPollingTickets; private _unsubscribeIdentifyListener; private _unreadCount; private _widgetSessionId; private _isWidgetEnabled; private _isDomainAllowed; private _widgetState; private _isWidgetRendered; private _hasProcessedRestoreToken; private _initializeWidgetPromise; private _lifecycleGeneration; private _currentView; private _tickets; private _showTicketList; private _consecutivePollingStatusZeroFailures; constructor(config: ConversationsRemoteConfig, _posthog: PostHog); private _onOnline; private _currentUrl; /** * Send a message programmatically via the API * Creates a new ticket if none exists or if newTicket is true * * @param message - The message text to send * @param userTraits - Optional user identification data (name, email) * @param newTicket - If true, forces creation of a new ticket (ignores current ticket) * @returns Promise with the response including ticket_id and message_id */ sendMessage(message: string, userTraits?: UserProvidedTraits, newTicket?: boolean): Promise<SendMessageResponse>; /** * Switch to a different ticket if an explicit ticketId is provided * This ensures subsequent operations (sendMessage, etc.) use the correct ticket */ private _switchToTicketIfNeeded; /** Fetch messages via the API */ getMessages(ticketId?: string, after?: string): Promise<GetMessagesResponse>; /** Mark messages as read via the API */ markAsRead(ticketId?: string): Promise<MarkAsReadResponse>; /** * Initialize the conversations manager. * Always initializes persistence and event listeners for API usage. * Only renders the widget if widgetEnabled is true AND domain is allowed. */ private _initialize; private _completeInitialization; private _restoreFromTokenWithRetry; private _restoreFromToken; /** * Initialize and render the widget UI * Uses a promise guard to prevent race conditions from concurrent calls */ private _initializeWidget; private _doInitializeWidget; /** * Extract name and email from PostHog's stored person properties. * * Person properties set via posthog.identify() are stored under the * $stored_person_properties persistence key, not as top-level props. * We check both locations plus the super-properties for completeness. */ private _getPersonTraits; /** * Get initial user traits from PostHog or localStorage */ private _getInitialUserTraits; /** * Handle user identification from the widget form */ private _handleIdentify; private _handleRequestRestoreLink; /** * Handle sending a message from the widget */ private _handleSendMessage; /** * Handle widget state changes */ private _handleStateChange; /** * Mark messages as read */ private _markMessagesAsRead; /** * Load messages for the current ticket */ private _loadMessages; private _isWidgetOpen; /** * Poll for new messages */ private _pollMessages; /** * Poll for tickets list */ private _pollTickets; /** * Load tickets list from API */ private _loadTickets; private _computeShowTicketList; private _isCurrentTicketResolved; /** * Patch the local _tickets cache with a new status for a given ticket and push * any UI-relevant changes (resolved lock + list visibility) to the widget. */ private _applyTicketStatusUpdate; /** * Main poll function that polls based on current view */ private _poll; private _trackPollingEndpointReachability; /** * Track HTTP 429s on polling requests so the loop can back off. Any 2xx * clears the streak; a 429 lengthens the next poll delay exponentially. */ private _trackPollingRateLimit; /** * Handle view changes from the widget */ private _handleViewChange; /** * Handle ticket selection from the list */ private _handleSelectTicket; /** * Handle new conversation request */ private _handleNewConversation; /** * Handle back to tickets request */ private _handleBackToTickets; /** * Determine initial view based on ticket count */ private _determineInitialView; /** * Apply a fetched ticket list to internal state and return the appropriate view. * Shared by _determineInitialView (widget boot) and _loadTicketsAndReconcileView * (identity change at runtime). */ private _applyTicketsToState; /** * Start the self-scheduling poll loop. * * Idempotent: safe to call from any state-changing handler (send, restore, * identity change, ticket selection). The loop polls immediately, then * reschedules itself with a delay derived from widget state and rate-limit * backoff (see _nextPollDelayMs), and stops itself when there is nothing to * poll (see _hasSomethingToPoll). */ private _startPolling; private _pollThenSchedule; /** * Schedule the next poll, or stop the loop when there is nothing to poll. * An idle widget with no conversation generates zero polling traffic. */ private _scheduleNextPoll; /** * Delay before the next poll: exponential backoff while rate limited, * otherwise the open/closed cadence. */ private _nextPollDelayMs; /** * Whether the current view has anything worth polling for. Messages view * needs an active ticket; tickets view needs at least one ticket. The * restore-request view never polls. */ private _hasSomethingToPoll; /** * Stop the poll loop. */ private _stopPolling; /** * Re-attach the widget container if it has been detached from the DOM. * * SPA frameworks such as Turbo Drive (Hotwire) and Rails Turbo replace the * entire document.body on navigation. That removes our container — which was * appended to body in _renderWidget — without any teardown call, so the * widget silently disappears until a full page reload. Re-attaching the same * element (rather than re-rendering) preserves widget state and avoids * refetching messages. */ private _reattachWidgetIfDetached; /** * Start watching for the widget being detached from the DOM (e.g. by an SPA * navigation that replaces document.body) and re-attach it when it happens. */ private _startReattachWatcher; /** * Stop the DOM re-attach watcher. */ private _stopReattachWatcher; /** * Setup listener for identify events. * When user calls posthog.identify(), hide the identification form * since we now know who they are. */ private _setupIdentifyListener; /** * Show the widget (render it to DOM). * The widget respects its saved state (open/closed). * Note: Domain restrictions still apply - widget won't render on disallowed domains. */ show(): void; /** * Hide and remove the widget from the DOM. * Conversation data is preserved - call show() to re-render. */ hide(): void; /** * Check if the widget is currently visible (rendered in DOM) */ isVisible(): boolean; /** Get tickets list for the current widget session or verified identity */ getTickets(options?: GetTicketsOptions): Promise<GetTicketsResponse>; requestRestoreLink(email: string): Promise<RequestRestoreLinkResponse>; restoreFromToken(restoreToken: string): Promise<RestoreFromTokenResponse>; restoreFromUrlToken(): Promise<RestoreFromTokenResponse | null>; /** * Get the current active ticket ID * Returns null if no conversation has been started yet */ getCurrentTicketId(): string | null; /** * Get the widget session ID (persistent browser identifier) * This ID is used for access control and stays the same across page loads */ getWidgetSessionId(): string; private _identityFields; setIdentity(): void; clearIdentity(): void; private _resetConversationState; private _loadTicketsAndReconcileView; /** * Clean up the widget */ destroy(): void; /** * Reset all conversation data and destroy the widget. * Called on posthog.reset() to start fresh. */ reset(): void; /** * Render the widget to the DOM */ private _renderWidget; } /** * Initialize the conversations widget. * This is the entry point called from the lazy-loaded bundle. * * Singleton guard: only one ConversationsManager per page. The toolbar's * internal PostHog instance is excluded upstream (see loadIfEnabled), so * this always belongs to the customer's main instance. */ export declare function initConversations(config: ConversationsRemoteConfig, posthog: PostHog): ConversationsManager;