UNPKG

posthog-js

Version:

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

258 lines • 11.3 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConversationsPersistence = void 0; var constants_1 = require("../../../constants"); var logger_1 = require("@posthog/browser-common/utils/logger"); var globals_1 = require("@posthog/browser-common/utils/globals"); var uuidv7_1 = require("@posthog/browser-common/utils/uuidv7"); var logger = (0, logger_1.createLogger)('[ConversationsPersistence]'); /** * Dedicated localStorage key scoped to the PostHog project token. * Format: `ph_conv_<token>` */ function storageKey(posthog) { var _a; var token = (_a = posthog.config) === null || _a === void 0 ? void 0 : _a.token; return token ? 'ph_conv_' + token : null; } /** * ConversationsPersistence manages conversation data in its own dedicated * localStorage entry, independent of PostHog's core persistence layer. * * This avoids a known issue where PostHog's persistence.props can lose data * when the cookie+localStorage merge in _parse() fails on large entries. * * Pattern follows toolbar and surveys extensions which also use dedicated * localStorage keys. */ var ConversationsPersistence = /** @class */ (function () { function ConversationsPersistence(_posthog) { this._posthog = _posthog; this._cachedWidgetSessionId = null; this._storageKey = storageKey(_posthog); this._migrateFromLegacyPersistence(); } /** * Get or create the widget session ID (random UUID for access control). * This ID is generated once per browser and persists across sessions. * It is NOT tied to distinct_id - it stays the same even when user identifies. * * SECURITY: This is the key for access control. Only the browser that created * the widget_session_id can access tickets associated with it. */ ConversationsPersistence.prototype.getOrCreateWidgetSessionId = function () { var _a; if (this._cachedWidgetSessionId) { return this._cachedWidgetSessionId; } var sessionId = (_a = this._read()) === null || _a === void 0 ? void 0 : _a.widgetSessionId; if (!sessionId) { sessionId = (0, uuidv7_1.uuidv7)(); this._write({ widgetSessionId: sessionId }); } this._cachedWidgetSessionId = sessionId; return sessionId; }; /** * Overwrite the widget session ID (used by restore flow). */ ConversationsPersistence.prototype.setWidgetSessionId = function (id) { this._cachedWidgetSessionId = id; var data = this._read() || {}; this._write(__assign(__assign({}, data), { widgetSessionId: id })); }; /** * Clear the widget session ID (called on posthog.reset()). * This will create a new session and lose access to previous tickets. */ ConversationsPersistence.prototype.clearWidgetSessionId = function () { this._cachedWidgetSessionId = null; var data = this._read(); if (data) { delete data.widgetSessionId; this._write(data); } }; ConversationsPersistence.prototype.saveTicketId = function (ticketId) { var data = this._read() || {}; this._write(__assign(__assign({}, data), { ticketId: ticketId })); }; ConversationsPersistence.prototype.loadTicketId = function () { var _a; return ((_a = this._read()) === null || _a === void 0 ? void 0 : _a.ticketId) || null; }; ConversationsPersistence.prototype.clearTicketId = function () { var data = this._read(); if (data) { delete data.ticketId; this._write(data); } }; ConversationsPersistence.prototype.saveWidgetState = function (state) { var data = this._read() || {}; this._write(__assign(__assign({}, data), { widgetState: state })); }; ConversationsPersistence.prototype.loadWidgetState = function () { var _a; var state = (_a = this._read()) === null || _a === void 0 ? void 0 : _a.widgetState; return state === 'open' || state === 'closed' ? state : null; }; ConversationsPersistence.prototype.saveUserTraits = function (traits) { var data = this._read() || {}; this._write(__assign(__assign({}, data), { userTraits: traits })); }; ConversationsPersistence.prototype.loadUserTraits = function () { var _a; var traits = (_a = this._read()) === null || _a === void 0 ? void 0 : _a.userTraits; return traits && (traits.name || traits.email) ? traits : null; }; ConversationsPersistence.prototype.clearUserTraits = function () { var data = this._read(); if (data) { delete data.userTraits; this._write(data); } }; ConversationsPersistence.prototype.clearAll = function () { var _a; this._cachedWidgetSessionId = null; if (this._storageKey) { try { (_a = globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.localStorage) === null || _a === void 0 ? void 0 : _a.removeItem(this._storageKey); } catch (_b) { logger.error('Failed to remove localStorage item'); } } }; ConversationsPersistence.prototype._read = function () { var _a; if (!this._storageKey) { return null; } try { var raw = (_a = globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.localStorage) === null || _a === void 0 ? void 0 : _a.getItem(this._storageKey); return raw ? JSON.parse(raw) : null; } catch (_b) { return null; } }; ConversationsPersistence.prototype._write = function (data) { var _a; if (!this._storageKey) { return; } try { (_a = globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.localStorage) === null || _a === void 0 ? void 0 : _a.setItem(this._storageKey, JSON.stringify(data)); } catch (error) { logger.error('Failed to write to localStorage', error); } }; /** * One-time migration: copy conversations data from PostHog's main * persistence blob into the dedicated localStorage key, then remove * the old keys from PostHog persistence so they stop bloating it. */ ConversationsPersistence.prototype._migrateFromLegacyPersistence = function () { var _a, _b; if (!this._storageKey || ((_a = this._read()) === null || _a === void 0 ? void 0 : _a.widgetSessionId)) { return; } try { var persistence = this._posthog.persistence; if (!persistence || ((_b = persistence.isDisabled) === null || _b === void 0 ? void 0 : _b.call(persistence))) { return; } var widgetSessionId = persistence.get_property(constants_1.CONVERSATIONS_LEGACY_WIDGET_SESSION_ID); if (!widgetSessionId) { // persistence.props may be empty (the bug) — try raw localStorage var legacyFromRaw = this._readLegacyFromRawStorage(); if (legacyFromRaw) { this._write(legacyFromRaw); logger.info('Migrated conversations data from raw localStorage'); } return; } var data = { widgetSessionId: widgetSessionId }; var ticketId = persistence.get_property(constants_1.CONVERSATIONS_LEGACY_TICKET_ID); if (ticketId) { data.ticketId = ticketId; } var widgetState = persistence.get_property(constants_1.CONVERSATIONS_LEGACY_WIDGET_STATE); if (widgetState === 'open' || widgetState === 'closed') { data.widgetState = widgetState; } var userTraits = persistence.get_property(constants_1.CONVERSATIONS_LEGACY_USER_TRAITS); if (userTraits) { data.userTraits = userTraits; } this._write(data); persistence.unregister(constants_1.CONVERSATIONS_LEGACY_WIDGET_SESSION_ID); persistence.unregister(constants_1.CONVERSATIONS_LEGACY_TICKET_ID); persistence.unregister(constants_1.CONVERSATIONS_LEGACY_WIDGET_STATE); persistence.unregister(constants_1.CONVERSATIONS_LEGACY_USER_TRAITS); logger.info('Migrated conversations data to dedicated storage'); } catch (error) { logger.error('Migration from legacy persistence failed', error); } }; /** * Fallback for migration: read legacy keys directly from raw localStorage * when PostHog persistence.props didn't load them (the original bug). */ ConversationsPersistence.prototype._readLegacyFromRawStorage = function () { var _a, _b; try { var token = (_a = this._posthog.config) === null || _a === void 0 ? void 0 : _a.token; if (!token) { return null; } var key = this._posthog.config.persistence_name ? 'ph_' + this._posthog.config.persistence_name : 'ph_' + token + '_posthog'; var raw = (_b = globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.localStorage) === null || _b === void 0 ? void 0 : _b.getItem(key); if (!raw) { return null; } var parsed = JSON.parse(raw); var widgetSessionId = parsed === null || parsed === void 0 ? void 0 : parsed[constants_1.CONVERSATIONS_LEGACY_WIDGET_SESSION_ID]; if (typeof widgetSessionId !== 'string' || !widgetSessionId) { return null; } var data = { widgetSessionId: widgetSessionId }; var ticketId = parsed === null || parsed === void 0 ? void 0 : parsed[constants_1.CONVERSATIONS_LEGACY_TICKET_ID]; if (ticketId) { data.ticketId = ticketId; } var widgetState = parsed === null || parsed === void 0 ? void 0 : parsed[constants_1.CONVERSATIONS_LEGACY_WIDGET_STATE]; if (widgetState === 'open' || widgetState === 'closed') { data.widgetState = widgetState; } var userTraits = parsed === null || parsed === void 0 ? void 0 : parsed[constants_1.CONVERSATIONS_LEGACY_USER_TRAITS]; if (userTraits) { data.userTraits = userTraits; } return data; } catch (_c) { return null; } }; return ConversationsPersistence; }()); exports.ConversationsPersistence = ConversationsPersistence; //# sourceMappingURL=persistence.js.map