@posthog/browser-common 0.2.1 → 0.2.3
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 +48 -24
- package/dist/client.d.ts +33 -106
- package/dist/config.js +1 -1
- package/dist/config.mjs +1 -1
- package/dist/core-extension.d.ts +80 -0
- package/dist/core-extension.js +36 -0
- package/dist/core-extension.mjs +2 -0
- package/dist/disposable.d.ts +2 -0
- package/dist/disposable.js +31 -1
- package/dist/disposable.mjs +14 -0
- package/dist/extension-runtime.d.ts +34 -0
- package/dist/extension-runtime.js +109 -0
- package/dist/extension-runtime.mjs +75 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +60 -7
- package/dist/index.mjs +4 -1
- package/dist/persistence.d.ts +15 -12
- package/dist/pubsub.d.ts +1 -1
- package/dist/pubsub.js +6 -8
- package/dist/pubsub.mjs +6 -8
- package/dist/types/compression.d.ts +5 -0
- package/dist/types/compression.js +39 -0
- package/dist/types/compression.mjs +5 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.js +87 -0
- package/dist/types/index.mjs +4 -0
- package/dist/types/network-recording.d.ts +42 -0
- package/dist/types/network-recording.js +18 -0
- package/dist/types/network-recording.mjs +0 -0
- package/dist/types/remote-config.d.ts +180 -0
- package/dist/types/remote-config.js +18 -0
- package/dist/types/remote-config.mjs +0 -0
- package/dist/types/surveys.d.ts +243 -0
- package/dist/types/surveys.js +140 -0
- package/dist/types/surveys.mjs +76 -0
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -6,16 +6,17 @@ 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
|
-
runtime primitives such as
|
|
9
|
+
(`Extension`), the host services it is handed (`Client`), the core analytics
|
|
10
|
+
capability (`CoreExtension`), and small shared runtime primitives such as
|
|
11
|
+
`Publisher`.
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
This contract is designed so an extension can run unchanged across major
|
|
14
|
+
versions of the web SDK. Concrete host adapters remain owned by their SDK
|
|
15
|
+
packages; browser-v1 and browser-v2 composition and loading integration are
|
|
16
|
+
separate from this shared runtime.
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Each SDK provides a _client adapter_ that implements `Client` over its own
|
|
18
|
-
internals, so extension code never depends on a specific SDK.
|
|
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.
|
|
19
20
|
|
|
20
21
|
## Concepts
|
|
21
22
|
|
|
@@ -24,7 +25,7 @@ internals, so extension code never depends on a specific SDK.
|
|
|
24
25
|
What you implement. The host calls only `setup` and `dispose`:
|
|
25
26
|
|
|
26
27
|
```ts
|
|
27
|
-
import type
|
|
28
|
+
import { CoreExtension, type Disposable, type Extension } from '@posthog/browser-common'
|
|
28
29
|
|
|
29
30
|
export function webContext(): Extension {
|
|
30
31
|
let removeProperties: Disposable | undefined
|
|
@@ -32,7 +33,11 @@ export function webContext(): Extension {
|
|
|
32
33
|
return {
|
|
33
34
|
name: 'webContext',
|
|
34
35
|
setup(client) {
|
|
35
|
-
|
|
36
|
+
const core = client.getExtension(CoreExtension)
|
|
37
|
+
if (!core) {
|
|
38
|
+
throw new Error('CoreExtension is required')
|
|
39
|
+
}
|
|
40
|
+
removeProperties = core.registerDynamicEventProperties(() => ({
|
|
36
41
|
$current_url: window.location.href,
|
|
37
42
|
}))
|
|
38
43
|
},
|
|
@@ -48,23 +53,44 @@ may be async (final flush). Static config the app sets goes in your constructor,
|
|
|
48
53
|
not on the `Client`.
|
|
49
54
|
|
|
50
55
|
Anything in `setup` that returns a `Disposable` must be held by the extension
|
|
51
|
-
and disposed in `dispose()`.
|
|
56
|
+
and disposed in `dispose()`. Use `createDisposable(teardown)` when adapting a
|
|
57
|
+
callback into idempotent teardown.
|
|
52
58
|
|
|
53
59
|
### `Client`
|
|
54
60
|
|
|
55
|
-
What an extension is given in `setup` — the host's
|
|
61
|
+
What an extension is given in `setup` — the host's extension services:
|
|
56
62
|
|
|
57
|
-
- **
|
|
58
|
-
- **events**: `capture(...)`, `registerDynamicEventProperties(...)` (contribute properties), `onEvent(...)` (observe)
|
|
59
|
-
- **transport**: `apiRequest(path, init?)`
|
|
60
|
-
- **server config**: `getRemoteConfig()` (current), `onRemoteConfig(...)` (changes)
|
|
61
|
-
- **lifecycle**: `onNewSession(...)`
|
|
63
|
+
- **transport**: `projectToken`, `sendRequest(path, init?)`
|
|
62
64
|
- **registry**: `getExtension(token)`
|
|
63
65
|
- **storage & logging**: `kv`, `logger`
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
### `CoreExtension`
|
|
68
|
+
|
|
69
|
+
A conforming host must register one `CoreExtension` before setting up product
|
|
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`
|
|
74
|
+
- **events**: `capture(...)`, `registerDynamicEventProperties(...)`, `onEvent(...)`
|
|
75
|
+
- **lifecycle**: `onNewSession(...)`
|
|
76
|
+
- **server config**: `getRemoteConfig()` (current), `onRemoteConfig(...)` (changes)
|
|
77
|
+
|
|
78
|
+
Identity, session, and the public project token are always-ready synchronous
|
|
79
|
+
reads. Operations that perform I/O, including `capture`, `sendRequest`, `kv`,
|
|
80
|
+
and `getRemoteConfig`, are awaitable.
|
|
81
|
+
|
|
82
|
+
### Host runtime
|
|
83
|
+
|
|
84
|
+
PostHog browser SDK implementations share extension registration and teardown
|
|
85
|
+
through `ExtensionRuntime`, imported from the dedicated
|
|
86
|
+
`@posthog/browser-common/extension-runtime` subpath. It reserves names and
|
|
87
|
+
capability tokens during setup, publishes providers only after successful
|
|
88
|
+
readiness, and disposes extensions once in reverse registration order. Concrete
|
|
89
|
+
SDKs still own the `Client` adapter, Core implementation, and SDK lifecycle
|
|
90
|
+
hooks.
|
|
91
|
+
|
|
92
|
+
`ExtensionRuntime` is host infrastructure, not part of the extension-author
|
|
93
|
+
surface exported from the package root.
|
|
68
94
|
|
|
69
95
|
### `Publisher`
|
|
70
96
|
|
|
@@ -127,7 +153,5 @@ v1 → `Client` porting map.
|
|
|
127
153
|
## Status
|
|
128
154
|
|
|
129
155
|
Early and internal. The package currently defines the extension contract, the
|
|
130
|
-
|
|
131
|
-
`utils/*` subpaths.
|
|
132
|
-
registry implementation, and a test `Client` — will land alongside the first
|
|
133
|
-
ported extension.
|
|
156
|
+
core analytics capability, a shared host runtime, shared lifecycle helpers, and
|
|
157
|
+
directly imported browser utilities under `utils/*` subpaths.
|
package/dist/client.d.ts
CHANGED
|
@@ -1,135 +1,62 @@
|
|
|
1
1
|
import type { Logger } from '@posthog/core';
|
|
2
|
-
import type { Disposable } from './disposable';
|
|
3
2
|
import type { KeyValueStore } from './persistence';
|
|
4
|
-
import type { Listener } from './pubsub';
|
|
5
3
|
import type { ExtensionToken } from './token';
|
|
6
|
-
/**
|
|
7
|
-
export interface SessionContext {
|
|
8
|
-
/** The stable session identifier attached to events captured during this session. */
|
|
9
|
-
sessionId: string;
|
|
10
|
-
/** The logical browser tab/window identifier attached alongside the session id. */
|
|
11
|
-
windowId: string;
|
|
12
|
-
/** When the session started, as a Unix timestamp in milliseconds. */
|
|
13
|
-
sessionStartTimestamp: number;
|
|
14
|
-
}
|
|
15
|
-
/** Why a new session started (a `reset` also starts a new session). */
|
|
16
|
-
export type NewSessionReason = 'initial' | 'reset' | 'idleTimeout' | 'maxLength' | 'crossTabAdoption';
|
|
17
|
-
/** Details emitted when the client starts or adopts a new session. */
|
|
18
|
-
export interface NewSessionInfo extends SessionContext {
|
|
19
|
-
/** The condition that caused this session to begin. */
|
|
20
|
-
reason: NewSessionReason;
|
|
21
|
-
}
|
|
22
|
-
/** A captured event, as observed by `onEvent`. */
|
|
23
|
-
export interface CapturedEventInfo {
|
|
24
|
-
/** The event name supplied to {@link Client.capture}. */
|
|
25
|
-
event: string;
|
|
26
|
-
/** The final event properties after client defaults and dynamic properties are applied. */
|
|
27
|
-
properties: Record<string, unknown>;
|
|
28
|
-
}
|
|
29
|
-
/** Per-call capture overrides, mirroring the client's public capture options. */
|
|
30
|
-
export interface CaptureOptions {
|
|
31
|
-
/** Override the event timestamp sent to PostHog. */
|
|
32
|
-
timestamp?: Date;
|
|
33
|
-
/** Override the event UUID used for de-duplication. */
|
|
34
|
-
uuid?: string;
|
|
35
|
-
/** Person properties to set, emitted as `$set`. */
|
|
36
|
-
set?: Record<string, unknown>;
|
|
37
|
-
/** Person properties to set if unset, emitted as `$set_once`. */
|
|
38
|
-
setOnce?: Record<string, unknown>;
|
|
39
|
-
}
|
|
40
|
-
/** A minimal response from {@link Client.apiRequest}. */
|
|
4
|
+
/** A minimal response from {@link Client.sendRequest}. */
|
|
41
5
|
export interface ApiResponse {
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
/** The
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
|
|
6
|
+
/** The HTTP status code returned by the transport, or a client-defined best-effort status for sendBeacon sends. */
|
|
7
|
+
statusCode: number;
|
|
8
|
+
/** The response body parsed as JSON when available. */
|
|
9
|
+
json?: unknown;
|
|
10
|
+
/** The response body as text when available. */
|
|
11
|
+
text?: string;
|
|
12
|
+
/** The transport error when the request failed before receiving an HTTP response. */
|
|
13
|
+
error?: unknown;
|
|
50
14
|
}
|
|
51
|
-
/**
|
|
52
|
-
export
|
|
53
|
-
|
|
15
|
+
/** Configured host used to resolve a relative request path. */
|
|
16
|
+
export type RequestTarget = 'api' | 'flags' | 'assets';
|
|
17
|
+
/** Browser transport requested for a send. */
|
|
18
|
+
export type RequestTransport = 'XHR' | 'fetch' | 'sendBeacon';
|
|
19
|
+
/** Options for sending a request through {@link Client.sendRequest}. */
|
|
20
|
+
export interface SendRequestInit {
|
|
21
|
+
/** Configured host to send through; defaults to the regular API host. */
|
|
22
|
+
target?: RequestTarget;
|
|
23
|
+
/** HTTP method to use; the host transport's default applies when omitted. */
|
|
54
24
|
method?: 'GET' | 'POST';
|
|
55
25
|
/** JSON-serialized by the client. */
|
|
56
26
|
body?: unknown;
|
|
57
27
|
/** Query string parameters appended to the request URL. */
|
|
58
28
|
query?: Record<string, string>;
|
|
59
|
-
/**
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
* unusable (e.g. `sendBeacon` only reports "queued"), so callers must not
|
|
64
|
-
* depend on it.
|
|
65
|
-
*/
|
|
66
|
-
unload?: boolean;
|
|
29
|
+
/** Additional headers merged with the host SDK's configured request headers. */
|
|
30
|
+
headers?: Record<string, string>;
|
|
31
|
+
/** Browser transport to prefer. `sendBeacon` returns a best-effort response immediately. */
|
|
32
|
+
transport?: RequestTransport;
|
|
67
33
|
/** Abort the request if it does not complete within this many milliseconds. */
|
|
68
34
|
timeoutMs?: number;
|
|
69
35
|
}
|
|
70
|
-
/**
|
|
71
|
-
* Server-provided configuration, as returned by the remote config response
|
|
72
|
-
* (sampling rates, suppression rules, feature enablement, quotas, …). A loose
|
|
73
|
-
* record by design — each extension reads only the keys it owns.
|
|
74
|
-
*/
|
|
75
|
-
export type RemoteConfig = Record<string, unknown>;
|
|
76
36
|
/**
|
|
77
37
|
* The host SDK's capability surface as seen by an extension — the client an
|
|
78
|
-
* extension is handed in `setup`.
|
|
79
|
-
*
|
|
38
|
+
* extension is handed in `setup`. A conforming host provides it as an adapter
|
|
39
|
+
* over its own internals.
|
|
80
40
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
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.
|
|
84
44
|
*/
|
|
85
45
|
export interface Client {
|
|
86
|
-
/**
|
|
87
|
-
readonly
|
|
88
|
-
/** The anonymous device id; used before `identify` and carried on identify events as `$anon_distinct_id`. */
|
|
89
|
-
readonly anonymousId: string;
|
|
90
|
-
/** Active group memberships (group type → group key), attached to events as `$groups`. */
|
|
91
|
-
readonly groups: Record<string, string>;
|
|
92
|
-
/** The current session, created on first read if needed; reading does not extend or rotate it. */
|
|
93
|
-
readonly session: SessionContext;
|
|
94
|
-
/** Records an analytics event through the client's normal pipeline. */
|
|
95
|
-
capture(event: string, properties?: Record<string, unknown> | null, options?: CaptureOptions): Promise<void>;
|
|
96
|
-
/**
|
|
97
|
-
* Registers a producer of properties merged into every captured event.
|
|
98
|
-
* Returns a {@link Disposable} that removes it; an extension disposes it in
|
|
99
|
-
* its own `dispose`. May be called more than once. The producer runs inline
|
|
100
|
-
* during event build, so it must be cheap and synchronous; it may return
|
|
101
|
-
* different properties each time (e.g. the current URL), and is recomputed
|
|
102
|
-
* per event rather than stored.
|
|
103
|
-
*/
|
|
104
|
-
registerDynamicEventProperties(producer: () => Record<string, unknown>): Disposable;
|
|
105
|
-
/**
|
|
106
|
-
* Sends a request to a PostHog endpoint; the client owns auth, headers, and
|
|
107
|
-
* transport (fetch / XHR / keepalive). `path` is relative to the configured
|
|
108
|
-
* API host, e.g. `/s/`, `/flags/`, `/api/surveys/`.
|
|
109
|
-
*/
|
|
110
|
-
apiRequest(path: string, init?: ApiRequestInit): Promise<ApiResponse>;
|
|
46
|
+
/** Public project token used to authenticate endpoint-specific requests. */
|
|
47
|
+
readonly projectToken: string;
|
|
111
48
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* Re-readable: each call resolves with the current config, awaiting the
|
|
115
|
-
* first fetch if none has landed. `await` it in `setup` to block until
|
|
116
|
-
* config is known, or `.then()` it to reconfigure once it arrives; later
|
|
117
|
-
* changes arrive via `onRemoteConfig`.
|
|
49
|
+
* Sends a request through the host SDK's transport. The extension owns the
|
|
50
|
+
* endpoint-specific path, method, authentication shape, body, and headers.
|
|
118
51
|
*/
|
|
119
|
-
|
|
120
|
-
/** Fires when server-provided config arrives or changes. */
|
|
121
|
-
readonly onRemoteConfig: Listener<RemoteConfig>;
|
|
122
|
-
/** Fires for every captured event — hot path, keep handlers cheap and synchronous. */
|
|
123
|
-
readonly onEvent: Listener<CapturedEventInfo>;
|
|
124
|
-
/** Fires when a new session starts, including on reset (discriminate via `reason`). */
|
|
125
|
-
readonly onNewSession: Listener<NewSessionInfo>;
|
|
52
|
+
sendRequest(path: string, init?: SendRequestInit): Promise<ApiResponse>;
|
|
126
53
|
/**
|
|
127
54
|
* Resolves another registered extension by a capability token it provides, or
|
|
128
55
|
* `undefined` if nothing registered provides it (not installed, or not loaded
|
|
129
56
|
* yet). Lets one extension use another without importing its implementation.
|
|
130
57
|
*/
|
|
131
58
|
getExtension<T>(token: ExtensionToken<T>): T | undefined;
|
|
132
|
-
/**
|
|
59
|
+
/** Awaitable key-value storage backed by the host client's persistence. */
|
|
133
60
|
readonly kv: KeyValueStore;
|
|
134
61
|
/** Logger that follows the host client's debug/noise policy. */
|
|
135
62
|
readonly logger: Logger;
|
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.3";
|
|
30
30
|
const Config = {
|
|
31
31
|
DEBUG: false,
|
|
32
32
|
LIB_VERSION: packageVersion,
|
package/dist/config.mjs
CHANGED
|
@@ -0,0 +1,80 @@
|
|
|
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>;
|
|
@@ -0,0 +1,36 @@
|
|
|
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/disposable.d.ts
CHANGED
package/dist/disposable.js
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
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
|
+
})();
|
|
3
14
|
(()=>{
|
|
4
15
|
__webpack_require__.r = (exports1)=>{
|
|
5
16
|
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
@@ -12,7 +23,26 @@ var __webpack_require__ = {};
|
|
|
12
23
|
})();
|
|
13
24
|
var __webpack_exports__ = {};
|
|
14
25
|
__webpack_require__.r(__webpack_exports__);
|
|
15
|
-
|
|
26
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
createDisposable: ()=>createDisposable
|
|
28
|
+
});
|
|
29
|
+
function createDisposable(dispose) {
|
|
30
|
+
let active = true;
|
|
31
|
+
let result;
|
|
32
|
+
return {
|
|
33
|
+
dispose: ()=>{
|
|
34
|
+
if (active) {
|
|
35
|
+
active = false;
|
|
36
|
+
result = dispose();
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
exports.createDisposable = __webpack_exports__.createDisposable;
|
|
43
|
+
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
44
|
+
"createDisposable"
|
|
45
|
+
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
16
46
|
Object.defineProperty(exports, '__esModule', {
|
|
17
47
|
value: true
|
|
18
48
|
});
|
package/dist/disposable.mjs
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type Logger } from '@posthog/core';
|
|
2
|
+
import type { Client } from './client';
|
|
3
|
+
import type { Disposable } from './disposable';
|
|
4
|
+
import type { Extension } from './extension';
|
|
5
|
+
import type { ExtensionToken } from './token';
|
|
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
|
+
*/
|
|
12
|
+
export declare class ExtensionRuntime implements Disposable {
|
|
13
|
+
private readonly _logger;
|
|
14
|
+
private readonly _extensions;
|
|
15
|
+
private readonly _registrationOrder;
|
|
16
|
+
private readonly _providerReservations;
|
|
17
|
+
private readonly _providers;
|
|
18
|
+
private _disposePromise;
|
|
19
|
+
constructor(_logger: Logger);
|
|
20
|
+
/**
|
|
21
|
+
* Sets up an extension and publishes its capabilities once setup succeeds.
|
|
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;
|
|
34
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
ExtensionRuntime: ()=>ExtensionRuntime
|
|
28
|
+
});
|
|
29
|
+
const core_namespaceObject = require("@posthog/core");
|
|
30
|
+
class ExtensionRuntime {
|
|
31
|
+
constructor(_logger){
|
|
32
|
+
this._logger = _logger;
|
|
33
|
+
this._extensions = new Map();
|
|
34
|
+
this._registrationOrder = [];
|
|
35
|
+
this._providerReservations = new Map();
|
|
36
|
+
this._providers = new Map();
|
|
37
|
+
}
|
|
38
|
+
async add(extension, client) {
|
|
39
|
+
if (this._disposePromise) throw new Error('Cannot add an extension to a disposed ExtensionRuntime');
|
|
40
|
+
if (this._extensions.has(extension.name)) throw new Error(`Browser extension "${extension.name}" is already registered`);
|
|
41
|
+
for (const token of extension.provides ?? [])if (this._providerReservations.has(token)) throw new Error(`Browser extension token "${token}" is already registered`);
|
|
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;
|
|
50
|
+
try {
|
|
51
|
+
setupResult = extension.setup(client);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
registered.setupPromise = this._handleSetupFailure(registered, error);
|
|
54
|
+
return registered.setupPromise;
|
|
55
|
+
}
|
|
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
|
+
}
|
|
63
|
+
dispose() {
|
|
64
|
+
if (!this._disposePromise) this._disposePromise = this._disposeAll();
|
|
65
|
+
return this._disposePromise;
|
|
66
|
+
}
|
|
67
|
+
async _disposeAll() {
|
|
68
|
+
for (const registered of this._registrationOrder.slice().reverse()){
|
|
69
|
+
await registered.setupPromise;
|
|
70
|
+
await this._disposeRegistration(registered);
|
|
71
|
+
}
|
|
72
|
+
this._extensions.clear();
|
|
73
|
+
this._registrationOrder.length = 0;
|
|
74
|
+
this._providerReservations.clear();
|
|
75
|
+
this._providers.clear();
|
|
76
|
+
}
|
|
77
|
+
async _handleSetupFailure(registered, error) {
|
|
78
|
+
this._removeRegistration(registered);
|
|
79
|
+
this._logger.error(`Failed to set up browser extension "${registered.extension.name}"`, error);
|
|
80
|
+
if (this._disposePromise) return;
|
|
81
|
+
await this._disposeRegistration(registered);
|
|
82
|
+
const index = this._registrationOrder.indexOf(registered);
|
|
83
|
+
if (-1 !== index) this._registrationOrder.splice(index, 1);
|
|
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);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
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
|
+
}
|
|
103
|
+
exports.ExtensionRuntime = __webpack_exports__.ExtensionRuntime;
|
|
104
|
+
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
105
|
+
"ExtensionRuntime"
|
|
106
|
+
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
107
|
+
Object.defineProperty(exports, '__esModule', {
|
|
108
|
+
value: true
|
|
109
|
+
});
|