UNPKG

posthog-js

Version:

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

480 lines • 19.1 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); }; var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; Object.defineProperty(exports, "__esModule", { value: true }); var globals_1 = require("../utils/globals"); var patch_1 = require("../extensions/replay/rrweb-plugins/patch"); var console_original_1 = require("../utils/console-original"); var extension_tokens_1 = require("../extension-tokens"); var core_1 = require("@posthog/core"); var LOG_BODY_SIZE_LIMIT = 10000; var LOG_ATTRIBUTES_LIMIT = 50; var appendWithLimit = function (parts, text, budget) { if (budget.remaining <= 0) { budget.truncated = true; return false; } if (text.length <= budget.remaining) { parts.push(text); budget.remaining -= text.length; return true; } parts.push(text.slice(0, budget.remaining)); budget.remaining = 0; budget.truncated = true; return false; }; var stringifyStringWithLimit = function (value, parts, budget) { var serialized = JSON.stringify(value); if (serialized.length <= budget.remaining) { return appendWithLimit(parts, serialized, budget); } budget.truncated = true; if (budget.remaining < 2) { return false; } var low = 0; var high = Math.min(value.length, budget.remaining - 2); while (low < high) { var mid = Math.ceil((low + high) / 2); if (JSON.stringify(value.slice(0, mid)).length <= budget.remaining) { low = mid; } else { high = mid - 1; } } return appendWithLimit(parts, JSON.stringify(value.slice(0, low)), budget); }; var isJSONSerializablePrimitive = function (value) { return typeof value !== 'undefined' && typeof value !== 'function' && typeof value !== 'symbol' && typeof value !== 'bigint'; }; var isNumberOrBoolean = function (value) { try { return (0, core_1.isNumber)(value) || (0, core_1.isBoolean)(value); } catch (_a) { return false; } }; var collectAttributeValue = function (key, value, collector) { if (collector.truncated) { return; } collector.keysRemaining -= 1; collector.sizeRemaining -= String(value).length + key.length; if (collector.keysRemaining <= 0 || collector.sizeRemaining <= 0) { collector.truncated = true; collector.result['attributes_truncated'] = true; return; } collector.result[key] = value; }; var collectFlattenedAttributes = function (value, key, collector) { if (collector.truncated) { return; } if ((0, core_1.isObject)(value)) { if (collector.seen.has(value)) { collectAttributeValue(key || 'circular', '[Circular]', collector); return; } collector.seen.add(value); try { for (var childKey in value) { try { if (!Object.prototype.hasOwnProperty.call(value, childKey)) { continue; } var childValue = value[childKey]; collectFlattenedAttributes(childValue, key ? "".concat(key, ".").concat(childKey) : childKey, collector); if (collector.truncated) { return; } } catch (_a) { continue; } } } catch (_b) { // we'll omit this object's properties considering we can't enumerate them } return; } collectAttributeValue(key, value, collector); }; var stringifyValueWithLimit = function (value, parts, budget, seen, inArray, attributeCollector) { if (inArray === void 0) { inArray = false; } if (!isJSONSerializablePrimitive(value)) { return inArray ? appendWithLimit(parts, 'null', budget) : true; } if ((0, core_1.isNull)(value) || isNumberOrBoolean(value)) { return appendWithLimit(parts, JSON.stringify(value), budget); } if (typeof value === 'string') { return stringifyStringWithLimit(value, parts, budget); } if (!(0, core_1.isObject)(value) && !(0, core_1.isArray)(value)) { return appendWithLimit(parts, JSON.stringify(value), budget); } if (seen.has(value)) { return stringifyStringWithLimit('[Circular]', parts, budget); } seen.add(value); try { var toJSON = value.toJSON; if ((0, core_1.isFunction)(toJSON)) { return stringifyValueWithLimit(toJSON.call(value), parts, budget, seen, inArray); } } catch (_a) { // If toJSON can't be read or throws, fall through to safe property enumeration. } try { var objectTag = Object.prototype.toString.call(value); if (objectTag === '[object String]') { return stringifyStringWithLimit(String(value.valueOf()), parts, budget); } if (objectTag === '[object Number]' || objectTag === '[object Boolean]') { return appendWithLimit(parts, JSON.stringify(value.valueOf()), budget); } } catch (_b) { // If Object.prototype.toString or valueOf throws, fall through to safe property enumeration. } if (value instanceof Error) { var errorObject = {}; try { for (var key in value) { if (Object.prototype.hasOwnProperty.call(value, key)) { errorObject[key] = value[key]; } } } catch (_c) { } try { errorObject.name = value.name; } catch (_d) { } try { errorObject.message = value.message; } catch (_e) { } try { errorObject.stack = value.stack; } catch (_f) { } return stringifyValueWithLimit(errorObject, parts, budget, seen, inArray, attributeCollector); } if ((0, core_1.isArray)(value)) { if (!appendWithLimit(parts, '[', budget)) { return false; } for (var i = 0; i < value.length; i++) { if (i > 0 && !appendWithLimit(parts, ',', budget)) { return false; } var item = void 0; try { item = value[i]; } catch (_g) { item = undefined; } if (!stringifyValueWithLimit(item, parts, budget, seen, true)) { return false; } } return appendWithLimit(parts, ']', budget); } if (!appendWithLimit(parts, '{', budget)) { return false; } var isFirst = true; try { for (var key in value) { if (!Object.prototype.hasOwnProperty.call(value, key)) { continue; } if (budget.remaining <= 0) { budget.truncated = true; return false; } var propertyValue = void 0; try { propertyValue = value[key]; } catch (_h) { continue; } if (attributeCollector) { try { collectFlattenedAttributes(propertyValue, key, attributeCollector); } catch (_j) { // we'll omit this object's attributes considering we can't read them safely } } if (!isJSONSerializablePrimitive(propertyValue)) { continue; } var propertyPrefix = "".concat(isFirst ? '' : ',').concat(JSON.stringify(key), ":"); if (propertyPrefix.length >= budget.remaining) { budget.truncated = true; return false; } var partsBeforeProperty = parts.length; var remainingBeforeProperty = budget.remaining; var truncatedBeforeProperty = budget.truncated; if (!appendWithLimit(parts, propertyPrefix, budget)) { return false; } var partsBeforeValue = parts.length; var serialized = stringifyValueWithLimit(propertyValue, parts, budget, seen, false); var truncatedAfterValue = budget.truncated; if (parts.length === partsBeforeValue) { parts.length = partsBeforeProperty; budget.remaining = remainingBeforeProperty; budget.truncated = serialized ? truncatedBeforeProperty : truncatedAfterValue; if (!serialized) { return false; } continue; } isFirst = false; if (!serialized) { return false; } } } catch (_k) { // we'll omit this object's properties considering we can't enumerate them } return appendWithLimit(parts, '}', budget); }; var stringifyArgsSafely = function (args, sizeLimit) { var parts = []; var budget = { remaining: sizeLimit, truncated: false }; var attributeCollector = (0, core_1.isObject)(args[0]) ? { result: {}, keysRemaining: LOG_ATTRIBUTES_LIMIT, sizeRemaining: LOG_BODY_SIZE_LIMIT, truncated: false, seen: new WeakSet([args[0]]), } : undefined; for (var i = 0; i < args.length; i++) { if (i > 0 && !appendWithLimit(parts, ' ', budget)) { break; } if (!stringifyValueWithLimit(args[i], parts, budget, new WeakSet(), false, i === 0 ? attributeCollector : undefined)) { break; } } return { body: parts.join('') + (budget.truncated ? '...' : ''), truncated: budget.truncated, attributes: (attributeCollector === null || attributeCollector === void 0 ? void 0 : attributeCollector.result) || {}, }; }; // Console method → OTLP severity level. `log` and `info` both map to `info`; // the originating method is preserved separately via the `log.source` attribute. var LEVEL_MAP = { debug: 'debug', log: 'info', warn: 'warn', error: 'error', info: 'info', }; var isClient = function (host) { return 'canCapture' in host; }; var getCapturingLogs = function (host) { if (isClient(host)) { return host.canCapture ? host.getExtension(extension_tokens_1.LogsExtension) : undefined; } return host.is_capturing() ? host.logs : undefined; }; // Compatibility for a bug where `_captureConsoleLog` was inadvertently used across // independently built bundles and received different mangled names. Pre-stable-ABI // cores used `le` through 1.410.4, `de` through 1.410.10, `he` through 1.418.3, `ui` // through 1.418.10, `ci` through 1.418.14, and `vi` through 1.419.2. var historicalCaptureConsoleLogName = function (version) { var match = /^1\.(\d+)\.(\d+)$/.exec(version); if (!match) { return undefined; } var minor = Number(match[1]); var patch = Number(match[2]); if (minor < 392 || minor > 419) { return undefined; } if (minor === 410) { return patch <= 4 ? 'le' : patch <= 10 ? 'de' : undefined; } if (minor === 418) { return patch <= 3 ? 'he' : patch <= 10 ? 'ui' : patch <= 14 ? 'ci' : patch <= 17 ? 'vi' : undefined; } if (minor === 419) { return patch <= 2 ? 'vi' : undefined; } return minor < 410 ? 'le' : 'he'; }; var captureConsoleLogForHost = function (host, logs, options) { if (isClient(host)) { logs.captureConsoleLog(options); return; } // `_captureConsoleLog` had six generated names across core-backed releases. // Select by the stable SDK version instead of probing generated names, // because the same name can identify a different method in another release. // Keep this historical ABI isolated here. A `captureLog` fallback would change the // service name, scope, queue, and rate limits. var name = historicalCaptureConsoleLogName(host.version); var historicalCaptureConsoleLog = name ? logs[name] : undefined; if ((0, core_1.isFunction)(historicalCaptureConsoleLog)) { historicalCaptureConsoleLog.call(logs, options); } }; var initializeLogs = function (host) { var e_1, _a; var currentHost = host; var restoreConsoleMethods = []; // `host` is carried here because the core SDK context has no equivalent. Session // attributes (window.id, sessionStartTimestamp, lastActivityTimestamp) are added // downstream by the core pipeline from the SDK context, alongside sessionId. var attributes = { host: globals_1.assignableWindow.location.host }; // Re-entrancy guard: the capture path itself logs — `captureConsoleLog` calls into // session management, which emits internal debug lines through PostHog's own logger, // which in turn writes to the (now wrapped) console. Without this flag that would // re-enter capture and recurse until the stack overflows. var isCapturingLog = false; var _loop_1 = function (level) { var logWrapper = function (next) { return function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } // Tracks whether *this* invocation acquired the re-entrancy guard, so that a // nested console call which skips capture doesn't release the guard early and // reopen the capture path while the outer invocation is still running. var acquiredGuard = false; try { var activeHost = currentHost; if (args.length > 0 && !isCapturingLog && activeHost) { var logs = getCapturingLogs(activeHost); if (logs) { isCapturingLog = true; acquiredGuard = true; var _a = stringifyArgsSafely(args, LOG_BODY_SIZE_LIMIT), body = _a.body, truncated = _a.truncated, flattenedAttributes = _a.attributes; var logAttributes = __assign(__assign({}, attributes), (truncated ? { body_truncated: 'true' } : {})); var options = { level: LEVEL_MAP[level], body: body, attributes: __assign(__assign({ 'log.source': "console.".concat(level) }, logAttributes), flattenedAttributes), }; // The core pipeline adds posthogDistinctId and url.full from the SDK context. captureConsoleLogForHost(activeHost, logs, options); } } } catch (_b) { // Capture must never break the page's own console output, so the // real console call below always runs even if capture throws. } finally { if (acquiredGuard) { isCapturingLog = false; } next.apply(globals_1.assignableWindow.console, args); } }; }; // Install as a `patch` layer rather than a bare assignment: session replay's // console plugin wraps these same methods and can arrive after this one, and a // top-of-stack-only restore leaks whichever wrapper ends up underneath — and // resurrects a lower one that has since been removed. var consoleBeforePatch = globals_1.assignableWindow.console[level]; restoreConsoleMethods.push((0, patch_1.patch)(globals_1.assignableWindow.console, level, function (next) { var wrapped = logWrapper(next); wrapped.__rrweb_original__ = (0, console_original_1.originalConsoleMethod)(consoleBeforePatch); return wrapped; })); }; try { for (var _b = __values(Object.keys(LEVEL_MAP)), _c = _b.next(); !_c.done; _c = _b.next()) { var level = _c.value; _loop_1(level); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_1) throw e_1.error; } } return function () { currentHost = undefined; restoreConsoleMethods.forEach(function (restore) { return restore(); }); }; }; /** Replays console calls the main bundle recorded before this script loaded. */ var replayConsoleBuffer = function (host, entries) { var e_2, _a; var _b; var logs = getCapturingLogs(host); if (!logs) { return; } try { for (var entries_1 = __values(entries), entries_1_1 = entries_1.next(); !entries_1_1.done; entries_1_1 = entries_1.next()) { var entry = entries_1_1.value; try { var _c = stringifyArgsSafely(entry.args, LOG_BODY_SIZE_LIMIT), body = _c.body, truncated = _c.truncated, flattenedAttributes = _c.attributes; (_b = logs.captureBufferedConsoleLog) === null || _b === void 0 ? void 0 : _b.call(logs, { level: LEVEL_MAP[entry.level], body: body, attributes: __assign(__assign({ 'log.source': "console.".concat(entry.level), host: globals_1.assignableWindow.location.host }, (truncated ? { body_truncated: 'true' } : {})), flattenedAttributes), }, entry.context, entry.occurredAtMs); } catch (_d) { // One entry that cannot be captured must not drop the rest of the buffer. } } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (entries_1_1 && !entries_1_1.done && (_a = entries_1.return)) _a.call(entries_1); } finally { if (e_2) throw e_2.error; } } }; globals_1.assignableWindow.__PosthogExtensions__ = globals_1.assignableWindow.__PosthogExtensions__ || {}; globals_1.assignableWindow.__PosthogExtensions__.logs = { initializeLogs: initializeLogs, replayConsoleBuffer: replayConsoleBuffer }; //# sourceMappingURL=logs.js.map