UNPKG

posthog-js

Version:

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

478 lines • 21.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PostHogConversations = void 0; var constants_1 = require("../../constants"); var globals_1 = require("../../utils/globals"); var logger_1 = require("@posthog/browser-common/utils/logger"); var core_1 = require("@posthog/core"); var general_utils_1 = require("@posthog/browser-common/utils/general-utils"); var logger = (0, logger_1.createLogger)('[Conversations]'); var NOT_AVAILABLE = 'Conversations not available yet.'; var PostHogConversations = /** @class */ (function () { function PostHogConversations(_instance) { this._instance = _instance; // This is set to undefined until the remote config is loaded // then it's set to true if conversations are enabled // or false if conversations are disabled in the project settings this._isConversationsEnabled = undefined; this._conversationsManager = null; this._isInitializing = false; this._remoteConfig = null; // Set when the lazy bundle fails to load or init (blocked, network, CSP, init throw); cleared on // successful load. Distinguishes a load failure from "still initializing" in getUnavailableReason(). // A boolean rather than the message: only the fact of failure is read, and this class lands in the // non-lazy bundle. The message is already logged at the point of failure. this._loadFailed = false; } PostHogConversations.prototype.initialize = function () { this.loadIfEnabled(); }; PostHogConversations.prototype.onRemoteConfig = function (result) { // Don't load conversations if disabled via config if (this._instance.config.disable_conversations) { return; } this._remoteConfigSuccessful = result.ok; if (!result.ok) { return; } var conversations = result.config['conversations']; if ((0, core_1.isNullish)(conversations)) { return; } // Handle both boolean and object response if ((0, core_1.isBoolean)(conversations)) { this._isConversationsEnabled = conversations; } else { // It's a ConversationsRemoteConfig object this._isConversationsEnabled = conversations.enabled; this._remoteConfig = conversations; } this.loadIfEnabled(); }; PostHogConversations.prototype.reset = function () { var _a; // Delegate cleanup to the lazy-loaded manager (which knows about persistence keys) // If not loaded, there's nothing to reset anyway (_a = this._conversationsManager) === null || _a === void 0 ? void 0 : _a.reset(); this._conversationsManager = null; // Reset local state this._isConversationsEnabled = undefined; this._remoteConfig = null; this._remoteConfigSuccessful = undefined; this._loadFailed = false; }; PostHogConversations.prototype.loadIfEnabled = function () { var _this = this; if (this._conversationsManager) { return; } if (this._isInitializing) { return; } if (this._instance.config.disable_conversations) { return; } // The toolbar's internal PostHog instance must not own the conversations // manager — its distinct_id is always anonymous and would be sent instead // of the identified user's ID. if ((0, general_utils_1.isToolbarInstance)(this._instance.config)) { return; } if (this._instance.config.cookieless_mode && this._instance.consent.isOptedOut()) { return; } var phExtensions = globals_1.assignableWindow === null || globals_1.assignableWindow === void 0 ? void 0 : globals_1.assignableWindow.__PosthogExtensions__; if (!phExtensions) { return; } // Wait for remote config to load if ((0, core_1.isUndefined)(this._isConversationsEnabled)) { return; } // Check if conversations are enabled if (!this._isConversationsEnabled) { return; } // Check if we have the required config if (!this._remoteConfig || !this._remoteConfig.token) { logger.error('Conversations enabled but missing token in remote config.'); return; } // Note: Domain check is done in ConversationsManager for widget rendering // The conversations API is loaded regardless of domain restrictions this._isInitializing = true; try { var initConversations = phExtensions.initConversations; if (initConversations) { // Conversations code is already loaded this._completeInitialization(initConversations); this._isInitializing = false; return; } // If we reach here, conversations code is not loaded yet var loadExternalDependency = phExtensions.loadExternalDependency; if (!loadExternalDependency) { this._handleLoadError(constants_1.LOAD_EXT_NOT_FOUND); return; } // Load the conversations bundle loadExternalDependency(this._instance, 'conversations', function (err) { if (err || !phExtensions.initConversations) { _this._handleLoadError('Could not load conversations script', err); } else { _this._completeInitialization(phExtensions.initConversations); } _this._isInitializing = false; }); } catch (e) { this._handleLoadError('Error initializing conversations', e); this._isInitializing = false; } }; /** Helper to finalize conversations initialization */ PostHogConversations.prototype._completeInitialization = function (initConversationsFn) { if (!this._remoteConfig) { logger.error('Cannot complete initialization: remote config is null'); return; } try { // Pass config and PostHog instance to the extension this._conversationsManager = initConversationsFn(this._remoteConfig, this._instance); this._loadFailed = false; logger.info('Conversations loaded successfully'); } catch (e) { this._handleLoadError('Error completing conversations initialization', e); } }; /** Helper to handle initialization errors */ PostHogConversations.prototype._handleLoadError = function (message, error) { logger.error(message, error); this._conversationsManager = null; this._isInitializing = false; this._loadFailed = true; }; /** * Show the conversations widget (button and chat panel) */ PostHogConversations.prototype.show = function () { if (!this._conversationsManager) { logger.warn('Conversations not loaded yet.'); return; } this._conversationsManager.show(); }; /** * Hide the conversations widget completely (button and chat panel) */ PostHogConversations.prototype.hide = function () { if (!this._conversationsManager) { return; } this._conversationsManager.hide(); }; /** * Check if conversations are available (enabled in remote config AND loaded) * Use this to check if conversations API can be used. */ PostHogConversations.prototype.isAvailable = function () { return this._isConversationsEnabled === true && !(0, core_1.isNull)(this._conversationsManager); }; /** * Explain why {@link isAvailable} is currently false, or null when conversations are available. * Lets a caller that falls back to another channel record the specific cause (bundle blocked, * disabled in the project, remote config not loaded yet, still initializing, …) rather than * collapsing every case into a single "unavailable" signal. */ PostHogConversations.prototype.getUnavailableReason = function () { if (this.isAvailable()) { return null; } if (this._instance.config.disable_conversations) { return 'disabled_by_config'; } // Mirrors loadIfEnabled's toolbar bail: the toolbar's internal instance never owns the // conversations manager, so its unavailability is deliberate rather than a failure. if ((0, general_utils_1.isToolbarInstance)(this._instance.config)) { return 'disabled_for_toolbar'; } if (this._instance.config.cookieless_mode && this._instance.consent.isOptedOut()) { return 'consent_opted_out'; } // Deliberately diverges from loadIfEnabled, which checks __PosthogExtensions__ before remote // config. Until remote config settles we do not know conversations was ever meant to load, so // reporting a missing extension bundle then would read as an ad blocker during normal startup. if (this._remoteConfigSuccessful === false) { return 'remote_config_failed'; } if ((0, core_1.isUndefined)(this._isConversationsEnabled)) { return this._remoteConfigSuccessful ? 'disabled_in_project' : 'remote_config_pending'; } if (!this._isConversationsEnabled) { return 'disabled_in_project'; } if ((0, core_1.isNullish)(this._remoteConfig) || !this._remoteConfig.token) { return 'missing_token'; } if (!(globals_1.assignableWindow === null || globals_1.assignableWindow === void 0 ? void 0 : globals_1.assignableWindow.__PosthogExtensions__)) { return 'extensions_unavailable'; } if (this._isInitializing) { return 'initializing'; } if (this._loadFailed) { return 'load_failed'; } return 'not_loaded'; }; /** * Check if the widget is currently visible (rendered and shown) */ PostHogConversations.prototype.isVisible = function () { var _a, _b; return (_b = (_a = this._conversationsManager) === null || _a === void 0 ? void 0 : _a.isVisible()) !== null && _b !== void 0 ? _b : false; }; /** * Send a message programmatically * 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 (starts new conversation) * @returns Promise with response or null if conversations not available yet * @note Conversations must be available first (check with isAvailable()) * * @example * // Send to existing or create new conversation * const response = await posthog.conversations.sendMessage('Hello!', { * name: 'John Doe', * email: 'john@example.com' * }) * * @example * // Force creation of a new conversation/ticket * const newConvo = await posthog.conversations.sendMessage('Start fresh', undefined, true) */ PostHogConversations.prototype.sendMessage = function (message, userTraits, newTicket) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { if (!this._conversationsManager) { logger.warn(NOT_AVAILABLE); return [2 /*return*/, null]; } return [2 /*return*/, this._conversationsManager.sendMessage(message, userTraits, newTicket)]; }); }); }; /** * Get messages for the current or specified ticket * * @param ticketId - Optional ticket ID (defaults to current active ticket) * @param after - Optional ISO timestamp to fetch messages after * @returns Promise with messages response or null if conversations not available yet * @note Conversations must be available first (check with isAvailable()) * * @example * // Get messages for current ticket * const messages = await posthog.conversations.getMessages() * * // Get messages for specific ticket * const messages = await posthog.conversations.getMessages('ticket-uuid') */ PostHogConversations.prototype.getMessages = function (ticketId, after) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { if (!this._conversationsManager) { logger.warn(NOT_AVAILABLE); return [2 /*return*/, null]; } return [2 /*return*/, this._conversationsManager.getMessages(ticketId, after)]; }); }); }; /** * Mark messages as read for the current or specified ticket * * @param ticketId - Optional ticket ID (defaults to current active ticket) * @returns Promise with response or null if conversations not available yet * @note Conversations must be available first (check with isAvailable()) * * @example * await posthog.conversations.markAsRead() */ PostHogConversations.prototype.markAsRead = function (ticketId) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { if (!this._conversationsManager) { logger.warn(NOT_AVAILABLE); return [2 /*return*/, null]; } return [2 /*return*/, this._conversationsManager.markAsRead(ticketId)]; }); }); }; /** * Get list of tickets for the current widget session * * @param options - Optional filtering and pagination options * @returns Promise with tickets response or null if conversations not available yet * @note Conversations must be available first (check with isAvailable()) * * @example * const tickets = await posthog.conversations.getTickets({ * limit: 10, * offset: 0, * status: 'open' * }) */ PostHogConversations.prototype.getTickets = function (options) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { if (!this._conversationsManager) { logger.warn(NOT_AVAILABLE); return [2 /*return*/, null]; } return [2 /*return*/, this._conversationsManager.getTickets(options)]; }); }); }; /** * Request a restore link email for previous conversations. * * @param email - Email address associated with previous conversations * @returns Promise with generic success response or null if conversations unavailable */ PostHogConversations.prototype.requestRestoreLink = function (email) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { if (!this._conversationsManager) { logger.warn(NOT_AVAILABLE); return [2 /*return*/, null]; } return [2 /*return*/, this._conversationsManager.requestRestoreLink(email)]; }); }); }; /** * Redeem a restore token and relink eligible tickets to this browser session. * * @param restoreToken - Opaque restore token from restore email link * @returns Promise with restore status or null if conversations unavailable */ PostHogConversations.prototype.restoreFromToken = function (restoreToken) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { if (!this._conversationsManager) { logger.warn(NOT_AVAILABLE); return [2 /*return*/, null]; } return [2 /*return*/, this._conversationsManager.restoreFromToken(restoreToken)]; }); }); }; /** * Parse and redeem `ph_conv_restore` token from the current URL. * * @returns Promise with restore status, or null when no token/conversations unavailable */ PostHogConversations.prototype.restoreFromUrlToken = function () { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { if (!this._conversationsManager) { logger.warn(NOT_AVAILABLE); return [2 /*return*/, null]; } return [2 /*return*/, this._conversationsManager.restoreFromUrlToken()]; }); }); }; /** * Get the current active ticket ID * Returns null if no conversation has been started yet or if not available * * @returns Ticket ID or null * @note Safe to call before conversations are available, will return null * * @example * const ticketId = posthog.conversations.getCurrentTicketId() * if (ticketId) { * console.log('Current ticket ID:', ticketId) * } */ PostHogConversations.prototype.getCurrentTicketId = function () { var _a, _b; return (_b = (_a = this._conversationsManager) === null || _a === void 0 ? void 0 : _a.getCurrentTicketId()) !== null && _b !== void 0 ? _b : null; }; /** * Get the widget session ID (persistent browser identifier) * This ID is used for access control and stays the same across page loads * Returns null if conversations not available yet * * @returns Session ID or null if not available * @note Safe to call before conversations are available, will return null * * @example * const sessionId = posthog.conversations.getWidgetSessionId() * if (!sessionId) { * // Conversations not available yet * posthog.conversations.show() * } */ PostHogConversations.prototype.getWidgetSessionId = function () { var _a, _b; return (_b = (_a = this._conversationsManager) === null || _a === void 0 ? void 0 : _a.getWidgetSessionId()) !== null && _b !== void 0 ? _b : null; }; /** @internal Called by PostHog.setIdentity() -- forwards to the manager without recursing */ PostHogConversations.prototype._onIdentityChanged = function () { var _a; (_a = this._conversationsManager) === null || _a === void 0 ? void 0 : _a.setIdentity(); }; /** @internal Called by PostHog.clearIdentity() -- forwards to the manager without recursing */ PostHogConversations.prototype._onIdentityCleared = function () { var _a; (_a = this._conversationsManager) === null || _a === void 0 ? void 0 : _a.clearIdentity(); }; return PostHogConversations; }()); exports.PostHogConversations = PostHogConversations; //# sourceMappingURL=posthog-conversations.js.map