@posthog/browser-common 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -56,17 +56,37 @@ What an extension is given in `setup` — the adapter shared by extensions on th
56
56
 
57
57
  - **identity and session**: `distinctId`, `anonymousId`, `deviceId`, `groups`, `session`, `initialPersonProperties`
58
58
  - **SDK metadata**: `library`
59
+ - **capture permission**: `canCapture`
59
60
  - **events**: `capture(...)`, `registerDynamicEventProperties(...)`, `onEvent(...)`
61
+ - **extensions**: `getExtension(token)`
60
62
  - **server config**: `onRemoteConfig(...)`
61
63
  - **transport**: `projectToken`, `sendRequest(path, init?)`, including `compression` and `sentAt` options
62
64
  - **storage and logging**: `kv`, `logger`
63
65
 
64
- Identity, session, SDK metadata, and the public project token are always-ready synchronous reads. `capture` and
65
- `sendRequest` are awaitable. For `sendRequest`, `sentAt` controls `sent_at` placement on POST requests; GET query mode
66
+ Identity, session, SDK metadata, capture permission, and the public project token are always-ready synchronous reads.
67
+ `capture` and `sendRequest` are awaitable. For `sendRequest`, `sentAt` controls `sent_at` placement on POST requests; GET query mode
66
68
  uses the cache-busting `_` parameter instead, and GET body mode has no effect. `onRemoteConfig` immediately replays the
67
69
  latest known success or failure and then reports subsequent outcomes. Extensions that want a named log prefix can
68
70
  create a child with `client.logger.createLogger('[myExtension]')`.
69
71
 
72
+ Extensions that expose controls to other extensions should export a typed stable-name token:
73
+
74
+ ```ts
75
+ import type { Extension, ExtensionToken } from '@posthog/browser-common'
76
+
77
+ export interface DiagnosticsExtension extends Extension {
78
+ flush(): void
79
+ }
80
+
81
+ export const DiagnosticsExtension = 'diagnostics' as ExtensionToken<DiagnosticsExtension>
82
+
83
+ const diagnostics = client.getExtension(DiagnosticsExtension)
84
+ diagnostics?.flush()
85
+ ```
86
+
87
+ Tokens are strings at runtime and must match the installed extension's `Extension.name`. Lookup is per-client and optional; a
88
+ returned extension may still be running `setup`, so consumers must not create mandatory startup cycles.
89
+
70
90
  Initialize KV during asynchronous setup before using its synchronous buffer:
71
91
 
72
92
  ```ts
@@ -89,9 +109,10 @@ store sensitive values unless their transmission is approved.
89
109
  PostHog browser SDK implementations share extension registration and teardown
90
110
  through `ExtensionRuntime`, imported from the dedicated
91
111
  `@posthog/browser-common/extension-runtime` subpath. It reserves extension names
92
- during setup, rolls back failed setup, and disposes extensions once in reverse
93
- registration order without waiting for pending setup. Concrete SDKs still own
94
- their `Client` adapter and SDK lifecycle hooks.
112
+ during setup, exposes registered extensions by typed stable name through `Client`,
113
+ rolls back failed setup, and disposes extensions once in reverse registration
114
+ order without waiting for pending setup. Concrete SDKs still own their `Client`
115
+ adapter and SDK lifecycle hooks.
95
116
 
96
117
  `ExtensionRuntime` is host infrastructure, not part of the extension-author
97
118
  surface exported from the package root.
package/dist/client.d.ts CHANGED
@@ -2,7 +2,9 @@ import type { Logger } from '@posthog/core';
2
2
  import type { Properties } from '@posthog/types';
3
3
  import type { Compression } from './types/compression';
4
4
  import type { Disposable } from './disposable';
5
+ import type { Extension } from './extension';
5
6
  import type { KeyValueStore } from './persistence';
7
+ import type { ExtensionToken } from './token';
6
8
  import type { Listener } from './pubsub';
7
9
  import type { RemoteConfigResult } from './types/remote-config';
8
10
  /** Recursively marks object properties as readonly while preserving callable values. */
@@ -95,10 +97,16 @@ export interface Client {
95
97
  readonly groups: DeepReadonly<Record<string, string>>;
96
98
  /** The current session, created on first read if needed. */
97
99
  readonly session: SessionContext;
100
+ /** Whether the host currently permits data capture. */
101
+ readonly canCapture: boolean;
98
102
  /** Records an analytics event through the client's normal pipeline. */
99
103
  capture(event: string, properties?: Properties | null, options?: CaptureOptions): Promise<void>;
100
104
  /** Registers a synchronous producer of properties merged into every captured event. */
101
105
  registerDynamicEventProperties(producer: () => Record<string, unknown>): Disposable;
106
+ /** Returns the extension registered under a typed stable name, or `undefined` when it is not installed. */
107
+ getExtension<T extends Extension>(token: ExtensionToken<T>): T | undefined;
108
+ /** Returns the extension registered under a stable name, or `undefined` when it is not installed. */
109
+ getExtension<T extends Extension = Extension>(name: string): T | undefined;
102
110
  /** Fires for every captured event through a deeply readonly view. */
103
111
  readonly onEvent: Listener<CapturedEventInfo>;
104
112
  /** Replays the latest remote-config outcome on subscription and fires for subsequent outcomes. */
package/dist/config.js CHANGED
@@ -26,7 +26,7 @@ __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
27
  default: ()=>__WEBPACK_DEFAULT_EXPORT__
28
28
  });
29
- const packageVersion = "0.5.1";
29
+ const packageVersion = "0.6.0";
30
30
  const Config = {
31
31
  DEBUG: false,
32
32
  LIB_VERSION: packageVersion,
package/dist/config.mjs CHANGED
@@ -1,4 +1,4 @@
1
- const packageVersion = "0.5.1";
1
+ const packageVersion = "0.6.0";
2
2
  const Config = {
3
3
  DEBUG: false,
4
4
  LIB_VERSION: packageVersion,
@@ -2,6 +2,7 @@ import { type Logger } from '@posthog/core';
2
2
  import type { Client } from './client';
3
3
  import type { Disposable } from './disposable';
4
4
  import type { Extension } from './extension';
5
+ import type { ExtensionToken } from './token';
5
6
  /** Shared setup and lifecycle registry for browser extension hosts. */
6
7
  export declare class ExtensionRuntime implements Disposable {
7
8
  private readonly _logger;
@@ -11,6 +12,10 @@ export declare class ExtensionRuntime implements Disposable {
11
12
  constructor(_logger: Logger, _client: Client);
12
13
  /** Reserves an extension name and sets it up with the host client adapter. */
13
14
  add(extension: Extension): Promise<void>;
15
+ /** Returns a registered extension by its typed stable name, including while setup is in progress. */
16
+ getExtension<T extends Extension>(token: ExtensionToken<T>): T | undefined;
17
+ /** Returns a registered extension by its stable name, including while setup is in progress. */
18
+ getExtension<T extends Extension = Extension>(name: string): T | undefined;
14
19
  /** Releases every registered extension once in reverse registration order without waiting for pending setup. */
15
20
  dispose(): void;
16
21
  private _disposeExtension;
@@ -48,6 +48,9 @@ class ExtensionRuntime {
48
48
  if (active) this._disposeExtension(extension);
49
49
  }
50
50
  }
51
+ getExtension(name) {
52
+ return this._extensions.get(name);
53
+ }
51
54
  dispose() {
52
55
  if (this._disposed) return;
53
56
  this._disposed = true;
@@ -20,6 +20,9 @@ class ExtensionRuntime {
20
20
  if (active) this._disposeExtension(extension);
21
21
  }
22
22
  }
23
+ getExtension(name) {
24
+ return this._extensions.get(name);
25
+ }
23
26
  dispose() {
24
27
  if (this._disposed) return;
25
28
  this._disposed = true;
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  * clients.
4
4
  */
5
5
  export type { Extension } from './extension';
6
+ export type { ExtensionToken } from './token';
6
7
  export * from './types';
7
8
  export { createDisposable, type Disposable } from './disposable';
8
9
  export type { Listener } from './pubsub';
@@ -0,0 +1,14 @@
1
+ /** Phantom brand carrying an extension type without emitting runtime code. */
2
+ declare const extensionTokenType: unique symbol;
3
+ /**
4
+ * A typed stable name for resolving an installed extension.
5
+ *
6
+ * Tokens are plain strings at runtime, so independently compiled scripts can
7
+ * share them without a registry or object-identity contract. The generic brand
8
+ * lets `Client.getExtension` infer the extension type. A token's string
9
+ * value must exactly match its extension's stable `name`.
10
+ */
11
+ export type ExtensionToken<T> = string & {
12
+ readonly [extensionTokenType]: T;
13
+ };
14
+ export {};
package/dist/token.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.r = (exports1)=>{
5
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
6
+ value: 'Module'
7
+ });
8
+ Object.defineProperty(exports1, '__esModule', {
9
+ value: true
10
+ });
11
+ };
12
+ })();
13
+ var __webpack_exports__ = {};
14
+ __webpack_require__.r(__webpack_exports__);
15
+ for(var __webpack_i__ in __webpack_exports__)exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
16
+ Object.defineProperty(exports, '__esModule', {
17
+ value: true
18
+ });
package/dist/token.mjs ADDED
File without changes
@@ -1,4 +1,5 @@
1
- import type { SurveyAppearance as CoreSurveyAppearance, SurveyQuestionTranslation, SurveyTranslation, SurveyValidationRule } from '@posthog/core';
1
+ import type { PropertyMatchType, SurveyAppearance as CoreSurveyAppearance, SurveyEventWithFilters, SurveyQuestionTranslation, SurveyTranslation, SurveyValidationRule } from '@posthog/core';
2
+ export type { PropertyFilters, PropertyMatchType, PropertyOperator, SurveyEventWithFilters } from '@posthog/core';
2
3
  export declare const SurveyEventType: {
3
4
  readonly Activation: "events";
4
5
  readonly Cancellation: "cancelEvents";
@@ -85,19 +86,6 @@ export declare const DisplaySurveyType: {
85
86
  readonly Inline: "inline";
86
87
  };
87
88
  export type DisplaySurveyType = (typeof DisplaySurveyType)[keyof typeof DisplaySurveyType];
88
- export type PropertyMatchType = 'regex' | 'not_regex' | 'exact' | 'is_not' | 'icontains' | 'not_icontains';
89
- /** Extended survey operator type with numeric comparisons. */
90
- export type PropertyOperator = PropertyMatchType | 'gt' | 'lt';
91
- export type PropertyFilters = {
92
- [propertyName: string]: {
93
- values: string[];
94
- operator: PropertyOperator;
95
- };
96
- };
97
- export interface SurveyEventWithFilters {
98
- name: string;
99
- propertyFilters?: PropertyFilters;
100
- }
101
89
  export type SurveyQuestionDescriptionContentType = 'html' | 'text';
102
90
  /** Browser survey appearance, including browser-only placement and rendering options. */
103
91
  export interface SurveyAppearance extends Omit<CoreSurveyAppearance, 'position' | 'widgetType'> {
@@ -240,4 +228,3 @@ export interface ActionStepType {
240
228
  type?: string;
241
229
  }[];
242
230
  }
243
- export {};
@@ -1,12 +1,7 @@
1
+ import type { PropertyOperator } from '@posthog/core';
1
2
  import type { Properties } from '@posthog/types';
2
- export type PropertyMatchType = 'exact' | 'is_not' | 'regex' | 'not_regex' | 'icontains' | 'not_icontains';
3
- export type PropertyOperator = PropertyMatchType | 'gt' | 'lt';
4
- export type PropertyFilters = {
5
- [propertyName: string]: {
6
- values: string[];
7
- operator: PropertyOperator;
8
- };
9
- };
3
+ export type { PropertyFilters, PropertyMatchType, PropertyOperator } from '@posthog/core';
4
+ export { matchPropertyFilters, propertyComparisons } from '@posthog/core/surveys';
10
5
  export interface SessionRecordingTriggerPropertyFilter {
11
6
  key: string;
12
7
  value?: string | number | boolean | (string | number | boolean)[] | null;
@@ -14,10 +9,8 @@ export interface SessionRecordingTriggerPropertyFilter {
14
9
  type?: string | null;
15
10
  }
16
11
  export declare function getPersonPropertiesHash(distinct_id: string, userPropertiesToSet?: Properties, userPropertiesToSetOnce?: Properties): string;
17
- export declare const propertyComparisons: Record<PropertyOperator, (targets: string[], values: string[]) => boolean>;
18
12
  /**
19
13
  * Evaluate trigger property filters (WHERE clauses) against event and person properties.
20
14
  * All filters must match (implicit AND). Returns true if no filters are present.
21
15
  */
22
16
  export declare function matchTriggerPropertyFilters(filters: SessionRecordingTriggerPropertyFilter[] | undefined, eventProperties: Properties | undefined, personProperties: Properties | undefined): boolean;
23
- export declare function matchPropertyFilters(propertyFilters: PropertyFilters | undefined, eventProperties: Properties | undefined): boolean;
@@ -24,14 +24,14 @@ var __webpack_require__ = {};
24
24
  var __webpack_exports__ = {};
25
25
  __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
- matchPropertyFilters: ()=>matchPropertyFilters,
28
- propertyComparisons: ()=>propertyComparisons,
27
+ matchPropertyFilters: ()=>surveys_namespaceObject.matchPropertyFilters,
28
+ propertyComparisons: ()=>surveys_namespaceObject.propertyComparisons,
29
29
  getPersonPropertiesHash: ()=>getPersonPropertiesHash,
30
30
  matchTriggerPropertyFilters: ()=>matchTriggerPropertyFilters
31
31
  });
32
32
  const core_namespaceObject = require("@posthog/core");
33
+ const surveys_namespaceObject = require("@posthog/core/surveys");
33
34
  const external_request_utils_js_namespaceObject = require("./request-utils.js");
34
- const external_regex_utils_js_namespaceObject = require("./regex-utils.js");
35
35
  function getPersonPropertiesHash(distinct_id, userPropertiesToSet, userPropertiesToSetOnce) {
36
36
  return (0, external_request_utils_js_namespaceObject.jsonStringify)({
37
37
  distinct_id,
@@ -39,23 +39,6 @@ function getPersonPropertiesHash(distinct_id, userPropertiesToSet, userPropertie
39
39
  userPropertiesToSetOnce
40
40
  });
41
41
  }
42
- const propertyComparisons = {
43
- exact: (targets, values)=>values.some((value)=>targets.some((target)=>value === target)),
44
- is_not: (targets, values)=>values.every((value)=>targets.every((target)=>value !== target)),
45
- regex: (targets, values)=>values.some((value)=>targets.some((target)=>(0, external_regex_utils_js_namespaceObject.isMatchingRegex)(value, target))),
46
- not_regex: (targets, values)=>values.every((value)=>targets.every((target)=>!(0, external_regex_utils_js_namespaceObject.isMatchingRegex)(value, target))),
47
- icontains: (targets, values)=>values.map(toLowerCase).some((value)=>targets.map(toLowerCase).some((target)=>value.includes(target))),
48
- not_icontains: (targets, values)=>values.map(toLowerCase).every((value)=>targets.map(toLowerCase).every((target)=>!value.includes(target))),
49
- gt: (targets, values)=>values.some((value)=>{
50
- const numValue = parseFloat(value);
51
- return !isNaN(numValue) && targets.some((t)=>numValue > parseFloat(t));
52
- }),
53
- lt: (targets, values)=>values.some((value)=>{
54
- const numValue = parseFloat(value);
55
- return !isNaN(numValue) && targets.some((t)=>numValue < parseFloat(t));
56
- })
57
- };
58
- const toLowerCase = (v)=>v.toLowerCase();
59
42
  const NEGATIVE_OPERATORS = new Set([
60
43
  'is_not',
61
44
  'not_icontains',
@@ -68,7 +51,7 @@ function matchTriggerPropertyFilters(filters, eventProperties, personProperties)
68
51
  const propertyValue = source?.[filter.key];
69
52
  const operator = filter.operator || 'exact';
70
53
  if ((0, core_namespaceObject.isUndefined)(propertyValue) || (0, core_namespaceObject.isNull)(propertyValue)) return NEGATIVE_OPERATORS.has(operator);
71
- const comparisonFunction = propertyComparisons[operator];
54
+ const comparisonFunction = surveys_namespaceObject.propertyComparisons[operator];
72
55
  if (!comparisonFunction) return false;
73
56
  if ((0, core_namespaceObject.isUndefined)(filter.value) || (0, core_namespaceObject.isNull)(filter.value)) return false;
74
57
  const targetValues = (0, core_namespaceObject.isArray)(filter.value) ? filter.value.map(String) : [
@@ -80,19 +63,6 @@ function matchTriggerPropertyFilters(filters, eventProperties, personProperties)
80
63
  return comparisonFunction(targetValues, actualValues);
81
64
  });
82
65
  }
83
- function matchPropertyFilters(propertyFilters, eventProperties) {
84
- if (!propertyFilters) return true;
85
- return Object.entries(propertyFilters).every(([propertyName, filter])=>{
86
- const eventPropertyValue = eventProperties?.[propertyName];
87
- if ((0, core_namespaceObject.isUndefined)(eventPropertyValue) || (0, core_namespaceObject.isNull)(eventPropertyValue)) return false;
88
- const eventValues = [
89
- String(eventPropertyValue)
90
- ];
91
- const comparisonFunction = propertyComparisons[filter.operator];
92
- if (!comparisonFunction) return false;
93
- return comparisonFunction(filter.values, eventValues);
94
- });
95
- }
96
66
  exports.getPersonPropertiesHash = __webpack_exports__.getPersonPropertiesHash;
97
67
  exports.matchPropertyFilters = __webpack_exports__.matchPropertyFilters;
98
68
  exports.matchTriggerPropertyFilters = __webpack_exports__.matchTriggerPropertyFilters;
@@ -1,6 +1,6 @@
1
1
  import { isArray, isNull, isUndefined } from "@posthog/core";
2
+ import { matchPropertyFilters, propertyComparisons } from "@posthog/core/surveys";
2
3
  import { jsonStringify } from "./request-utils.mjs";
3
- import { isMatchingRegex } from "./regex-utils.mjs";
4
4
  function getPersonPropertiesHash(distinct_id, userPropertiesToSet, userPropertiesToSetOnce) {
5
5
  return jsonStringify({
6
6
  distinct_id,
@@ -8,23 +8,6 @@ function getPersonPropertiesHash(distinct_id, userPropertiesToSet, userPropertie
8
8
  userPropertiesToSetOnce
9
9
  });
10
10
  }
11
- const propertyComparisons = {
12
- exact: (targets, values)=>values.some((value)=>targets.some((target)=>value === target)),
13
- is_not: (targets, values)=>values.every((value)=>targets.every((target)=>value !== target)),
14
- regex: (targets, values)=>values.some((value)=>targets.some((target)=>isMatchingRegex(value, target))),
15
- not_regex: (targets, values)=>values.every((value)=>targets.every((target)=>!isMatchingRegex(value, target))),
16
- icontains: (targets, values)=>values.map(toLowerCase).some((value)=>targets.map(toLowerCase).some((target)=>value.includes(target))),
17
- not_icontains: (targets, values)=>values.map(toLowerCase).every((value)=>targets.map(toLowerCase).every((target)=>!value.includes(target))),
18
- gt: (targets, values)=>values.some((value)=>{
19
- const numValue = parseFloat(value);
20
- return !isNaN(numValue) && targets.some((t)=>numValue > parseFloat(t));
21
- }),
22
- lt: (targets, values)=>values.some((value)=>{
23
- const numValue = parseFloat(value);
24
- return !isNaN(numValue) && targets.some((t)=>numValue < parseFloat(t));
25
- })
26
- };
27
- const toLowerCase = (v)=>v.toLowerCase();
28
11
  const NEGATIVE_OPERATORS = new Set([
29
12
  'is_not',
30
13
  'not_icontains',
@@ -49,17 +32,4 @@ function matchTriggerPropertyFilters(filters, eventProperties, personProperties)
49
32
  return comparisonFunction(targetValues, actualValues);
50
33
  });
51
34
  }
52
- function matchPropertyFilters(propertyFilters, eventProperties) {
53
- if (!propertyFilters) return true;
54
- return Object.entries(propertyFilters).every(([propertyName, filter])=>{
55
- const eventPropertyValue = eventProperties?.[propertyName];
56
- if (isUndefined(eventPropertyValue) || isNull(eventPropertyValue)) return false;
57
- const eventValues = [
58
- String(eventPropertyValue)
59
- ];
60
- const comparisonFunction = propertyComparisons[filter.operator];
61
- if (!comparisonFunction) return false;
62
- return comparisonFunction(filter.values, eventValues);
63
- });
64
- }
65
35
  export { getPersonPropertiesHash, matchPropertyFilters, matchTriggerPropertyFilters, propertyComparisons };
@@ -1,2 +1 @@
1
- export declare const isValidRegex: (str: string) => boolean;
2
- export declare const isMatchingRegex: (value: string, pattern: string) => boolean;
1
+ export { isMatchingRegex, isValidRegex } from '@posthog/core/surveys';
@@ -24,25 +24,10 @@ var __webpack_require__ = {};
24
24
  var __webpack_exports__ = {};
25
25
  __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
- isMatchingRegex: ()=>isMatchingRegex,
28
- isValidRegex: ()=>isValidRegex
27
+ isMatchingRegex: ()=>surveys_namespaceObject.isMatchingRegex,
28
+ isValidRegex: ()=>surveys_namespaceObject.isValidRegex
29
29
  });
30
- const isValidRegex = function(str) {
31
- try {
32
- new RegExp(str);
33
- } catch {
34
- return false;
35
- }
36
- return true;
37
- };
38
- const isMatchingRegex = function(value, pattern) {
39
- if (!isValidRegex(pattern)) return false;
40
- try {
41
- return new RegExp(pattern).test(value);
42
- } catch {
43
- return false;
44
- }
45
- };
30
+ const surveys_namespaceObject = require("@posthog/core/surveys");
46
31
  exports.isMatchingRegex = __webpack_exports__.isMatchingRegex;
47
32
  exports.isValidRegex = __webpack_exports__.isValidRegex;
48
33
  for(var __webpack_i__ in __webpack_exports__)if (-1 === [
@@ -1,17 +1,2 @@
1
- const isValidRegex = function(str) {
2
- try {
3
- new RegExp(str);
4
- } catch {
5
- return false;
6
- }
7
- return true;
8
- };
9
- const isMatchingRegex = function(value, pattern) {
10
- if (!isValidRegex(pattern)) return false;
11
- try {
12
- return new RegExp(pattern).test(value);
13
- } catch {
14
- return false;
15
- }
16
- };
1
+ import { isMatchingRegex, isValidRegex } from "@posthog/core/surveys";
17
2
  export { isMatchingRegex, isValidRegex };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/browser-common",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Internal shared browser utilities and extension primitives for PostHog Browser SDKs",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -65,8 +65,8 @@
65
65
  }
66
66
  },
67
67
  "dependencies": {
68
- "@posthog/core": "^1.48.10",
69
- "@posthog/types": "^1.405.2"
68
+ "@posthog/core": "^1.48.11",
69
+ "@posthog/types": "^1.406.1"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@rslib/core": "0.10.6",