@posthog/browser-common 0.2.3 → 0.2.5
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 +33 -74
- package/dist/client.d.ts +57 -18
- package/dist/config.js +1 -1
- package/dist/config.mjs +1 -1
- package/dist/disposable.d.ts +5 -13
- package/dist/disposable.js +3 -3
- package/dist/disposable.mjs +3 -3
- package/dist/extension-runtime.d.ts +9 -26
- package/dist/extension-runtime.js +24 -58
- package/dist/extension-runtime.mjs +24 -58
- package/dist/extension.d.ts +11 -23
- package/dist/index.d.ts +1 -4
- package/dist/index.js +9 -17
- package/dist/index.mjs +1 -2
- package/dist/utils/prototype-utils.js +20 -9
- package/dist/utils/prototype-utils.mjs +21 -10
- package/package.json +4 -3
- package/dist/core-extension.d.ts +0 -80
- package/dist/core-extension.js +0 -36
- package/dist/core-extension.mjs +0 -2
- package/dist/token.d.ts +0 -22
- package/dist/token.js +0 -18
- package/dist/token.mjs +0 -0
package/README.md
CHANGED
|
@@ -6,26 +6,22 @@ runtime, but it is not a public API surface and does not provide compatibility
|
|
|
6
6
|
guarantees outside PostHog SDK packages.
|
|
7
7
|
|
|
8
8
|
The shared extension contract includes the interface an extension implements
|
|
9
|
-
(`Extension`), the host
|
|
10
|
-
|
|
11
|
-
`Publisher`.
|
|
9
|
+
(`Extension`), the host adapter it receives (`Client`), and small shared runtime
|
|
10
|
+
primitives such as `Publisher`.
|
|
12
11
|
|
|
13
12
|
This contract is designed so an extension can run unchanged across major
|
|
14
13
|
versions of the web SDK. Concrete host adapters remain owned by their SDK
|
|
15
14
|
packages; browser-v1 and browser-v2 composition and loading integration are
|
|
16
15
|
separate from this shared runtime.
|
|
17
16
|
|
|
18
|
-
A conforming SDK provides a _client adapter_ that implements `Client` over its
|
|
19
|
-
own internals, so extension code never depends on a specific SDK.
|
|
20
|
-
|
|
21
17
|
## Concepts
|
|
22
18
|
|
|
23
19
|
### `Extension`
|
|
24
20
|
|
|
25
|
-
What you implement. The host calls
|
|
21
|
+
What you implement. The host calls `setup` once and optional `dispose` for final cleanup:
|
|
26
22
|
|
|
27
23
|
```ts
|
|
28
|
-
import {
|
|
24
|
+
import type { Disposable, Extension } from '@posthog/browser-common'
|
|
29
25
|
|
|
30
26
|
export function webContext(): Extension {
|
|
31
27
|
let removeProperties: Disposable | undefined
|
|
@@ -33,11 +29,7 @@ export function webContext(): Extension {
|
|
|
33
29
|
return {
|
|
34
30
|
name: 'webContext',
|
|
35
31
|
setup(client) {
|
|
36
|
-
|
|
37
|
-
if (!core) {
|
|
38
|
-
throw new Error('CoreExtension is required')
|
|
39
|
-
}
|
|
40
|
-
removeProperties = core.registerDynamicEventProperties(() => ({
|
|
32
|
+
removeProperties = client.registerDynamicEventProperties(() => ({
|
|
41
33
|
$current_url: window.location.href,
|
|
42
34
|
}))
|
|
43
35
|
},
|
|
@@ -48,93 +40,63 @@ export function webContext(): Extension {
|
|
|
48
40
|
}
|
|
49
41
|
```
|
|
50
42
|
|
|
51
|
-
`setup(client)` may be async
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
`setup(client)` may be async to read state before the extension is ready. Async
|
|
44
|
+
extensions must guard work after each `await` so cleanup cannot be followed by
|
|
45
|
+
late listener or timer installation. `dispose()` is synchronous, optional,
|
|
46
|
+
idempotent, and best-effort. Static app config goes in the constructor, not on
|
|
47
|
+
`Client`.
|
|
54
48
|
|
|
55
49
|
Anything in `setup` that returns a `Disposable` must be held by the extension
|
|
56
50
|
and disposed in `dispose()`. Use `createDisposable(teardown)` when adapting a
|
|
57
|
-
callback into idempotent teardown.
|
|
51
|
+
synchronous callback into idempotent teardown.
|
|
58
52
|
|
|
59
53
|
### `Client`
|
|
60
54
|
|
|
61
|
-
What an extension is given in `setup` — the host
|
|
62
|
-
|
|
63
|
-
- **transport**: `projectToken`, `sendRequest(path, init?)`
|
|
64
|
-
- **registry**: `getExtension(token)`
|
|
65
|
-
- **storage & logging**: `kv`, `logger`
|
|
66
|
-
|
|
67
|
-
### `CoreExtension`
|
|
55
|
+
What an extension is given in `setup` — the adapter shared by extensions on that host SDK instance:
|
|
68
56
|
|
|
69
|
-
|
|
70
|
-
extensions. Resolve it through `client.getExtension(CoreExtension)` for behavior
|
|
71
|
-
owned by the PostHog client's analytics core:
|
|
72
|
-
|
|
73
|
-
- **identity & session**: `distinctId`, `anonymousId`, `groups`, `session`
|
|
57
|
+
- **identity and session**: `distinctId`, `anonymousId`, `groups`, `session`
|
|
74
58
|
- **events**: `capture(...)`, `registerDynamicEventProperties(...)`, `onEvent(...)`
|
|
75
|
-
- **
|
|
76
|
-
- **
|
|
59
|
+
- **server config**: `getRemoteConfig()` and `onRemoteConfig(...)`
|
|
60
|
+
- **transport**: `projectToken`, `sendRequest(path, init?)`
|
|
61
|
+
- **storage and logging**: `kv`, `logger`
|
|
77
62
|
|
|
78
63
|
Identity, session, and the public project token are always-ready synchronous
|
|
79
|
-
reads. Operations that perform I/O, including `capture`, `sendRequest`,
|
|
80
|
-
and `getRemoteConfig`, are awaitable.
|
|
64
|
+
reads. Operations that may perform I/O, including `capture`, `sendRequest`,
|
|
65
|
+
`kv`, and `getRemoteConfig`, are awaitable. Extensions that want a named log
|
|
66
|
+
prefix can create a child with `client.logger.createLogger('[myExtension]')`.
|
|
81
67
|
|
|
82
68
|
### Host runtime
|
|
83
69
|
|
|
84
70
|
PostHog browser SDK implementations share extension registration and teardown
|
|
85
71
|
through `ExtensionRuntime`, imported from the dedicated
|
|
86
|
-
`@posthog/browser-common/extension-runtime` subpath. It reserves names
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
hooks.
|
|
72
|
+
`@posthog/browser-common/extension-runtime` subpath. It reserves extension names
|
|
73
|
+
during setup, rolls back failed setup, and disposes extensions once in reverse
|
|
74
|
+
registration order without waiting for pending setup. Concrete SDKs still own
|
|
75
|
+
their `Client` adapter and SDK lifecycle hooks.
|
|
91
76
|
|
|
92
77
|
`ExtensionRuntime` is host infrastructure, not part of the extension-author
|
|
93
78
|
surface exported from the package root.
|
|
94
79
|
|
|
95
80
|
### `Publisher`
|
|
96
81
|
|
|
97
|
-
Use `Publisher<T>` when an extension
|
|
98
|
-
|
|
99
|
-
`listener`, and dispose it when the extension is torn down:
|
|
82
|
+
Use `Publisher<T>` when an extension exposes an event stream. Keep the publisher
|
|
83
|
+
private, expose only its listener, and dispose it with the extension:
|
|
100
84
|
|
|
101
85
|
```ts
|
|
102
86
|
import { Publisher, type Listener } from '@posthog/browser-common'
|
|
103
87
|
|
|
104
|
-
const changes = new Publisher<
|
|
88
|
+
const changes = new Publisher<{ enabled: boolean }>()
|
|
89
|
+
export const onChange: Listener<{ enabled: boolean }> = changes.listener
|
|
105
90
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
changes.publish({ flag: 'beta-ui', value: true })
|
|
91
|
+
changes.publish({ enabled: true })
|
|
109
92
|
changes.dispose()
|
|
110
93
|
```
|
|
111
94
|
|
|
112
|
-
## Cross-extension dependencies
|
|
113
|
-
|
|
114
|
-
Extensions depend on one another through tokens, never implementation imports:
|
|
115
|
-
|
|
116
|
-
```ts
|
|
117
|
-
import { FeatureFlags } from './feature-flags/token'
|
|
118
|
-
|
|
119
|
-
const flags = client.getExtension(FeatureFlags) // FeatureFlagsExtension | undefined
|
|
120
|
-
if (flags && (await flags.getFeatureFlag('beta-ui'))) {
|
|
121
|
-
/* … */
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
A token is an implementation-free branded string, so importing it never pulls
|
|
126
|
-
the provider's code into your bundle — each extension stays independently
|
|
127
|
-
tree-shakable and lazily loadable. Use a package-qualified runtime string, such
|
|
128
|
-
as `posthog.featureFlags`, that is globally unique and stable so separately
|
|
129
|
-
compiled scripts resolve the same capability. An extension that provides a
|
|
130
|
-
capability declares its token(s) in `provides`.
|
|
131
|
-
|
|
132
95
|
## Utilities
|
|
133
96
|
|
|
134
97
|
Reusable browser utilities are exposed through `utils/*` subpaths, but they are
|
|
135
98
|
intentionally not re-exported from the package root or a utility barrel. Import
|
|
136
|
-
the exact file
|
|
137
|
-
helpers:
|
|
99
|
+
the exact file needed so lazy extension bundles do not pull in unrelated helpers:
|
|
138
100
|
|
|
139
101
|
```ts
|
|
140
102
|
import { createLogger } from '@posthog/browser-common/utils/logger'
|
|
@@ -145,13 +107,10 @@ import { formDataToQuery } from '@posthog/browser-common/utils/request-utils'
|
|
|
145
107
|
|
|
146
108
|
See the **`develop-extension`** skill
|
|
147
109
|
([`.agents/skills/develop-extension/SKILL.md`](./.agents/skills/develop-extension/SKILL.md))
|
|
148
|
-
for the
|
|
149
|
-
synchronous, dispose your disposables, design for asynchronous readiness,
|
|
150
|
-
cross-extension state goes through `getExtension`, not shared storage), and the
|
|
151
|
-
v1 → `Client` porting map.
|
|
110
|
+
for the complete authoring and browser-v1 porting guide.
|
|
152
111
|
|
|
153
112
|
## Status
|
|
154
113
|
|
|
155
|
-
Early and internal. The package currently defines the extension
|
|
156
|
-
|
|
157
|
-
|
|
114
|
+
Early and internal. The package currently defines the extension and client
|
|
115
|
+
contracts, a shared host runtime, lifecycle helpers, and directly imported
|
|
116
|
+
browser utilities under `utils/*` subpaths.
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
import type { Logger } from '@posthog/core';
|
|
2
|
+
import type { Properties } from '@posthog/types';
|
|
3
|
+
import type { Disposable } from './disposable';
|
|
2
4
|
import type { KeyValueStore } from './persistence';
|
|
3
|
-
import type {
|
|
5
|
+
import type { Listener } from './pubsub';
|
|
6
|
+
import type { RemoteConfig } from './types/remote-config';
|
|
7
|
+
/** Recursively marks object properties as readonly while preserving callable values. */
|
|
8
|
+
export type DeepReadonly<T> = T extends (...args: never[]) => unknown ? T : T extends object ? {
|
|
9
|
+
readonly [K in keyof T]: DeepReadonly<T[K]>;
|
|
10
|
+
} : T;
|
|
11
|
+
/** The current session, stamped on events to tie them to a session and a browser tab. */
|
|
12
|
+
export interface SessionContext {
|
|
13
|
+
/** The stable session identifier attached to events captured during this session. */
|
|
14
|
+
readonly sessionId: string;
|
|
15
|
+
/** The logical browser tab/window identifier attached alongside the session id. */
|
|
16
|
+
readonly windowId: string;
|
|
17
|
+
/** When the session started, as a Unix timestamp in milliseconds. */
|
|
18
|
+
readonly sessionStartTimestamp: number;
|
|
19
|
+
}
|
|
20
|
+
/** A captured event, as observed by `onEvent`. */
|
|
21
|
+
export interface CapturedEventInfo {
|
|
22
|
+
/** The finalized captured event name. */
|
|
23
|
+
readonly event: string;
|
|
24
|
+
/** The final event properties after client defaults and dynamic properties are applied. */
|
|
25
|
+
readonly properties: DeepReadonly<Record<string, unknown>>;
|
|
26
|
+
}
|
|
27
|
+
/** Per-call capture overrides, mirroring the client's public capture options. */
|
|
28
|
+
export interface CaptureOptions {
|
|
29
|
+
/** Override the event timestamp sent to PostHog. */
|
|
30
|
+
timestamp?: Date;
|
|
31
|
+
/** Override the event UUID used for de-duplication. */
|
|
32
|
+
uuid?: string;
|
|
33
|
+
/** Person properties to set, emitted as `$set`. */
|
|
34
|
+
set?: Record<string, unknown>;
|
|
35
|
+
/** Person properties to set if unset, emitted as `$set_once`. */
|
|
36
|
+
setOnce?: Record<string, unknown>;
|
|
37
|
+
}
|
|
4
38
|
/** A minimal response from {@link Client.sendRequest}. */
|
|
5
39
|
export interface ApiResponse {
|
|
6
40
|
/** The HTTP status code returned by the transport, or a client-defined best-effort status for sendBeacon sends. */
|
|
@@ -34,28 +68,33 @@ export interface SendRequestInit {
|
|
|
34
68
|
timeoutMs?: number;
|
|
35
69
|
}
|
|
36
70
|
/**
|
|
37
|
-
* The host SDK
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* Host services that may do I/O are awaitable; a host can complete them
|
|
42
|
-
* synchronously when its underlying implementation supports that. Core
|
|
43
|
-
* analytics behavior is provided separately by the core extension.
|
|
71
|
+
* The host SDK surface handed to an extension in `setup`. A conforming host
|
|
72
|
+
* provides it as an adapter over its own analytics, transport, persistence,
|
|
73
|
+
* event, and remote-config internals.
|
|
44
74
|
*/
|
|
45
75
|
export interface Client {
|
|
76
|
+
/** The id events are currently attributed to. */
|
|
77
|
+
readonly distinctId: string;
|
|
78
|
+
/** The anonymous device id carried across identify calls. */
|
|
79
|
+
readonly anonymousId: string;
|
|
80
|
+
/** Active group memberships attached to events as `$groups`. */
|
|
81
|
+
readonly groups: DeepReadonly<Record<string, string>>;
|
|
82
|
+
/** The current session, created on first read if needed. */
|
|
83
|
+
readonly session: SessionContext;
|
|
84
|
+
/** Records an analytics event through the client's normal pipeline. */
|
|
85
|
+
capture(event: string, properties?: Properties | null, options?: CaptureOptions): Promise<void>;
|
|
86
|
+
/** Registers a synchronous producer of properties merged into every captured event. */
|
|
87
|
+
registerDynamicEventProperties(producer: () => Record<string, unknown>): Disposable;
|
|
88
|
+
/** Fires for every captured event through a deeply readonly view. */
|
|
89
|
+
readonly onEvent: Listener<CapturedEventInfo>;
|
|
90
|
+
/** Resolves with the current remote config, awaiting the first outcome when necessary. */
|
|
91
|
+
getRemoteConfig(): Promise<DeepReadonly<RemoteConfig> | undefined>;
|
|
92
|
+
/** Fires when server-provided config arrives or changes successfully. */
|
|
93
|
+
readonly onRemoteConfig: Listener<DeepReadonly<RemoteConfig>>;
|
|
46
94
|
/** Public project token used to authenticate endpoint-specific requests. */
|
|
47
95
|
readonly projectToken: string;
|
|
48
|
-
/**
|
|
49
|
-
* Sends a request through the host SDK's transport. The extension owns the
|
|
50
|
-
* endpoint-specific path, method, authentication shape, body, and headers.
|
|
51
|
-
*/
|
|
96
|
+
/** Sends a request through the host SDK's transport. */
|
|
52
97
|
sendRequest(path: string, init?: SendRequestInit): Promise<ApiResponse>;
|
|
53
|
-
/**
|
|
54
|
-
* Resolves another registered extension by a capability token it provides, or
|
|
55
|
-
* `undefined` if nothing registered provides it (not installed, or not loaded
|
|
56
|
-
* yet). Lets one extension use another without importing its implementation.
|
|
57
|
-
*/
|
|
58
|
-
getExtension<T>(token: ExtensionToken<T>): T | undefined;
|
|
59
98
|
/** Awaitable key-value storage backed by the host client's persistence. */
|
|
60
99
|
readonly kv: KeyValueStore;
|
|
61
100
|
/** Logger that follows the host client's debug/noise policy. */
|
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.2.
|
|
29
|
+
const packageVersion = "0.2.5";
|
|
30
30
|
const Config = {
|
|
31
31
|
DEBUG: false,
|
|
32
32
|
LIB_VERSION: packageVersion,
|
package/dist/config.mjs
CHANGED
package/dist/disposable.d.ts
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Something with an async-capable teardown. `dispose` may be async so
|
|
3
|
-
* teardown can do final work — e.g. a last flush of buffered data — that the
|
|
4
|
-
* client can await before it finishes shutting down.
|
|
5
|
-
*/
|
|
1
|
+
/** A resource handle with idempotent, best-effort cleanup. */
|
|
6
2
|
export interface Disposable {
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
* idempotent so callers can safely dispose during both extension teardown
|
|
10
|
-
* and client shutdown.
|
|
11
|
-
*/
|
|
12
|
-
dispose(): void | Promise<void>;
|
|
3
|
+
/** Release resources owned by this object. */
|
|
4
|
+
dispose(): void;
|
|
13
5
|
}
|
|
14
|
-
/** Invokes teardown at most once
|
|
15
|
-
export declare function createDisposable(dispose: () => void
|
|
6
|
+
/** Invokes teardown at most once without awaiting Promise results. */
|
|
7
|
+
export declare function createDisposable(dispose: () => void): Disposable;
|
package/dist/disposable.js
CHANGED
|
@@ -26,16 +26,16 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
26
26
|
__webpack_require__.d(__webpack_exports__, {
|
|
27
27
|
createDisposable: ()=>createDisposable
|
|
28
28
|
});
|
|
29
|
+
const core_namespaceObject = require("@posthog/core");
|
|
29
30
|
function createDisposable(dispose) {
|
|
30
31
|
let active = true;
|
|
31
|
-
let result;
|
|
32
32
|
return {
|
|
33
33
|
dispose: ()=>{
|
|
34
34
|
if (active) {
|
|
35
35
|
active = false;
|
|
36
|
-
result = dispose();
|
|
36
|
+
const result = dispose();
|
|
37
|
+
if (result && (0, core_namespaceObject.isFunction)(result.then)) result.then(void 0, ()=>{});
|
|
37
38
|
}
|
|
38
|
-
return result;
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
}
|
package/dist/disposable.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import { isFunction } from "@posthog/core";
|
|
1
2
|
function createDisposable(dispose) {
|
|
2
3
|
let active = true;
|
|
3
|
-
let result;
|
|
4
4
|
return {
|
|
5
5
|
dispose: ()=>{
|
|
6
6
|
if (active) {
|
|
7
7
|
active = false;
|
|
8
|
-
result = dispose();
|
|
8
|
+
const result = dispose();
|
|
9
|
+
if (result && isFunction(result.then)) result.then(void 0, ()=>{});
|
|
9
10
|
}
|
|
10
|
-
return result;
|
|
11
11
|
}
|
|
12
12
|
};
|
|
13
13
|
}
|
|
@@ -2,33 +2,16 @@ 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
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Shared lifecycle and capability registry for browser extension hosts.
|
|
8
|
-
*
|
|
9
|
-
* Hosts provide the concrete Client adapter while this runtime coordinates
|
|
10
|
-
* names, capability readiness, setup failures, and reverse-order teardown.
|
|
11
|
-
*/
|
|
5
|
+
/** Shared setup and lifecycle registry for browser extension hosts. */
|
|
12
6
|
export declare class ExtensionRuntime implements Disposable {
|
|
13
7
|
private readonly _logger;
|
|
8
|
+
private readonly _client;
|
|
14
9
|
private readonly _extensions;
|
|
15
|
-
private
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* Names and tokens remain reserved while asynchronous setup is pending.
|
|
23
|
-
*/
|
|
24
|
-
add(extension: Extension, client: Client): Promise<void>;
|
|
25
|
-
/** Resolves a capability only after its provider has completed setup. */
|
|
26
|
-
getExtension<T>(token: ExtensionToken<T>): T | undefined;
|
|
27
|
-
/** Disposes every registered extension once, in reverse registration order. */
|
|
28
|
-
dispose(): Promise<void>;
|
|
29
|
-
private _disposeAll;
|
|
30
|
-
private _handleSetupFailure;
|
|
31
|
-
private _disposeRegistration;
|
|
32
|
-
private _removeRegistration;
|
|
33
|
-
private _publishRegistration;
|
|
10
|
+
private _disposed;
|
|
11
|
+
constructor(_logger: Logger, _client: Client);
|
|
12
|
+
/** Reserves an extension name and sets it up with the host client adapter. */
|
|
13
|
+
add(extension: Extension): Promise<void>;
|
|
14
|
+
/** Releases every registered extension once in reverse registration order without waiting for pending setup. */
|
|
15
|
+
dispose(): void;
|
|
16
|
+
private _disposeExtension;
|
|
34
17
|
}
|
|
@@ -28,77 +28,43 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
28
28
|
});
|
|
29
29
|
const core_namespaceObject = require("@posthog/core");
|
|
30
30
|
class ExtensionRuntime {
|
|
31
|
-
constructor(_logger){
|
|
31
|
+
constructor(_logger, _client){
|
|
32
32
|
this._logger = _logger;
|
|
33
|
+
this._client = _client;
|
|
33
34
|
this._extensions = new Map();
|
|
34
|
-
this.
|
|
35
|
-
this._providerReservations = new Map();
|
|
36
|
-
this._providers = new Map();
|
|
35
|
+
this._disposed = false;
|
|
37
36
|
}
|
|
38
|
-
async add(extension
|
|
39
|
-
if (this.
|
|
37
|
+
async add(extension) {
|
|
38
|
+
if (this._disposed) throw new Error('Cannot add an extension to a disposed ExtensionRuntime');
|
|
40
39
|
if (this._extensions.has(extension.name)) throw new Error(`Browser extension "${extension.name}" is already registered`);
|
|
41
|
-
|
|
42
|
-
const registered = {
|
|
43
|
-
extension,
|
|
44
|
-
setupPromise: Promise.resolve()
|
|
45
|
-
};
|
|
46
|
-
this._extensions.set(extension.name, registered);
|
|
47
|
-
this._registrationOrder.push(registered);
|
|
48
|
-
for (const token of extension.provides ?? [])this._providerReservations.set(token, registered);
|
|
49
|
-
let setupResult;
|
|
40
|
+
this._extensions.set(extension.name, extension);
|
|
50
41
|
try {
|
|
51
|
-
|
|
42
|
+
const setup = extension.setup(this._client);
|
|
43
|
+
if (setup) await setup;
|
|
52
44
|
} catch (error) {
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
const active = this._extensions.get(extension.name) === extension;
|
|
46
|
+
if (active) this._extensions.delete(extension.name);
|
|
47
|
+
this._logger.error(`Failed to set up browser extension "${extension.name}"`, error);
|
|
48
|
+
if (active) this._disposeExtension(extension);
|
|
55
49
|
}
|
|
56
|
-
if (setupResult && (0, core_namespaceObject.isFunction)(setupResult.then)) registered.setupPromise = setupResult.then(()=>this._publishRegistration(registered)).catch((error)=>this._handleSetupFailure(registered, error));
|
|
57
|
-
else this._publishRegistration(registered);
|
|
58
|
-
return registered.setupPromise;
|
|
59
|
-
}
|
|
60
|
-
getExtension(token) {
|
|
61
|
-
return this._providers.get(token);
|
|
62
50
|
}
|
|
63
51
|
dispose() {
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
async _disposeAll() {
|
|
68
|
-
for (const registered of this._registrationOrder.slice().reverse()){
|
|
69
|
-
await registered.setupPromise;
|
|
70
|
-
await this._disposeRegistration(registered);
|
|
71
|
-
}
|
|
52
|
+
if (this._disposed) return;
|
|
53
|
+
this._disposed = true;
|
|
54
|
+
const extensions = Array.from(this._extensions.values()).reverse();
|
|
72
55
|
this._extensions.clear();
|
|
73
|
-
this.
|
|
74
|
-
this._providerReservations.clear();
|
|
75
|
-
this._providers.clear();
|
|
56
|
+
for (const extension of extensions)this._disposeExtension(extension);
|
|
76
57
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
_disposeRegistration(registered) {
|
|
86
|
-
if (!registered.disposalPromise) registered.disposalPromise = Promise.resolve().then(()=>registered.extension.dispose()).catch((error)=>{
|
|
87
|
-
this._logger.error(`Failed to dispose browser extension "${registered.extension.name}"`, error);
|
|
88
|
-
});
|
|
89
|
-
return registered.disposalPromise;
|
|
90
|
-
}
|
|
91
|
-
_removeRegistration(registered) {
|
|
92
|
-
if (this._extensions.get(registered.extension.name) === registered) this._extensions.delete(registered.extension.name);
|
|
93
|
-
for (const token of registered.extension.provides ?? []){
|
|
94
|
-
if (this._providerReservations.get(token) === registered) this._providerReservations.delete(token);
|
|
95
|
-
if (this._providers.get(token) === registered.extension) this._providers.delete(token);
|
|
58
|
+
_disposeExtension(extension) {
|
|
59
|
+
try {
|
|
60
|
+
const result = extension.dispose?.();
|
|
61
|
+
if (result && (0, core_namespaceObject.isFunction)(result.then)) result.then(void 0, (error)=>{
|
|
62
|
+
this._logger.error(`Failed to dispose browser extension "${extension.name}"`, error);
|
|
63
|
+
});
|
|
64
|
+
} catch (error) {
|
|
65
|
+
this._logger.error(`Failed to dispose browser extension "${extension.name}"`, error);
|
|
96
66
|
}
|
|
97
67
|
}
|
|
98
|
-
_publishRegistration(registered) {
|
|
99
|
-
if (this._disposePromise || this._extensions.get(registered.extension.name) !== registered) return;
|
|
100
|
-
for (const token of registered.extension.provides ?? [])this._providers.set(token, registered.extension);
|
|
101
|
-
}
|
|
102
68
|
}
|
|
103
69
|
exports.ExtensionRuntime = __webpack_exports__.ExtensionRuntime;
|
|
104
70
|
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
@@ -1,75 +1,41 @@
|
|
|
1
1
|
import { isFunction } from "@posthog/core";
|
|
2
2
|
class ExtensionRuntime {
|
|
3
|
-
constructor(_logger){
|
|
3
|
+
constructor(_logger, _client){
|
|
4
4
|
this._logger = _logger;
|
|
5
|
+
this._client = _client;
|
|
5
6
|
this._extensions = new Map();
|
|
6
|
-
this.
|
|
7
|
-
this._providerReservations = new Map();
|
|
8
|
-
this._providers = new Map();
|
|
7
|
+
this._disposed = false;
|
|
9
8
|
}
|
|
10
|
-
async add(extension
|
|
11
|
-
if (this.
|
|
9
|
+
async add(extension) {
|
|
10
|
+
if (this._disposed) throw new Error('Cannot add an extension to a disposed ExtensionRuntime');
|
|
12
11
|
if (this._extensions.has(extension.name)) throw new Error(`Browser extension "${extension.name}" is already registered`);
|
|
13
|
-
|
|
14
|
-
const registered = {
|
|
15
|
-
extension,
|
|
16
|
-
setupPromise: Promise.resolve()
|
|
17
|
-
};
|
|
18
|
-
this._extensions.set(extension.name, registered);
|
|
19
|
-
this._registrationOrder.push(registered);
|
|
20
|
-
for (const token of extension.provides ?? [])this._providerReservations.set(token, registered);
|
|
21
|
-
let setupResult;
|
|
12
|
+
this._extensions.set(extension.name, extension);
|
|
22
13
|
try {
|
|
23
|
-
|
|
14
|
+
const setup = extension.setup(this._client);
|
|
15
|
+
if (setup) await setup;
|
|
24
16
|
} catch (error) {
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
const active = this._extensions.get(extension.name) === extension;
|
|
18
|
+
if (active) this._extensions.delete(extension.name);
|
|
19
|
+
this._logger.error(`Failed to set up browser extension "${extension.name}"`, error);
|
|
20
|
+
if (active) this._disposeExtension(extension);
|
|
27
21
|
}
|
|
28
|
-
if (setupResult && isFunction(setupResult.then)) registered.setupPromise = setupResult.then(()=>this._publishRegistration(registered)).catch((error)=>this._handleSetupFailure(registered, error));
|
|
29
|
-
else this._publishRegistration(registered);
|
|
30
|
-
return registered.setupPromise;
|
|
31
|
-
}
|
|
32
|
-
getExtension(token) {
|
|
33
|
-
return this._providers.get(token);
|
|
34
22
|
}
|
|
35
23
|
dispose() {
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
async _disposeAll() {
|
|
40
|
-
for (const registered of this._registrationOrder.slice().reverse()){
|
|
41
|
-
await registered.setupPromise;
|
|
42
|
-
await this._disposeRegistration(registered);
|
|
43
|
-
}
|
|
24
|
+
if (this._disposed) return;
|
|
25
|
+
this._disposed = true;
|
|
26
|
+
const extensions = Array.from(this._extensions.values()).reverse();
|
|
44
27
|
this._extensions.clear();
|
|
45
|
-
this.
|
|
46
|
-
this._providerReservations.clear();
|
|
47
|
-
this._providers.clear();
|
|
48
|
-
}
|
|
49
|
-
async _handleSetupFailure(registered, error) {
|
|
50
|
-
this._removeRegistration(registered);
|
|
51
|
-
this._logger.error(`Failed to set up browser extension "${registered.extension.name}"`, error);
|
|
52
|
-
if (this._disposePromise) return;
|
|
53
|
-
await this._disposeRegistration(registered);
|
|
54
|
-
const index = this._registrationOrder.indexOf(registered);
|
|
55
|
-
if (-1 !== index) this._registrationOrder.splice(index, 1);
|
|
28
|
+
for (const extension of extensions)this._disposeExtension(extension);
|
|
56
29
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
for (const token of registered.extension.provides ?? []){
|
|
66
|
-
if (this._providerReservations.get(token) === registered) this._providerReservations.delete(token);
|
|
67
|
-
if (this._providers.get(token) === registered.extension) this._providers.delete(token);
|
|
30
|
+
_disposeExtension(extension) {
|
|
31
|
+
try {
|
|
32
|
+
const result = extension.dispose?.();
|
|
33
|
+
if (result && isFunction(result.then)) result.then(void 0, (error)=>{
|
|
34
|
+
this._logger.error(`Failed to dispose browser extension "${extension.name}"`, error);
|
|
35
|
+
});
|
|
36
|
+
} catch (error) {
|
|
37
|
+
this._logger.error(`Failed to dispose browser extension "${extension.name}"`, error);
|
|
68
38
|
}
|
|
69
39
|
}
|
|
70
|
-
_publishRegistration(registered) {
|
|
71
|
-
if (this._disposePromise || this._extensions.get(registered.extension.name) !== registered) return;
|
|
72
|
-
for (const token of registered.extension.provides ?? [])this._providers.set(token, registered.extension);
|
|
73
|
-
}
|
|
74
40
|
}
|
|
75
41
|
export { ExtensionRuntime };
|
package/dist/extension.d.ts
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import type { Disposable } from './disposable';
|
|
2
1
|
import type { Client } from './client';
|
|
3
|
-
import type { ExtensionToken } from './token';
|
|
4
2
|
/**
|
|
5
|
-
* A shared browser extension. The client calls
|
|
6
|
-
*
|
|
7
|
-
* an extension consumes flows
|
|
3
|
+
* A shared browser extension. The client calls `setup(client)` once to start it
|
|
4
|
+
* and optional `dispose()` once for final best-effort resource cleanup.
|
|
5
|
+
* Everything an extension consumes flows through {@link Client}.
|
|
8
6
|
*
|
|
9
|
-
* `setup` may be async so an extension can read async
|
|
10
|
-
* before it is ready
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* dispose them in `dispose`. The host disposes the extension; it does not track
|
|
14
|
-
* the extension's individual subscriptions.
|
|
7
|
+
* `setup` may be async so an extension can read async KV state or remote config
|
|
8
|
+
* before it is ready. Async extensions must guard work after each `await` so
|
|
9
|
+
* cleanup cannot be followed by late listener, timer, or patch installation.
|
|
10
|
+
* The disposables an extension creates in setup are its own to release.
|
|
15
11
|
*
|
|
16
12
|
* An extension that exposes app-facing controls extends `Extension` with named
|
|
17
13
|
* methods that share its state, e.g.:
|
|
@@ -23,23 +19,15 @@ import type { ExtensionToken } from './token';
|
|
|
23
19
|
* isActive(): boolean
|
|
24
20
|
* }
|
|
25
21
|
* ```
|
|
26
|
-
*
|
|
27
|
-
* The client still only calls `setup` and `dispose`; the controls are for the
|
|
28
|
-
* application that constructed the extension.
|
|
29
22
|
*/
|
|
30
|
-
export interface Extension
|
|
23
|
+
export interface Extension {
|
|
31
24
|
/** Stable extension name used for diagnostics and de-duplication within a client instance. */
|
|
32
25
|
readonly name: string;
|
|
33
|
-
/**
|
|
34
|
-
* Capability tokens this extension answers to, so others can resolve it via
|
|
35
|
-
* `client.getExtension(token)`. The extension must be assignable to each
|
|
36
|
-
* token's provided type. Most extensions provide nothing.
|
|
37
|
-
*/
|
|
38
|
-
readonly provides?: readonly ExtensionToken<unknown>[];
|
|
39
26
|
/**
|
|
40
27
|
* Start the extension with the host client's capability surface. Called once
|
|
41
|
-
* after construction; return a promise when setup needs asynchronous state
|
|
42
|
-
* such as persisted data or remote config.
|
|
28
|
+
* after construction; return a promise when setup needs asynchronous state.
|
|
43
29
|
*/
|
|
44
30
|
setup(client: Client): void | Promise<void>;
|
|
31
|
+
/** Release final resources synchronously. Feature-level start/stop remains extension-owned. */
|
|
32
|
+
dispose?(): void;
|
|
45
33
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,12 +3,9 @@
|
|
|
3
3
|
* clients.
|
|
4
4
|
*/
|
|
5
5
|
export type { Extension } from './extension';
|
|
6
|
-
export { CoreExtension } from './core-extension';
|
|
7
|
-
export type { DeepReadonly, SessionContext, NewSessionReason, NewSessionInfo, CapturedEventInfo, CaptureOptions, } from './core-extension';
|
|
8
6
|
export * from './types';
|
|
9
7
|
export { createDisposable, type Disposable } from './disposable';
|
|
10
|
-
export type { ExtensionToken } from './token';
|
|
11
8
|
export type { Listener } from './pubsub';
|
|
12
9
|
export { Publisher } from './pubsub';
|
|
13
|
-
export type { Client, ApiResponse, RequestTarget, RequestTransport, SendRequestInit } from './client';
|
|
10
|
+
export type { Client, DeepReadonly, SessionContext, CapturedEventInfo, CaptureOptions, ApiResponse, RequestTarget, RequestTransport, SendRequestInit, } from './client';
|
|
14
11
|
export type { KeyValueStore } from './persistence';
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __webpack_modules__ = {
|
|
3
|
-
"./core-extension": function(module) {
|
|
4
|
-
module.exports = require("./core-extension.js");
|
|
5
|
-
},
|
|
6
3
|
"./disposable": function(module) {
|
|
7
4
|
module.exports = require("./disposable.js");
|
|
8
5
|
},
|
|
@@ -57,30 +54,25 @@ var __webpack_exports__ = {};
|
|
|
57
54
|
(()=>{
|
|
58
55
|
__webpack_require__.r(__webpack_exports__);
|
|
59
56
|
__webpack_require__.d(__webpack_exports__, {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
createDisposable: ()=>_disposable__WEBPACK_IMPORTED_MODULE_2__.createDisposable
|
|
57
|
+
Publisher: ()=>_pubsub__WEBPACK_IMPORTED_MODULE_2__.Publisher,
|
|
58
|
+
createDisposable: ()=>_disposable__WEBPACK_IMPORTED_MODULE_1__.createDisposable
|
|
63
59
|
});
|
|
64
|
-
var
|
|
65
|
-
var _types__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__("./types");
|
|
60
|
+
var _types__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./types");
|
|
66
61
|
var __WEBPACK_REEXPORT_OBJECT__ = {};
|
|
67
|
-
for(var __WEBPACK_IMPORT_KEY__ in
|
|
68
|
-
"
|
|
62
|
+
for(var __WEBPACK_IMPORT_KEY__ in _types__WEBPACK_IMPORTED_MODULE_0__)if ([
|
|
63
|
+
"Publisher",
|
|
69
64
|
"default",
|
|
70
|
-
"
|
|
71
|
-
"Publisher"
|
|
65
|
+
"createDisposable"
|
|
72
66
|
].indexOf(__WEBPACK_IMPORT_KEY__) < 0) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
|
|
73
|
-
return
|
|
67
|
+
return _types__WEBPACK_IMPORTED_MODULE_0__[key];
|
|
74
68
|
}).bind(0, __WEBPACK_IMPORT_KEY__);
|
|
75
69
|
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
|
|
76
|
-
var
|
|
77
|
-
var
|
|
70
|
+
var _disposable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__("./disposable");
|
|
71
|
+
var _pubsub__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__("./pubsub");
|
|
78
72
|
})();
|
|
79
|
-
exports.CoreExtension = __webpack_exports__.CoreExtension;
|
|
80
73
|
exports.Publisher = __webpack_exports__.Publisher;
|
|
81
74
|
exports.createDisposable = __webpack_exports__.createDisposable;
|
|
82
75
|
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
83
|
-
"CoreExtension",
|
|
84
76
|
"Publisher",
|
|
85
77
|
"createDisposable"
|
|
86
78
|
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { CoreExtension } from "./core-extension.mjs";
|
|
2
1
|
import { createDisposable } from "./disposable.mjs";
|
|
3
2
|
import { Publisher } from "./pubsub.mjs";
|
|
4
3
|
export * from "./types/index.mjs";
|
|
5
|
-
export {
|
|
4
|
+
export { Publisher, createDisposable };
|
|
@@ -37,15 +37,26 @@ function getNativeImplementation(name, assignableWindow) {
|
|
|
37
37
|
let impl = assignableWindow[name];
|
|
38
38
|
if ((0, core_namespaceObject.isNativeFunction)(impl) && !(0, external_type_utils_js_namespaceObject.isAngularZonePresent)()) return cachedImplementations[name] = impl.bind(assignableWindow);
|
|
39
39
|
const document = assignableWindow.document;
|
|
40
|
-
if (document && (0, core_namespaceObject.isFunction)(document.createElement))
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
if (document && (0, core_namespaceObject.isFunction)(document.createElement)) {
|
|
41
|
+
let sandbox;
|
|
42
|
+
let keepSandboxAttached = false;
|
|
43
|
+
try {
|
|
44
|
+
sandbox = document.createElement('iframe');
|
|
45
|
+
sandbox.hidden = true;
|
|
46
|
+
document.head.appendChild(sandbox);
|
|
47
|
+
const contentWindow = sandbox.contentWindow;
|
|
48
|
+
if (contentWindow && contentWindow[name]) {
|
|
49
|
+
impl = contentWindow[name];
|
|
50
|
+
if ('MutationObserver' === name && (0, core_namespaceObject.isWebKit)(assignableWindow.navigator?.userAgent ?? '')) {
|
|
51
|
+
sandbox.classList.add('rr-block', 'ph-no-capture');
|
|
52
|
+
keepSandboxAttached = true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch (e) {
|
|
56
|
+
external_logger_js_namespaceObject.logger.warn(`Could not create sandbox iframe for ${name} check, bailing to assignableWindow.${name}: `, e);
|
|
57
|
+
} finally{
|
|
58
|
+
if (!keepSandboxAttached && sandbox?.parentNode) sandbox.parentNode.removeChild(sandbox);
|
|
59
|
+
}
|
|
49
60
|
}
|
|
50
61
|
if (!impl || !(0, core_namespaceObject.isFunction)(impl)) return impl;
|
|
51
62
|
return cachedImplementations[name] = impl.bind(assignableWindow);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isFunction, isNativeFunction } from "@posthog/core";
|
|
1
|
+
import { isFunction, isNativeFunction, isWebKit } from "@posthog/core";
|
|
2
2
|
import { logger } from "./logger.mjs";
|
|
3
3
|
import { isAngularZonePresent } from "./type-utils.mjs";
|
|
4
4
|
const cachedImplementations = {};
|
|
@@ -8,15 +8,26 @@ function getNativeImplementation(name, assignableWindow) {
|
|
|
8
8
|
let impl = assignableWindow[name];
|
|
9
9
|
if (isNativeFunction(impl) && !isAngularZonePresent()) return cachedImplementations[name] = impl.bind(assignableWindow);
|
|
10
10
|
const document = assignableWindow.document;
|
|
11
|
-
if (document && isFunction(document.createElement))
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
if (document && isFunction(document.createElement)) {
|
|
12
|
+
let sandbox;
|
|
13
|
+
let keepSandboxAttached = false;
|
|
14
|
+
try {
|
|
15
|
+
sandbox = document.createElement('iframe');
|
|
16
|
+
sandbox.hidden = true;
|
|
17
|
+
document.head.appendChild(sandbox);
|
|
18
|
+
const contentWindow = sandbox.contentWindow;
|
|
19
|
+
if (contentWindow && contentWindow[name]) {
|
|
20
|
+
impl = contentWindow[name];
|
|
21
|
+
if ('MutationObserver' === name && isWebKit(assignableWindow.navigator?.userAgent ?? '')) {
|
|
22
|
+
sandbox.classList.add('rr-block', 'ph-no-capture');
|
|
23
|
+
keepSandboxAttached = true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
} catch (e) {
|
|
27
|
+
logger.warn(`Could not create sandbox iframe for ${name} check, bailing to assignableWindow.${name}: `, e);
|
|
28
|
+
} finally{
|
|
29
|
+
if (!keepSandboxAttached && sandbox?.parentNode) sandbox.parentNode.removeChild(sandbox);
|
|
30
|
+
}
|
|
20
31
|
}
|
|
21
32
|
if (!impl || !isFunction(impl)) return impl;
|
|
22
33
|
return cachedImplementations[name] = impl.bind(assignableWindow);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/browser-common",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Internal shared browser utilities and extension primitives for PostHog Browser SDKs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -65,13 +65,14 @@
|
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@posthog/core": "^1.45.
|
|
69
|
-
"@posthog/types": "^1.
|
|
68
|
+
"@posthog/core": "^1.45.3",
|
|
69
|
+
"@posthog/types": "^1.399.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@rslib/core": "0.10.6",
|
|
73
73
|
"@types/jest": "^29.5.14",
|
|
74
74
|
"jest": "29.7.0",
|
|
75
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
75
76
|
"ts-jest": "29.4.11",
|
|
76
77
|
"typescript": "5.8.2",
|
|
77
78
|
"@posthog-tooling/tsconfig-base": "1.1.1"
|
package/dist/core-extension.d.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import type { JsonRecord, Properties } from '@posthog/types';
|
|
2
|
-
import type { Disposable } from './disposable';
|
|
3
|
-
import type { Extension } from './extension';
|
|
4
|
-
import type { Listener } from './pubsub';
|
|
5
|
-
import type { RemoteConfig } from './types/remote-config';
|
|
6
|
-
import type { ExtensionToken } from './token';
|
|
7
|
-
/** Recursively marks object properties as readonly while preserving callable values. */
|
|
8
|
-
export type DeepReadonly<T> = T extends (...args: never[]) => unknown ? T : T extends object ? {
|
|
9
|
-
readonly [K in keyof T]: DeepReadonly<T[K]>;
|
|
10
|
-
} : T;
|
|
11
|
-
/** The current session, stamped on events to tie them to a session and a browser tab. */
|
|
12
|
-
export interface SessionContext {
|
|
13
|
-
/** The stable session identifier attached to events captured during this session. */
|
|
14
|
-
readonly sessionId: string;
|
|
15
|
-
/** The logical browser tab/window identifier attached alongside the session id. */
|
|
16
|
-
readonly windowId: string;
|
|
17
|
-
/** When the session started, as a Unix timestamp in milliseconds. */
|
|
18
|
-
readonly sessionStartTimestamp: number;
|
|
19
|
-
}
|
|
20
|
-
/** Why a new session started (a `reset` also starts a new session). */
|
|
21
|
-
export type NewSessionReason = 'initial' | 'reset' | 'idleTimeout' | 'maxLength' | 'crossTabAdoption';
|
|
22
|
-
/** Details emitted when the client starts or adopts a new session. */
|
|
23
|
-
export interface NewSessionInfo extends SessionContext {
|
|
24
|
-
/** The condition that caused this session to begin. */
|
|
25
|
-
readonly reason: NewSessionReason;
|
|
26
|
-
}
|
|
27
|
-
/** A captured event, as observed by `onEvent`. */
|
|
28
|
-
export interface CapturedEventInfo {
|
|
29
|
-
/** The finalized captured event name. */
|
|
30
|
-
readonly event: string;
|
|
31
|
-
/** The final event properties after client defaults and dynamic properties are applied. */
|
|
32
|
-
readonly properties: DeepReadonly<JsonRecord>;
|
|
33
|
-
}
|
|
34
|
-
/** Per-call capture overrides, mirroring the client's public capture options. */
|
|
35
|
-
export interface CaptureOptions {
|
|
36
|
-
/** Override the event timestamp sent to PostHog. */
|
|
37
|
-
timestamp?: Date;
|
|
38
|
-
/** Override the event UUID used for de-duplication. */
|
|
39
|
-
uuid?: string;
|
|
40
|
-
/** Person properties to set, emitted as `$set`. */
|
|
41
|
-
set?: Record<string, unknown>;
|
|
42
|
-
/** Person properties to set if unset, emitted as `$set_once`. */
|
|
43
|
-
setOnce?: Record<string, unknown>;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* The host SDK's core analytics behavior, exposed as an extension so shared
|
|
47
|
-
* extensions can depend on the event pipeline without depending on a concrete
|
|
48
|
-
* PostHog client implementation.
|
|
49
|
-
*/
|
|
50
|
-
export interface CoreExtension extends Extension {
|
|
51
|
-
/** The id events are currently attributed to. */
|
|
52
|
-
readonly distinctId: string;
|
|
53
|
-
/** The anonymous device id carried across identify calls. */
|
|
54
|
-
readonly anonymousId: string;
|
|
55
|
-
/** Active group memberships attached to events as `$groups`. */
|
|
56
|
-
readonly groups: DeepReadonly<Record<string, string>>;
|
|
57
|
-
/** The current session, created on first read if needed. */
|
|
58
|
-
readonly session: SessionContext;
|
|
59
|
-
/** Records an analytics event through the client's normal pipeline. */
|
|
60
|
-
capture(event: string, properties?: Properties | null, options?: CaptureOptions): Promise<void>;
|
|
61
|
-
/**
|
|
62
|
-
* Registers a producer of properties merged into every captured event.
|
|
63
|
-
* The producer runs inline while the event is built and must be synchronous.
|
|
64
|
-
*/
|
|
65
|
-
registerDynamicEventProperties(producer: () => Record<string, unknown>): Disposable;
|
|
66
|
-
/** Fires for every captured event through a deeply readonly view. */
|
|
67
|
-
readonly onEvent: Listener<CapturedEventInfo>;
|
|
68
|
-
/** Fires when a new session starts, including on reset. */
|
|
69
|
-
readonly onNewSession: Listener<NewSessionInfo>;
|
|
70
|
-
/**
|
|
71
|
-
* Resolves with the current remote config, awaiting the first outcome when
|
|
72
|
-
* necessary. A failed outcome resolves to `undefined`; later successful
|
|
73
|
-
* changes are published through `onRemoteConfig`.
|
|
74
|
-
*/
|
|
75
|
-
getRemoteConfig(): Promise<DeepReadonly<RemoteConfig> | undefined>;
|
|
76
|
-
/** Fires through a deeply readonly view when server-provided config arrives or changes successfully. */
|
|
77
|
-
readonly onRemoteConfig: Listener<DeepReadonly<RemoteConfig>>;
|
|
78
|
-
}
|
|
79
|
-
/** Capability token used to resolve the host SDK's core analytics extension. */
|
|
80
|
-
export declare const CoreExtension: ExtensionToken<CoreExtension>;
|
package/dist/core-extension.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __webpack_require__ = {};
|
|
3
|
-
(()=>{
|
|
4
|
-
__webpack_require__.d = (exports1, definition)=>{
|
|
5
|
-
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
6
|
-
enumerable: true,
|
|
7
|
-
get: definition[key]
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
|
-
})();
|
|
11
|
-
(()=>{
|
|
12
|
-
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
13
|
-
})();
|
|
14
|
-
(()=>{
|
|
15
|
-
__webpack_require__.r = (exports1)=>{
|
|
16
|
-
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
17
|
-
value: 'Module'
|
|
18
|
-
});
|
|
19
|
-
Object.defineProperty(exports1, '__esModule', {
|
|
20
|
-
value: true
|
|
21
|
-
});
|
|
22
|
-
};
|
|
23
|
-
})();
|
|
24
|
-
var __webpack_exports__ = {};
|
|
25
|
-
__webpack_require__.r(__webpack_exports__);
|
|
26
|
-
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
-
CoreExtension: ()=>CoreExtension
|
|
28
|
-
});
|
|
29
|
-
const CoreExtension = 'posthog.core';
|
|
30
|
-
exports.CoreExtension = __webpack_exports__.CoreExtension;
|
|
31
|
-
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
32
|
-
"CoreExtension"
|
|
33
|
-
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
34
|
-
Object.defineProperty(exports, '__esModule', {
|
|
35
|
-
value: true
|
|
36
|
-
});
|
package/dist/core-extension.mjs
DELETED
package/dist/token.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/** Phantom brand carrying the capability type without emitting runtime code. */
|
|
2
|
-
declare const extensionTokenType: unique symbol;
|
|
3
|
-
/**
|
|
4
|
-
* A typed, implementation-free string for resolving an extension that provides
|
|
5
|
-
* a capability. Declared as a shared `const` next to the providing extension's
|
|
6
|
-
* interface:
|
|
7
|
-
*
|
|
8
|
-
* ```ts
|
|
9
|
-
* export interface FeatureFlagsExtension extends Extension { … }
|
|
10
|
-
* export const FeatureFlags = 'posthog.featureFlags' as ExtensionToken<FeatureFlagsExtension>
|
|
11
|
-
* ```
|
|
12
|
-
*
|
|
13
|
-
* A token holds no implementation, so importing one never pulls the provider's
|
|
14
|
-
* code into the consumer's bundle. Its generic brand lets `getExtension` infer
|
|
15
|
-
* the provided type, while its runtime string value remains stable across
|
|
16
|
-
* independently compiled scripts. Token strings must be globally unique and
|
|
17
|
-
* stable for the lifetime of the capability contract.
|
|
18
|
-
*/
|
|
19
|
-
export type ExtensionToken<T> = string & {
|
|
20
|
-
readonly [extensionTokenType]: T;
|
|
21
|
-
};
|
|
22
|
-
export {};
|
package/dist/token.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
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
DELETED
|
File without changes
|