🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@posthog/browser-common

Package Overview
Dependencies
Maintainers
22
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@posthog/browser-common - npm Package Compare versions

Comparing version
0.2.1
to
0.2.2
+80
dist/core-extension.d.ts
import type { JsonRecord, Properties } from '@posthog/types';
import type { Disposable } from './disposable';
import type { Extension } from './extension';
import type { Listener } from './pubsub';
import type { RemoteConfig } from './types/remote-config';
import type { ExtensionToken } from './token';
/** Recursively marks object properties as readonly while preserving callable values. */
export type DeepReadonly<T> = T extends (...args: never[]) => unknown ? T : T extends object ? {
readonly [K in keyof T]: DeepReadonly<T[K]>;
} : T;
/** The current session, stamped on events to tie them to a session and a browser tab. */
export interface SessionContext {
/** The stable session identifier attached to events captured during this session. */
readonly sessionId: string;
/** The logical browser tab/window identifier attached alongside the session id. */
readonly windowId: string;
/** When the session started, as a Unix timestamp in milliseconds. */
readonly sessionStartTimestamp: number;
}
/** Why a new session started (a `reset` also starts a new session). */
export type NewSessionReason = 'initial' | 'reset' | 'idleTimeout' | 'maxLength' | 'crossTabAdoption';
/** Details emitted when the client starts or adopts a new session. */
export interface NewSessionInfo extends SessionContext {
/** The condition that caused this session to begin. */
readonly reason: NewSessionReason;
}
/** A captured event, as observed by `onEvent`. */
export interface CapturedEventInfo {
/** The finalized captured event name. */
readonly event: string;
/** The final event properties after client defaults and dynamic properties are applied. */
readonly properties: DeepReadonly<JsonRecord>;
}
/** Per-call capture overrides, mirroring the client's public capture options. */
export interface CaptureOptions {
/** Override the event timestamp sent to PostHog. */
timestamp?: Date;
/** Override the event UUID used for de-duplication. */
uuid?: string;
/** Person properties to set, emitted as `$set`. */
set?: Record<string, unknown>;
/** Person properties to set if unset, emitted as `$set_once`. */
setOnce?: Record<string, unknown>;
}
/**
* The host SDK's core analytics behavior, exposed as an extension so shared
* extensions can depend on the event pipeline without depending on a concrete
* PostHog client implementation.
*/
export interface CoreExtension extends Extension {
/** The id events are currently attributed to. */
readonly distinctId: string;
/** The anonymous device id carried across identify calls. */
readonly anonymousId: string;
/** Active group memberships attached to events as `$groups`. */
readonly groups: DeepReadonly<Record<string, string>>;
/** The current session, created on first read if needed. */
readonly session: SessionContext;
/** Records an analytics event through the client's normal pipeline. */
capture(event: string, properties?: Properties | null, options?: CaptureOptions): Promise<void>;
/**
* Registers a producer of properties merged into every captured event.
* The producer runs inline while the event is built and must be synchronous.
*/
registerDynamicEventProperties(producer: () => Record<string, unknown>): Disposable;
/** Fires for every captured event through a deeply readonly view. */
readonly onEvent: Listener<CapturedEventInfo>;
/** Fires when a new session starts, including on reset. */
readonly onNewSession: Listener<NewSessionInfo>;
/**
* Resolves with the current remote config, awaiting the first outcome when
* necessary. A failed outcome resolves to `undefined`; later successful
* changes are published through `onRemoteConfig`.
*/
getRemoteConfig(): Promise<DeepReadonly<RemoteConfig> | undefined>;
/** Fires through a deeply readonly view when server-provided config arrives or changes successfully. */
readonly onRemoteConfig: Listener<DeepReadonly<RemoteConfig>>;
}
/** Capability token used to resolve the host SDK's core analytics extension. */
export declare const CoreExtension: ExtensionToken<CoreExtension>;
"use strict";
var __webpack_require__ = {};
(()=>{
__webpack_require__.d = (exports1, definition)=>{
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
enumerable: true,
get: definition[key]
});
};
})();
(()=>{
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
})();
(()=>{
__webpack_require__.r = (exports1)=>{
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
value: 'Module'
});
Object.defineProperty(exports1, '__esModule', {
value: true
});
};
})();
var __webpack_exports__ = {};
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
CoreExtension: ()=>CoreExtension
});
const CoreExtension = 'posthog.core';
exports.CoreExtension = __webpack_exports__.CoreExtension;
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
"CoreExtension"
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
Object.defineProperty(exports, '__esModule', {
value: true
});
const CoreExtension = 'posthog.core';
export { CoreExtension };
import { type Logger } from '@posthog/core';
import type { Client } from './client';
import type { Disposable } from './disposable';
import type { Extension } from './extension';
import type { ExtensionToken } from './token';
/**
* Shared lifecycle and capability registry for browser extension hosts.
*
* Hosts provide the concrete Client adapter while this runtime coordinates
* names, capability readiness, setup failures, and reverse-order teardown.
*/
export declare class ExtensionRuntime implements Disposable {
private readonly _logger;
private readonly _extensions;
private readonly _registrationOrder;
private readonly _providerReservations;
private readonly _providers;
private _disposePromise;
constructor(_logger: Logger);
/**
* Sets up an extension and publishes its capabilities once setup succeeds.
* Names and tokens remain reserved while asynchronous setup is pending.
*/
add(extension: Extension, client: Client): Promise<void>;
/** Resolves a capability only after its provider has completed setup. */
getExtension<T>(token: ExtensionToken<T>): T | undefined;
/** Disposes every registered extension once, in reverse registration order. */
dispose(): Promise<void>;
private _disposeAll;
private _handleSetupFailure;
private _disposeRegistration;
private _removeRegistration;
private _publishRegistration;
}
"use strict";
var __webpack_require__ = {};
(()=>{
__webpack_require__.d = (exports1, definition)=>{
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
enumerable: true,
get: definition[key]
});
};
})();
(()=>{
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
})();
(()=>{
__webpack_require__.r = (exports1)=>{
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
value: 'Module'
});
Object.defineProperty(exports1, '__esModule', {
value: true
});
};
})();
var __webpack_exports__ = {};
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
ExtensionRuntime: ()=>ExtensionRuntime
});
const core_namespaceObject = require("@posthog/core");
class ExtensionRuntime {
constructor(_logger){
this._logger = _logger;
this._extensions = new Map();
this._registrationOrder = [];
this._providerReservations = new Map();
this._providers = new Map();
}
async add(extension, client) {
if (this._disposePromise) throw new Error('Cannot add an extension to a disposed ExtensionRuntime');
if (this._extensions.has(extension.name)) throw new Error(`Browser extension "${extension.name}" is already registered`);
for (const token of extension.provides ?? [])if (this._providerReservations.has(token)) throw new Error(`Browser extension token "${token}" is already registered`);
const registered = {
extension,
setupPromise: Promise.resolve()
};
this._extensions.set(extension.name, registered);
this._registrationOrder.push(registered);
for (const token of extension.provides ?? [])this._providerReservations.set(token, registered);
let setupResult;
try {
setupResult = extension.setup(client);
} catch (error) {
registered.setupPromise = this._handleSetupFailure(registered, error);
return registered.setupPromise;
}
if (setupResult && (0, core_namespaceObject.isFunction)(setupResult.then)) registered.setupPromise = setupResult.then(()=>this._publishRegistration(registered)).catch((error)=>this._handleSetupFailure(registered, error));
else this._publishRegistration(registered);
return registered.setupPromise;
}
getExtension(token) {
return this._providers.get(token);
}
dispose() {
if (!this._disposePromise) this._disposePromise = this._disposeAll();
return this._disposePromise;
}
async _disposeAll() {
for (const registered of this._registrationOrder.slice().reverse()){
await registered.setupPromise;
await this._disposeRegistration(registered);
}
this._extensions.clear();
this._registrationOrder.length = 0;
this._providerReservations.clear();
this._providers.clear();
}
async _handleSetupFailure(registered, error) {
this._removeRegistration(registered);
this._logger.error(`Failed to set up browser extension "${registered.extension.name}"`, error);
if (this._disposePromise) return;
await this._disposeRegistration(registered);
const index = this._registrationOrder.indexOf(registered);
if (-1 !== index) this._registrationOrder.splice(index, 1);
}
_disposeRegistration(registered) {
if (!registered.disposalPromise) registered.disposalPromise = Promise.resolve().then(()=>registered.extension.dispose()).catch((error)=>{
this._logger.error(`Failed to dispose browser extension "${registered.extension.name}"`, error);
});
return registered.disposalPromise;
}
_removeRegistration(registered) {
if (this._extensions.get(registered.extension.name) === registered) this._extensions.delete(registered.extension.name);
for (const token of registered.extension.provides ?? []){
if (this._providerReservations.get(token) === registered) this._providerReservations.delete(token);
if (this._providers.get(token) === registered.extension) this._providers.delete(token);
}
}
_publishRegistration(registered) {
if (this._disposePromise || this._extensions.get(registered.extension.name) !== registered) return;
for (const token of registered.extension.provides ?? [])this._providers.set(token, registered.extension);
}
}
exports.ExtensionRuntime = __webpack_exports__.ExtensionRuntime;
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
"ExtensionRuntime"
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
Object.defineProperty(exports, '__esModule', {
value: true
});
import { isFunction } from "@posthog/core";
class ExtensionRuntime {
constructor(_logger){
this._logger = _logger;
this._extensions = new Map();
this._registrationOrder = [];
this._providerReservations = new Map();
this._providers = new Map();
}
async add(extension, client) {
if (this._disposePromise) throw new Error('Cannot add an extension to a disposed ExtensionRuntime');
if (this._extensions.has(extension.name)) throw new Error(`Browser extension "${extension.name}" is already registered`);
for (const token of extension.provides ?? [])if (this._providerReservations.has(token)) throw new Error(`Browser extension token "${token}" is already registered`);
const registered = {
extension,
setupPromise: Promise.resolve()
};
this._extensions.set(extension.name, registered);
this._registrationOrder.push(registered);
for (const token of extension.provides ?? [])this._providerReservations.set(token, registered);
let setupResult;
try {
setupResult = extension.setup(client);
} catch (error) {
registered.setupPromise = this._handleSetupFailure(registered, error);
return registered.setupPromise;
}
if (setupResult && isFunction(setupResult.then)) registered.setupPromise = setupResult.then(()=>this._publishRegistration(registered)).catch((error)=>this._handleSetupFailure(registered, error));
else this._publishRegistration(registered);
return registered.setupPromise;
}
getExtension(token) {
return this._providers.get(token);
}
dispose() {
if (!this._disposePromise) this._disposePromise = this._disposeAll();
return this._disposePromise;
}
async _disposeAll() {
for (const registered of this._registrationOrder.slice().reverse()){
await registered.setupPromise;
await this._disposeRegistration(registered);
}
this._extensions.clear();
this._registrationOrder.length = 0;
this._providerReservations.clear();
this._providers.clear();
}
async _handleSetupFailure(registered, error) {
this._removeRegistration(registered);
this._logger.error(`Failed to set up browser extension "${registered.extension.name}"`, error);
if (this._disposePromise) return;
await this._disposeRegistration(registered);
const index = this._registrationOrder.indexOf(registered);
if (-1 !== index) this._registrationOrder.splice(index, 1);
}
_disposeRegistration(registered) {
if (!registered.disposalPromise) registered.disposalPromise = Promise.resolve().then(()=>registered.extension.dispose()).catch((error)=>{
this._logger.error(`Failed to dispose browser extension "${registered.extension.name}"`, error);
});
return registered.disposalPromise;
}
_removeRegistration(registered) {
if (this._extensions.get(registered.extension.name) === registered) this._extensions.delete(registered.extension.name);
for (const token of registered.extension.provides ?? []){
if (this._providerReservations.get(token) === registered) this._providerReservations.delete(token);
if (this._providers.get(token) === registered.extension) this._providers.delete(token);
}
}
_publishRegistration(registered) {
if (this._disposePromise || this._extensions.get(registered.extension.name) !== registered) return;
for (const token of registered.extension.provides ?? [])this._providers.set(token, registered.extension);
}
}
export { ExtensionRuntime };
export declare const Compression: {
readonly GZipJS: "gzip-js";
readonly Base64: "base64";
};
export type Compression = (typeof Compression)[keyof typeof Compression];
"use strict";
var __webpack_require__ = {};
(()=>{
__webpack_require__.d = (exports1, definition)=>{
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
enumerable: true,
get: definition[key]
});
};
})();
(()=>{
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
})();
(()=>{
__webpack_require__.r = (exports1)=>{
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
value: 'Module'
});
Object.defineProperty(exports1, '__esModule', {
value: true
});
};
})();
var __webpack_exports__ = {};
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
Compression: ()=>Compression
});
const Compression = {
GZipJS: 'gzip-js',
Base64: 'base64'
};
exports.Compression = __webpack_exports__.Compression;
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
"Compression"
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
Object.defineProperty(exports, '__esModule', {
value: true
});
const Compression = {
GZipJS: 'gzip-js',
Base64: 'base64'
};
export { Compression };
export * from './compression';
export * from './network-recording';
export * from './remote-config';
export * from './surveys';
"use strict";
var __webpack_modules__ = {
"./compression": function(module) {
module.exports = require("./compression.js");
},
"./network-recording": function(module) {
module.exports = require("./network-recording.js");
},
"./remote-config": function(module) {
module.exports = require("./remote-config.js");
},
"./surveys": function(module) {
module.exports = require("./surveys.js");
}
};
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
var cachedModule = __webpack_module_cache__[moduleId];
if (void 0 !== cachedModule) return cachedModule.exports;
var module = __webpack_module_cache__[moduleId] = {
exports: {}
};
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.exports;
}
(()=>{
__webpack_require__.n = (module)=>{
var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
__webpack_require__.d(getter, {
a: getter
});
return getter;
};
})();
(()=>{
__webpack_require__.d = (exports1, definition)=>{
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
enumerable: true,
get: definition[key]
});
};
})();
(()=>{
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
})();
(()=>{
__webpack_require__.r = (exports1)=>{
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
value: 'Module'
});
Object.defineProperty(exports1, '__esModule', {
value: true
});
};
})();
var __webpack_exports__ = {};
(()=>{
__webpack_require__.r(__webpack_exports__);
var _compression__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./compression");
var __WEBPACK_REEXPORT_OBJECT__ = {};
for(var __WEBPACK_IMPORT_KEY__ in _compression__WEBPACK_IMPORTED_MODULE_0__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
return _compression__WEBPACK_IMPORTED_MODULE_0__[key];
}).bind(0, __WEBPACK_IMPORT_KEY__);
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
var _network_recording__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__("./network-recording");
var __WEBPACK_REEXPORT_OBJECT__ = {};
for(var __WEBPACK_IMPORT_KEY__ in _network_recording__WEBPACK_IMPORTED_MODULE_1__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
return _network_recording__WEBPACK_IMPORTED_MODULE_1__[key];
}).bind(0, __WEBPACK_IMPORT_KEY__);
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
var _remote_config__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__("./remote-config");
var __WEBPACK_REEXPORT_OBJECT__ = {};
for(var __WEBPACK_IMPORT_KEY__ in _remote_config__WEBPACK_IMPORTED_MODULE_2__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
return _remote_config__WEBPACK_IMPORTED_MODULE_2__[key];
}).bind(0, __WEBPACK_IMPORT_KEY__);
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
var _surveys__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__("./surveys");
var __WEBPACK_REEXPORT_OBJECT__ = {};
for(var __WEBPACK_IMPORT_KEY__ in _surveys__WEBPACK_IMPORTED_MODULE_3__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
return _surveys__WEBPACK_IMPORTED_MODULE_3__[key];
}).bind(0, __WEBPACK_IMPORT_KEY__);
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
})();
for(var __webpack_i__ in __webpack_exports__)exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
Object.defineProperty(exports, '__esModule', {
value: true
});
export * from "./compression.mjs";
export * from "./network-recording.mjs";
export * from "./remote-config.mjs";
export * from "./surveys.mjs";
import type { CapturedNetworkRequest, InitiatorType } from '@posthog/types';
export type NetworkRecordOptions = {
initiatorTypes?: InitiatorType[];
maskRequestFn?: (data: CapturedNetworkRequest) => CapturedNetworkRequest | undefined;
recordHeaders?: boolean | {
request: boolean;
response: boolean;
};
recordBody?: boolean | string[] | {
request: boolean | string[];
response: boolean | string[];
};
recordInitialRequests?: boolean;
/**
* whether to record PerformanceEntry events for network requests
*/
recordPerformance?: boolean;
/**
* the PerformanceObserver will only observe these entry types
*/
performanceEntryTypeToObserve: string[];
/**
* the maximum size of the request/response body to record
* NB this will be at most 1MB even if set larger
*/
payloadSizeLimitBytes: number;
/**
* when true, read bodies through a streaming reader that stops at payloadSizeLimitBytes
* instead of buffering the whole body and then enforcing the limit. Reads only a clone of
* the body, so it never consumes the stream the page itself reads.
* @default false
*/
streamNetworkBody?: boolean;
/**
* some domains we should never record the payload
* for example other companies session replay ingestion payloads aren't super useful but are gigantic
* if this isn't provided we use a default list
* if this is provided - we add the provided list to the default list
* i.e. we never record the payloads on the default deny list
*/
payloadHostDenyList?: string[];
};
"use strict";
var __webpack_require__ = {};
(()=>{
__webpack_require__.r = (exports1)=>{
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
value: 'Module'
});
Object.defineProperty(exports1, '__esModule', {
value: true
});
};
})();
var __webpack_exports__ = {};
__webpack_require__.r(__webpack_exports__);
for(var __webpack_i__ in __webpack_exports__)exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
Object.defineProperty(exports, '__esModule', {
value: true
});
import type { PerformanceCaptureConfig, SessionRecordingCanvasOptions, SessionRecordingOptions, ToolbarParams } from '@posthog/types';
import type { Compression } from './compression';
import type { NetworkRecordOptions } from './network-recording';
import type { PropertyMatchType, Survey } from './surveys';
export type FlagVariant = {
flag: string;
variant: string;
};
export interface SessionRecordingUrlTrigger {
url: string;
matching: 'regex';
}
/**
* V2 event trigger - always an object with name, optionally with property filters.
* The server normalizes bare event name strings to this shape before sending.
*/
export interface SessionRecordingEventTrigger {
name: string;
properties?: SessionRecordingTriggerPropertyFilter[];
}
export interface SessionRecordingTriggerPropertyFilter {
key: string;
type: 'event' | 'person';
operator?: 'exact' | 'is_not' | 'icontains' | 'not_icontains' | 'regex' | 'not_regex' | 'gt' | 'lt';
value?: string | number | boolean | string[];
}
/**
* V2 Trigger Group - represents a single trigger group with its own conditions and sample rate
*/
export interface SessionRecordingTriggerGroup {
id: string;
name: string;
sampleRate: number;
minDurationMs?: number;
conditions: {
matchType: 'any' | 'all';
events?: SessionRecordingEventTrigger[];
urls?: SessionRecordingUrlTrigger[];
flag?: string | FlagVariant;
properties?: SessionRecordingTriggerPropertyFilter[];
};
}
export type SessionRecordingRemoteConfig = SessionRecordingCanvasOptions & {
endpoint?: string;
consoleLogRecordingEnabled?: boolean;
sampleRate?: string | null;
minimumDurationMilliseconds?: number;
linkedFlag?: string | FlagVariant | null;
networkPayloadCapture?: Pick<NetworkRecordOptions, 'recordBody' | 'recordHeaders'>;
masking?: Pick<SessionRecordingOptions, 'maskAllInputs' | 'maskTextSelector' | 'blockSelector'>;
urlTriggers?: SessionRecordingUrlTrigger[];
scriptConfig?: {
script?: string | undefined;
};
urlBlocklist?: SessionRecordingUrlTrigger[];
eventTriggers?: string[];
/**
* Controls how event, URL, sampling, and linked flag triggers are combined.
*
* `any` means that if any of the triggers match, the session will be recorded.
* `all` means that all the triggers must match for the session to be recorded.
*/
triggerMatchType?: 'any' | 'all';
/**
* Config version - defaults to 1 (legacy).
* When version is 2, triggerGroups is used instead of individual trigger fields.
*/
version?: 1 | 2;
/**
* V2 Trigger Groups - multiple named trigger groups with their own conditions and sample rates.
* Only used when version === 2.
*/
triggerGroups?: SessionRecordingTriggerGroup[];
};
export interface ErrorTrackingSuppressionRule {
type: 'AND' | 'OR';
values: ErrorTrackingSuppressionRuleValue[];
}
export interface ErrorTrackingSuppressionRuleValue {
key: '$exception_types' | '$exception_values';
operator: PropertyMatchType;
value: string | string[];
type: string;
}
/** Position of the conversations widget on the screen. */
export type WidgetPosition = 'bottom_left' | 'bottom_right' | 'top_left' | 'top_right';
/** Remote configuration for conversations from the PostHog server. */
export interface ConversationsRemoteConfig {
/** Whether conversations are enabled for this team. */
enabled: boolean;
/** Whether the widget UI should be shown. */
widgetEnabled?: boolean;
/** Public token for authenticating conversations API requests. */
token: string;
/** Greeting text to show when the widget is first opened. */
greetingText?: string;
/** Primary color for the widget UI. */
color?: string;
/** Placeholder text for the message input. */
placeholderText?: string;
/** Whether to require an email address before starting a conversation. */
requireEmail?: boolean;
/** Whether to show the name field in the identification form. */
collectName?: boolean;
/** Title for the identification form. */
identificationFormTitle?: string;
/** Description for the identification form. */
identificationFormDescription?: string;
/** Domains where the widget may be shown. */
domains?: string[];
/** Position of the widget on the screen. */
widgetPosition?: WidgetPosition;
}
/**
* Remote configuration for a PostHog browser client.
*
* These settings can be configured in PostHog. Configuration set directly on
* the client takes precedence over values from the server.
*/
export interface RemoteConfig {
/** Supported compression algorithms. */
supportedCompression: Compression[];
/**
* If true, disables autocapture. When absent or not a boolean, the SDK
* keeps the last known server value; a visitor with no stored value keeps
* autocapture off until a response containing the field arrives.
*/
autocapture_opt_out?: boolean;
/** Performance capture configuration shared by replay and web vitals. */
capturePerformance?: boolean | PerformanceCaptureConfig;
/** Custom endpoint configuration for analytics events. */
analytics?: {
endpoint?: string;
};
/** Whether `$elements_chain` is sent as a string rather than an array. */
elementsChainAsString?: boolean;
/** Error tracking configuration. */
errorTracking?: {
autocaptureExceptions?: boolean;
captureExtensionExceptions?: boolean;
suppressionRules?: ErrorTrackingSuppressionRule[];
};
/** Log capture configuration. */
logs?: {
captureConsoleLogs?: boolean;
};
/** Exception autocapture configuration. */
autocaptureExceptions?: boolean | {
endpoint?: string;
};
/** Session recording configuration. */
sessionRecording?: SessionRecordingRemoteConfig | false;
/** Whether surveys are enabled, optionally including their definitions. */
surveys?: boolean | Survey[];
/** Whether product tours are enabled. */
productTours?: boolean;
/** Parameters for the toolbar. */
toolbarParams: ToolbarParams;
/** @deprecated Renamed to `toolbarParams`; still present on older API responses. */
editorParams?: ToolbarParams;
/** @deprecated Moved to `toolbarParams`. */
toolbarVersion: 'toolbar';
/** Whether the current user is authenticated. */
isAuthenticated: boolean;
/** Site apps available to the client. */
siteApps: {
id: string;
url: string;
}[];
/** Whether heatmaps are enabled. */
heatmaps?: boolean;
/** Whether to capture only identified users by default. */
defaultIdentifiedOnly?: boolean;
/** Whether to capture dead clicks. */
captureDeadClicks?: boolean;
/** Whether the team has any feature flags enabled. */
hasFeatureFlags?: boolean;
/** Conversations widget configuration. */
conversations?: boolean | ConversationsRemoteConfig;
}
"use strict";
var __webpack_require__ = {};
(()=>{
__webpack_require__.r = (exports1)=>{
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
value: 'Module'
});
Object.defineProperty(exports1, '__esModule', {
value: true
});
};
})();
var __webpack_exports__ = {};
__webpack_require__.r(__webpack_exports__);
for(var __webpack_i__ in __webpack_exports__)exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
Object.defineProperty(exports, '__esModule', {
value: true
});
import type { SurveyAppearance as CoreSurveyAppearance, SurveyQuestionTranslation, SurveyTranslation, SurveyValidationRule } from '@posthog/core';
export declare const SurveyEventType: {
readonly Activation: "events";
readonly Cancellation: "cancelEvents";
};
export type SurveyEventType = (typeof SurveyEventType)[keyof typeof SurveyEventType];
export declare const SurveyWidgetType: {
readonly Button: "button";
readonly Tab: "tab";
readonly Selector: "selector";
};
export type SurveyWidgetType = (typeof SurveyWidgetType)[keyof typeof SurveyWidgetType];
export declare const SurveyPosition: {
readonly TopLeft: "top_left";
readonly TopRight: "top_right";
readonly TopCenter: "top_center";
readonly MiddleLeft: "middle_left";
readonly MiddleRight: "middle_right";
readonly MiddleCenter: "middle_center";
readonly Left: "left";
readonly Center: "center";
readonly Right: "right";
readonly NextToTrigger: "next_to_trigger";
};
export type SurveyPosition = (typeof SurveyPosition)[keyof typeof SurveyPosition];
export declare const SurveyTabPosition: {
readonly Top: "top";
readonly Left: "left";
readonly Right: "right";
readonly Bottom: "bottom";
};
export type SurveyTabPosition = (typeof SurveyTabPosition)[keyof typeof SurveyTabPosition];
export declare const SurveyType: {
readonly Popover: "popover";
readonly API: "api";
readonly Widget: "widget";
readonly ExternalSurvey: "external_survey";
};
export type SurveyType = (typeof SurveyType)[keyof typeof SurveyType];
export declare const SurveyQuestionType: {
readonly Open: "open";
readonly MultipleChoice: "multiple_choice";
readonly SingleChoice: "single_choice";
readonly Rating: "rating";
readonly Link: "link";
};
export type SurveyQuestionType = (typeof SurveyQuestionType)[keyof typeof SurveyQuestionType];
export declare const SurveyQuestionBranchingType: {
readonly NextQuestion: "next_question";
readonly End: "end";
readonly ResponseBased: "response_based";
readonly SpecificQuestion: "specific_question";
};
export type SurveyQuestionBranchingType = (typeof SurveyQuestionBranchingType)[keyof typeof SurveyQuestionBranchingType];
export declare const SurveySchedule: {
readonly Once: "once";
readonly Recurring: "recurring";
readonly Always: "always";
};
export type SurveySchedule = (typeof SurveySchedule)[keyof typeof SurveySchedule];
export declare const SurveyEventName: {
readonly SHOWN: "survey shown";
readonly DISMISSED: "survey dismissed";
readonly SENT: "survey sent";
readonly ABANDONED: "survey abandoned";
};
export type SurveyEventName = (typeof SurveyEventName)[keyof typeof SurveyEventName];
export declare const SurveyEventProperties: {
readonly SURVEY_ID: "$survey_id";
readonly SURVEY_NAME: "$survey_name";
readonly SURVEY_RESPONSE: "$survey_response";
readonly SURVEY_ITERATION: "$survey_iteration";
readonly SURVEY_ITERATION_START_DATE: "$survey_iteration_start_date";
readonly SURVEY_PARTIALLY_COMPLETED: "$survey_partially_completed";
readonly SURVEY_SUBMISSION_ID: "$survey_submission_id";
readonly SURVEY_QUESTIONS: "$survey_questions";
readonly SURVEY_COMPLETED: "$survey_completed";
readonly PRODUCT_TOUR_ID: "$product_tour_id";
readonly SURVEY_LAST_SEEN_DATE: "$survey_last_seen_date";
readonly SURVEY_LANGUAGE: "$survey_language";
};
export type SurveyEventProperties = (typeof SurveyEventProperties)[keyof typeof SurveyEventProperties];
export declare const DisplaySurveyType: {
readonly Popover: "popover";
readonly Inline: "inline";
};
export type DisplaySurveyType = (typeof DisplaySurveyType)[keyof typeof DisplaySurveyType];
export type PropertyMatchType = 'regex' | 'not_regex' | 'exact' | 'is_not' | 'icontains' | 'not_icontains';
/** Extended survey operator type with numeric comparisons. */
export type PropertyOperator = PropertyMatchType | 'gt' | 'lt';
export type PropertyFilters = {
[propertyName: string]: {
values: string[];
operator: PropertyOperator;
};
};
export interface SurveyEventWithFilters {
name: string;
propertyFilters?: PropertyFilters;
}
export type SurveyQuestionDescriptionContentType = 'html' | 'text';
/** Browser survey appearance, including browser-only placement and rendering options. */
export interface SurveyAppearance extends Omit<CoreSurveyAppearance, 'position' | 'widgetType'> {
/** @deprecated Not currently used. */
descriptionTextColor?: string;
ratingButtonHoverColor?: string;
whiteLabel?: boolean;
tabPosition?: SurveyTabPosition;
fontFamily?: string;
maxWidth?: string;
zIndex?: string;
disabledButtonOpacity?: string;
boxPadding?: string;
/** @deprecated Use `inputBackground` instead. */
inputBackgroundColor?: string;
hideCancelButton?: boolean;
disableAutofocus?: boolean;
position?: SurveyPosition;
widgetType?: SurveyWidgetType;
}
export type SurveyQuestion = BasicSurveyQuestion | LinkSurveyQuestion | RatingSurveyQuestion | MultipleSurveyQuestion;
interface SurveyQuestionBase {
question: string;
id?: string;
description?: string | null;
descriptionContentType?: SurveyQuestionDescriptionContentType;
optional?: boolean;
buttonText?: string;
branching?: NextQuestionBranching | EndBranching | ResponseBasedBranching | SpecificQuestionBranching;
validation?: SurveyValidationRule[];
translations?: Record<string, SurveyQuestionTranslation>;
}
export interface BasicSurveyQuestion extends SurveyQuestionBase {
type: typeof SurveyQuestionType.Open;
}
export interface LinkSurveyQuestion extends SurveyQuestionBase {
type: typeof SurveyQuestionType.Link;
link?: string | null;
}
export interface RatingSurveyQuestion extends SurveyQuestionBase {
type: typeof SurveyQuestionType.Rating;
display: 'number' | 'emoji';
scale: 2 | 3 | 5 | 7 | 10;
lowerBoundLabel: string;
upperBoundLabel: string;
skipSubmitButton?: boolean;
}
export interface MultipleSurveyQuestion extends SurveyQuestionBase {
type: typeof SurveyQuestionType.SingleChoice | typeof SurveyQuestionType.MultipleChoice;
choices: string[];
hasOpenChoice?: boolean;
shuffleOptions?: boolean;
skipSubmitButton?: boolean;
}
interface NextQuestionBranching {
type: typeof SurveyQuestionBranchingType.NextQuestion;
}
interface EndBranching {
type: typeof SurveyQuestionBranchingType.End;
}
interface ResponseBasedBranching {
type: typeof SurveyQuestionBranchingType.ResponseBased;
responseValues: Record<string, any>;
}
interface SpecificQuestionBranching {
type: typeof SurveyQuestionBranchingType.SpecificQuestion;
index: number;
}
/** A survey definition returned as part of browser remote config. */
export interface Survey {
id: string;
name: string;
description?: string;
type: SurveyType;
translations?: Record<string, SurveyTranslation>;
feature_flag_keys: {
key: string;
value?: string;
}[] | null;
linked_flag_key: string | null;
targeting_flag_key: string | null;
internal_targeting_flag_key: string | null;
questions: SurveyQuestion[];
appearance: SurveyAppearance | null;
conditions: {
url?: string;
selector?: string;
seenSurveyWaitPeriodInDays?: number;
urlMatchType?: PropertyMatchType;
events: {
repeatedActivation?: boolean;
values: SurveyEventWithFilters[];
} | null;
cancelEvents: {
values: SurveyEventWithFilters[];
} | null;
actions: {
values: SurveyActionType[];
} | null;
deviceTypes?: string[];
deviceTypesMatchType?: PropertyMatchType;
linkedFlagVariant?: string;
} | null;
start_date: string | null;
end_date: string | null;
current_iteration: number | null;
current_iteration_start_date: string | null;
schedule?: SurveySchedule | null;
enable_partial_responses?: boolean | null;
}
export type SurveyWithTypeAndAppearance = Pick<Survey, 'id' | 'type' | 'appearance'>;
export interface SurveyActionType {
id: number;
name: string | null;
steps?: ActionStepType[];
}
/** Sync with plugin-server/src/types.ts. */
export type ActionStepStringMatching = 'contains' | 'exact' | 'regex';
export interface ActionStepType {
event?: string | null;
selector?: string | null;
/** Pre-compiled regex pattern for matching selector against `$elements_chain`. */
selector_regex?: string | null;
/** @deprecated Only `selector` should be used now. */
tag_name?: string;
text?: string | null;
/** @default StringMatching.Exact */
text_matching?: ActionStepStringMatching | null;
href?: string | null;
/** @default StringMatching.Exact */
href_matching?: ActionStepStringMatching | null;
url?: string | null;
/** @default StringMatching.Contains */
url_matching?: ActionStepStringMatching | null;
/** Property filters for action step matching. */
properties?: {
key: string;
value?: string | number | boolean | (string | number | boolean)[] | null;
operator?: PropertyMatchType;
type?: string;
}[];
}
export {};
"use strict";
var __webpack_require__ = {};
(()=>{
__webpack_require__.d = (exports1, definition)=>{
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
enumerable: true,
get: definition[key]
});
};
})();
(()=>{
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
})();
(()=>{
__webpack_require__.r = (exports1)=>{
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
value: 'Module'
});
Object.defineProperty(exports1, '__esModule', {
value: true
});
};
})();
var __webpack_exports__ = {};
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
DisplaySurveyType: ()=>DisplaySurveyType,
SurveyEventName: ()=>SurveyEventName,
SurveyEventProperties: ()=>SurveyEventProperties,
SurveyEventType: ()=>SurveyEventType,
SurveyPosition: ()=>SurveyPosition,
SurveyQuestionBranchingType: ()=>SurveyQuestionBranchingType,
SurveyQuestionType: ()=>SurveyQuestionType,
SurveySchedule: ()=>SurveySchedule,
SurveyTabPosition: ()=>SurveyTabPosition,
SurveyType: ()=>SurveyType,
SurveyWidgetType: ()=>SurveyWidgetType
});
const SurveyEventType = {
Activation: 'events',
Cancellation: 'cancelEvents'
};
const SurveyWidgetType = {
Button: 'button',
Tab: 'tab',
Selector: 'selector'
};
const SurveyPosition = {
TopLeft: 'top_left',
TopRight: 'top_right',
TopCenter: 'top_center',
MiddleLeft: 'middle_left',
MiddleRight: 'middle_right',
MiddleCenter: 'middle_center',
Left: 'left',
Center: 'center',
Right: 'right',
NextToTrigger: 'next_to_trigger'
};
const SurveyTabPosition = {
Top: 'top',
Left: 'left',
Right: 'right',
Bottom: 'bottom'
};
const SurveyType = {
Popover: 'popover',
API: 'api',
Widget: 'widget',
ExternalSurvey: 'external_survey'
};
const SurveyQuestionType = {
Open: 'open',
MultipleChoice: 'multiple_choice',
SingleChoice: 'single_choice',
Rating: 'rating',
Link: 'link'
};
const SurveyQuestionBranchingType = {
NextQuestion: 'next_question',
End: 'end',
ResponseBased: 'response_based',
SpecificQuestion: 'specific_question'
};
const SurveySchedule = {
Once: 'once',
Recurring: 'recurring',
Always: 'always'
};
const SurveyEventName = {
SHOWN: 'survey shown',
DISMISSED: 'survey dismissed',
SENT: 'survey sent',
ABANDONED: 'survey abandoned'
};
const SurveyEventProperties = {
SURVEY_ID: '$survey_id',
SURVEY_NAME: '$survey_name',
SURVEY_RESPONSE: '$survey_response',
SURVEY_ITERATION: '$survey_iteration',
SURVEY_ITERATION_START_DATE: '$survey_iteration_start_date',
SURVEY_PARTIALLY_COMPLETED: '$survey_partially_completed',
SURVEY_SUBMISSION_ID: '$survey_submission_id',
SURVEY_QUESTIONS: '$survey_questions',
SURVEY_COMPLETED: '$survey_completed',
PRODUCT_TOUR_ID: '$product_tour_id',
SURVEY_LAST_SEEN_DATE: '$survey_last_seen_date',
SURVEY_LANGUAGE: '$survey_language'
};
const DisplaySurveyType = {
Popover: 'popover',
Inline: 'inline'
};
exports.DisplaySurveyType = __webpack_exports__.DisplaySurveyType;
exports.SurveyEventName = __webpack_exports__.SurveyEventName;
exports.SurveyEventProperties = __webpack_exports__.SurveyEventProperties;
exports.SurveyEventType = __webpack_exports__.SurveyEventType;
exports.SurveyPosition = __webpack_exports__.SurveyPosition;
exports.SurveyQuestionBranchingType = __webpack_exports__.SurveyQuestionBranchingType;
exports.SurveyQuestionType = __webpack_exports__.SurveyQuestionType;
exports.SurveySchedule = __webpack_exports__.SurveySchedule;
exports.SurveyTabPosition = __webpack_exports__.SurveyTabPosition;
exports.SurveyType = __webpack_exports__.SurveyType;
exports.SurveyWidgetType = __webpack_exports__.SurveyWidgetType;
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
"DisplaySurveyType",
"SurveyEventName",
"SurveyEventProperties",
"SurveyEventType",
"SurveyPosition",
"SurveyQuestionBranchingType",
"SurveyQuestionType",
"SurveySchedule",
"SurveyTabPosition",
"SurveyType",
"SurveyWidgetType"
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
Object.defineProperty(exports, '__esModule', {
value: true
});
const SurveyEventType = {
Activation: 'events',
Cancellation: 'cancelEvents'
};
const SurveyWidgetType = {
Button: 'button',
Tab: 'tab',
Selector: 'selector'
};
const SurveyPosition = {
TopLeft: 'top_left',
TopRight: 'top_right',
TopCenter: 'top_center',
MiddleLeft: 'middle_left',
MiddleRight: 'middle_right',
MiddleCenter: 'middle_center',
Left: 'left',
Center: 'center',
Right: 'right',
NextToTrigger: 'next_to_trigger'
};
const SurveyTabPosition = {
Top: 'top',
Left: 'left',
Right: 'right',
Bottom: 'bottom'
};
const SurveyType = {
Popover: 'popover',
API: 'api',
Widget: 'widget',
ExternalSurvey: 'external_survey'
};
const SurveyQuestionType = {
Open: 'open',
MultipleChoice: 'multiple_choice',
SingleChoice: 'single_choice',
Rating: 'rating',
Link: 'link'
};
const SurveyQuestionBranchingType = {
NextQuestion: 'next_question',
End: 'end',
ResponseBased: 'response_based',
SpecificQuestion: 'specific_question'
};
const SurveySchedule = {
Once: 'once',
Recurring: 'recurring',
Always: 'always'
};
const SurveyEventName = {
SHOWN: 'survey shown',
DISMISSED: 'survey dismissed',
SENT: 'survey sent',
ABANDONED: 'survey abandoned'
};
const SurveyEventProperties = {
SURVEY_ID: '$survey_id',
SURVEY_NAME: '$survey_name',
SURVEY_RESPONSE: '$survey_response',
SURVEY_ITERATION: '$survey_iteration',
SURVEY_ITERATION_START_DATE: '$survey_iteration_start_date',
SURVEY_PARTIALLY_COMPLETED: '$survey_partially_completed',
SURVEY_SUBMISSION_ID: '$survey_submission_id',
SURVEY_QUESTIONS: '$survey_questions',
SURVEY_COMPLETED: '$survey_completed',
PRODUCT_TOUR_ID: '$product_tour_id',
SURVEY_LAST_SEEN_DATE: '$survey_last_seen_date',
SURVEY_LANGUAGE: '$survey_language'
};
const DisplaySurveyType = {
Popover: 'popover',
Inline: 'inline'
};
export { DisplaySurveyType, SurveyEventName, SurveyEventProperties, SurveyEventType, SurveyPosition, SurveyQuestionBranchingType, SurveyQuestionType, SurveySchedule, SurveyTabPosition, SurveyType, SurveyWidgetType };
+15
-91
import type { Logger } from '@posthog/core';
import type { Disposable } from './disposable';
import type { KeyValueStore } from './persistence';
import type { Listener } from './pubsub';
import type { ExtensionToken } from './token';
/** The current session, stamped on events to tie them to a session and a browser tab. */
export interface SessionContext {
/** The stable session identifier attached to events captured during this session. */
sessionId: string;
/** The logical browser tab/window identifier attached alongside the session id. */
windowId: string;
/** When the session started, as a Unix timestamp in milliseconds. */
sessionStartTimestamp: number;
}
/** Why a new session started (a `reset` also starts a new session). */
export type NewSessionReason = 'initial' | 'reset' | 'idleTimeout' | 'maxLength' | 'crossTabAdoption';
/** Details emitted when the client starts or adopts a new session. */
export interface NewSessionInfo extends SessionContext {
/** The condition that caused this session to begin. */
reason: NewSessionReason;
}
/** A captured event, as observed by `onEvent`. */
export interface CapturedEventInfo {
/** The event name supplied to {@link Client.capture}. */
event: string;
/** The final event properties after client defaults and dynamic properties are applied. */
properties: Record<string, unknown>;
}
/** Per-call capture overrides, mirroring the client's public capture options. */
export interface CaptureOptions {
/** Override the event timestamp sent to PostHog. */
timestamp?: Date;
/** Override the event UUID used for de-duplication. */
uuid?: string;
/** Person properties to set, emitted as `$set`. */
set?: Record<string, unknown>;
/** Person properties to set if unset, emitted as `$set_once`. */
setOnce?: Record<string, unknown>;
}
/** A minimal response from {@link Client.apiRequest}. */
export interface ApiResponse {
/** Whether the request completed with a 2xx status, or was queued for best-effort unload transport. */
ok: boolean;
/** The HTTP status code returned by the transport, or a client-defined best-effort status for unload sends. */
status: number;
/** Parse the response body as JSON; may be unavailable for best-effort unload requests. */
json(): Promise<unknown>;
/** Read the response body as text; may be unavailable for best-effort unload requests. */
text(): Promise<string>;
statusCode: number;
/** The response body parsed as JSON when available. */
json?: unknown;
/** The response body as text when available. */
text?: string;
/** The transport error when the request failed before receiving an HTTP response. */
error?: unknown;
}

@@ -62,4 +26,4 @@ /** Options for sending a request through {@link Client.apiRequest}. */

* most reliable fire-and-forget transport available — `sendBeacon`, fetch
* `keepalive`, or sync XHR. The response is best-effort: `.json()` may be
* unusable (e.g. `sendBeacon` only reports "queued"), so callers must not
* `keepalive`, or sync XHR. The response is best-effort: `json` may be
* unavailable (e.g. `sendBeacon` only reports "queued"), so callers must not
* depend on it.

@@ -72,37 +36,12 @@ */

/**
* Server-provided configuration, as returned by the remote config response
* (sampling rates, suppression rules, feature enablement, quotas, …). A loose
* record by design — each extension reads only the keys it owns.
*/
export type RemoteConfig = Record<string, unknown>;
/**
* The host SDK's capability surface as seen by an extension — the client an
* extension is handed in `setup`. Each SDK (v1, v2) provides it as a client
* adapter over its own internals.
* extension is handed in `setup`. A conforming host provides it as an adapter
* over its own internals.
*
* Synchronous members are always-ready in-memory reads (identity, session);
* asynchronous members do I/O or wait for something to become ready
* (`capture`, `apiRequest`, `kv`, `getRemoteConfig`).
* Host services that may do I/O are awaitable; a host can complete them
* synchronously when its underlying implementation supports that. Core
* analytics behavior is provided separately by the core extension.
*/
export interface Client {
/** The id events are currently attributed to — the anonymous id, or the identified user's id after `identify`. */
readonly distinctId: string;
/** The anonymous device id; used before `identify` and carried on identify events as `$anon_distinct_id`. */
readonly anonymousId: string;
/** Active group memberships (group type → group key), attached to events as `$groups`. */
readonly groups: Record<string, string>;
/** The current session, created on first read if needed; reading does not extend or rotate it. */
readonly session: SessionContext;
/** Records an analytics event through the client's normal pipeline. */
capture(event: string, properties?: Record<string, unknown> | null, options?: CaptureOptions): Promise<void>;
/**
* Registers a producer of properties merged into every captured event.
* Returns a {@link Disposable} that removes it; an extension disposes it in
* its own `dispose`. May be called more than once. The producer runs inline
* during event build, so it must be cheap and synchronous; it may return
* different properties each time (e.g. the current URL), and is recomputed
* per event rather than stored.
*/
registerDynamicEventProperties(producer: () => Record<string, unknown>): Disposable;
/**
* Sends a request to a PostHog endpoint; the client owns auth, headers, and

@@ -114,17 +53,2 @@ * transport (fetch / XHR / keepalive). `path` is relative to the configured

/**
* Resolves with the client's remote config once available, or `undefined`
* if the fetch failed (never rejects, never hangs).
* Re-readable: each call resolves with the current config, awaiting the
* first fetch if none has landed. `await` it in `setup` to block until
* config is known, or `.then()` it to reconfigure once it arrives; later
* changes arrive via `onRemoteConfig`.
*/
getRemoteConfig(): Promise<RemoteConfig | undefined>;
/** Fires when server-provided config arrives or changes. */
readonly onRemoteConfig: Listener<RemoteConfig>;
/** Fires for every captured event — hot path, keep handlers cheap and synchronous. */
readonly onEvent: Listener<CapturedEventInfo>;
/** Fires when a new session starts, including on reset (discriminate via `reason`). */
readonly onNewSession: Listener<NewSessionInfo>;
/**
* Resolves another registered extension by a capability token it provides, or

@@ -135,3 +59,3 @@ * `undefined` if nothing registered provides it (not installed, or not loaded

getExtension<T>(token: ExtensionToken<T>): T | undefined;
/** Async key-value storage scoped to this client instance and extension. */
/** Awaitable key-value storage backed by the host client's persistence. */
readonly kv: KeyValueStore;

@@ -138,0 +62,0 @@ /** Logger that follows the host client's debug/noise policy. */

@@ -29,3 +29,3 @@ "use strict";

});
const packageVersion = "0.2.1";
const packageVersion = "0.2.2";
const Config = {

@@ -32,0 +32,0 @@ DEBUG: false,

@@ -1,2 +0,2 @@

const packageVersion = "0.2.1";
const packageVersion = "0.2.2";
const Config = {

@@ -3,0 +3,0 @@ DEBUG: false,

@@ -14,1 +14,3 @@ /**

}
/** Invokes teardown at most once and returns its first result to every caller. */
export declare function createDisposable(dispose: () => void | Promise<void>): Disposable;
"use strict";
var __webpack_require__ = {};
(()=>{
__webpack_require__.d = (exports1, definition)=>{
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
enumerable: true,
get: definition[key]
});
};
})();
(()=>{
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
})();
(()=>{
__webpack_require__.r = (exports1)=>{

@@ -15,5 +26,24 @@ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {

__webpack_require__.r(__webpack_exports__);
for(var __webpack_i__ in __webpack_exports__)exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
__webpack_require__.d(__webpack_exports__, {
createDisposable: ()=>createDisposable
});
function createDisposable(dispose) {
let active = true;
let result;
return {
dispose: ()=>{
if (active) {
active = false;
result = dispose();
}
return result;
}
};
}
exports.createDisposable = __webpack_exports__.createDisposable;
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
"createDisposable"
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
Object.defineProperty(exports, '__esModule', {
value: true
});

@@ -6,7 +6,10 @@ /**

export type { Extension } from './extension';
export type { Disposable } from './disposable';
export { CoreExtension } from './core-extension';
export type { DeepReadonly, SessionContext, NewSessionReason, NewSessionInfo, CapturedEventInfo, CaptureOptions, } from './core-extension';
export * from './types';
export { createDisposable, type Disposable } from './disposable';
export type { ExtensionToken } from './token';
export type { Listener } from './pubsub';
export { Publisher } from './pubsub';
export type { Client, SessionContext, NewSessionReason, NewSessionInfo, CapturedEventInfo, CaptureOptions, ApiResponse, ApiRequestInit, RemoteConfig, } from './client';
export type { Client, ApiResponse, ApiRequestInit } from './client';
export type { KeyValueStore } from './persistence';
"use strict";
var __webpack_require__ = {};
var __webpack_modules__ = {
"./core-extension": function(module) {
module.exports = require("./core-extension.js");
},
"./disposable": function(module) {
module.exports = require("./disposable.js");
},
"./pubsub": function(module) {
module.exports = require("./pubsub.js");
},
"./types": function(module) {
module.exports = require("./types/index.js");
}
};
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
var cachedModule = __webpack_module_cache__[moduleId];
if (void 0 !== cachedModule) return cachedModule.exports;
var module = __webpack_module_cache__[moduleId] = {
exports: {}
};
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.exports;
}
(()=>{
__webpack_require__.n = (module)=>{
var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
__webpack_require__.d(getter, {
a: getter
});
return getter;
};
})();
(()=>{
__webpack_require__.d = (exports1, definition)=>{

@@ -25,10 +57,31 @@ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {

var __webpack_exports__ = {};
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
Publisher: ()=>external_pubsub_js_namespaceObject.Publisher
});
const external_pubsub_js_namespaceObject = require("./pubsub.js");
(()=>{
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
CoreExtension: ()=>_core_extension__WEBPACK_IMPORTED_MODULE_0__.CoreExtension,
Publisher: ()=>_pubsub__WEBPACK_IMPORTED_MODULE_3__.Publisher,
createDisposable: ()=>_disposable__WEBPACK_IMPORTED_MODULE_2__.createDisposable
});
var _core_extension__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./core-extension");
var _types__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__("./types");
var __WEBPACK_REEXPORT_OBJECT__ = {};
for(var __WEBPACK_IMPORT_KEY__ in _types__WEBPACK_IMPORTED_MODULE_1__)if ([
"createDisposable",
"default",
"CoreExtension",
"Publisher"
].indexOf(__WEBPACK_IMPORT_KEY__) < 0) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
return _types__WEBPACK_IMPORTED_MODULE_1__[key];
}).bind(0, __WEBPACK_IMPORT_KEY__);
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
var _disposable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__("./disposable");
var _pubsub__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__("./pubsub");
})();
exports.CoreExtension = __webpack_exports__.CoreExtension;
exports.Publisher = __webpack_exports__.Publisher;
exports.createDisposable = __webpack_exports__.createDisposable;
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
"Publisher"
"CoreExtension",
"Publisher",
"createDisposable"
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];

@@ -35,0 +88,0 @@ Object.defineProperty(exports, '__esModule', {

@@ -0,2 +1,5 @@

import { CoreExtension } from "./core-extension.mjs";
import { createDisposable } from "./disposable.mjs";
import { Publisher } from "./pubsub.mjs";
export { Publisher };
export * from "./types/index.mjs";
export { CoreExtension, Publisher, createDisposable };
/**
* Async key-value store for small extension state. Backed by whatever the
* client provides — a synchronous store resolves immediately, an asynchronous
* one (e.g. IndexedDB) does real I/O — so reads and writes are always awaited.
* Key-value store for small extension state. Implementations backed by
* synchronous persistence may return immediately, while asynchronous stores
* (for example IndexedDB) may return promises. Consumers can await either.
*
* The store is namespaced to the client instance, so keys are local to the
* extension and never collide with core SDK state. Values must be
* JSON-serializable; setting `null`/`undefined` removes the key.
* Keys map verbatim to the host client's shared persistence. In browser-v1,
* unknown keys are normally included as event properties, collisions overwrite
* host/core state, and reset clears them. Every SDK-owned key therefore needs an
* explicit event/hidden/derived exposure policy in each host, and sensitive data
* must not be stored unless its transmission is approved. Values must be
* JSON-serializable.
*/

@@ -16,10 +19,10 @@ export interface KeyValueStore {

*/
get<T = unknown>(key: string): Promise<T | undefined>;
get<T = unknown>(key: string): T | undefined | Promise<T | undefined>;
/**
* Store a JSON-serializable value by key. Passing `null` or `undefined`
* removes the key instead of persisting that value.
* Forward a JSON-serializable value, including nullish values, to the host's
* native persistence. `undefined` is not portable or durable storage.
*/
set(key: string, value: unknown): Promise<void>;
/** Remove a value by key. Resolves successfully when the key is already absent. */
remove(key: string): Promise<void>;
set(key: string, value: unknown): void | Promise<void>;
/** Remove a value by key. This is the portable deletion operation. */
remove(key: string): void | Promise<void>;
}

@@ -1,2 +0,2 @@

import type { Disposable } from './disposable';
import { type Disposable } from './disposable';
/**

@@ -3,0 +3,0 @@ * Call it with a handler to start listening; dispose the returned

@@ -29,2 +29,3 @@ "use strict";

});
const external_disposable_js_namespaceObject = require("./disposable.js");
class Publisher {

@@ -51,10 +52,7 @@ publish(payload) {

this._subscriptions.push(subscription);
return {
dispose: ()=>{
if (!subscription.isActive) return;
subscription.isActive = false;
const index = this._subscriptions.indexOf(subscription);
if (-1 !== index) this._subscriptions.splice(index, 1);
}
};
return (0, external_disposable_js_namespaceObject.createDisposable)(()=>{
subscription.isActive = false;
const index = this._subscriptions.indexOf(subscription);
if (-1 !== index) this._subscriptions.splice(index, 1);
});
};

@@ -61,0 +59,0 @@ }

@@ -0,1 +1,2 @@

import { createDisposable } from "./disposable.mjs";
class Publisher {

@@ -22,10 +23,7 @@ publish(payload) {

this._subscriptions.push(subscription);
return {
dispose: ()=>{
if (!subscription.isActive) return;
subscription.isActive = false;
const index = this._subscriptions.indexOf(subscription);
if (-1 !== index) this._subscriptions.splice(index, 1);
}
};
return createDisposable(()=>{
subscription.isActive = false;
const index = this._subscriptions.indexOf(subscription);
if (-1 !== index) this._subscriptions.splice(index, 1);
});
};

@@ -32,0 +30,0 @@ }

{
"name": "@posthog/browser-common",
"version": "0.2.1",
"version": "0.2.2",
"description": "Internal shared browser utilities and extension primitives for PostHog Browser SDKs",

@@ -32,2 +32,5 @@ "license": "MIT",

],
"extension-runtime": [
"dist/extension-runtime.d.ts"
],
"utils/*": [

@@ -54,2 +57,7 @@ "dist/utils/*.d.ts"

},
"./extension-runtime": {
"types": "./dist/extension-runtime.d.ts",
"require": "./dist/extension-runtime.js",
"import": "./dist/extension-runtime.mjs"
},
"./utils/*": {

@@ -62,4 +70,4 @@ "types": "./dist/utils/*.d.ts",

"dependencies": {
"@posthog/types": "^1.398.0",
"@posthog/core": "^1.45.1"
"@posthog/core": "^1.45.1",
"@posthog/types": "^1.398.0"
},

@@ -66,0 +74,0 @@ "devDependencies": {

+47
-23

@@ -9,13 +9,14 @@ # @posthog/browser-common

The shared extension contract includes the interface an extension implements
(`Extension`), the host capabilities it is handed (`Client`), and small shared
runtime primitives such as `Publisher`.
(`Extension`), the host services it is handed (`Client`), the core analytics
capability (`CoreExtension`), and small shared runtime primitives such as
`Publisher`.
An extension written against this contract runs unchanged across major versions of the web SDK:
This contract is designed so an extension can run unchanged across major
versions of the web SDK. Concrete host adapters remain owned by their SDK
packages; browser-v1 and browser-v2 composition and loading integration are
separate from this shared runtime.
- **v1** is synchronous; extensions are registered statically.
- **v2** is asynchronous; extensions are loaded dynamically.
A conforming SDK provides a _client adapter_ that implements `Client` over its
own internals, so extension code never depends on a specific SDK.
Each SDK provides a _client adapter_ that implements `Client` over its own
internals, so extension code never depends on a specific SDK.
## Concepts

@@ -28,3 +29,3 @@

```ts
import type { Disposable, Extension } from '@posthog/browser-common'
import { CoreExtension, type Disposable, type Extension } from '@posthog/browser-common'

@@ -37,3 +38,7 @@ export function webContext(): Extension {

setup(client) {
removeProperties = client.registerDynamicEventProperties(() => ({
const core = client.getExtension(CoreExtension)
if (!core) {
throw new Error('CoreExtension is required')
}
removeProperties = core.registerDynamicEventProperties(() => ({
$current_url: window.location.href,

@@ -54,20 +59,41 @@ }))

Anything in `setup` that returns a `Disposable` must be held by the extension
and disposed in `dispose()`.
and disposed in `dispose()`. Use `createDisposable(teardown)` when adapting a
callback into idempotent teardown.
### `Client`
What an extension is given in `setup` — the host's capability surface:
What an extension is given in `setup` — the host's extension services:
- **identity & session** (synchronous reads): `distinctId`, `anonymousId`, `groups`, `session`
- **events**: `capture(...)`, `registerDynamicEventProperties(...)` (contribute properties), `onEvent(...)` (observe)
- **transport**: `apiRequest(path, init?)`
- **server config**: `getRemoteConfig()` (current), `onRemoteConfig(...)` (changes)
- **lifecycle**: `onNewSession(...)`
- **registry**: `getExtension(token)`
- **storage & logging**: `kv`, `logger`
Synchronous members are always-ready in-memory reads; everything that does I/O
or waits for readiness (`capture`, `apiRequest`, `kv`, `getRemoteConfig`) is
asynchronous.
### `CoreExtension`
A conforming host must register one `CoreExtension` before setting up product
extensions. Resolve it through `client.getExtension(CoreExtension)` for behavior
owned by the PostHog client's analytics core:
- **identity & session**: `distinctId`, `anonymousId`, `groups`, `session`
- **events**: `capture(...)`, `registerDynamicEventProperties(...)`, `onEvent(...)`
- **lifecycle**: `onNewSession(...)`
- **server config**: `getRemoteConfig()` (current), `onRemoteConfig(...)` (changes)
Identity and session are always-ready synchronous reads. Operations that perform
I/O, including `capture`, `apiRequest`, `kv`, and `getRemoteConfig`, are
awaitable.
### Host runtime
PostHog browser SDK implementations share extension registration and teardown
through `ExtensionRuntime`, imported from the dedicated
`@posthog/browser-common/extension-runtime` subpath. It reserves names and
capability tokens during setup, publishes providers only after successful
readiness, and disposes extensions once in reverse registration order. Concrete
SDKs still own the `Client` adapter, Core implementation, and SDK lifecycle
hooks.
`ExtensionRuntime` is host infrastructure, not part of the extension-author
surface exported from the package root.
### `Publisher`

@@ -134,5 +160,3 @@

Early and internal. The package currently defines the extension contract, the
shared `Publisher` helper, and directly imported browser utilities under
`utils/*` subpaths. Additional shared runtime helpers — key-value stores, the
registry implementation, and a test `Client` — will land alongside the first
ported extension.
core analytics capability, a shared host runtime, shared lifecycle helpers, and
directly imported browser utilities under `utils/*` subpaths.