@signap/signals-angular 2.2.0-dev.20260930000246.gd4b7f02 → 2.2.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 +15 -1
- package/dist/index.cjs +121 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -12
- package/dist/index.d.ts +62 -12
- package/dist/index.js +131 -18
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -71,7 +71,8 @@ round-trip — no separate ack/lookup transition.
|
|
|
71
71
|
| field | type | when populated |
|
|
72
72
|
|-------------|------------------------------------------|----------------------------------------|
|
|
73
73
|
| `data` | `Signal<IdentifyResult \| undefined>` | once `agent.identify()` resolves |
|
|
74
|
-
| `isLoading` | `Signal<boolean>` | true between call time and `data
|
|
74
|
+
| `isLoading` | `Signal<boolean>` | true between call time and `data`; false while held |
|
|
75
|
+
| `isHeld` | `Signal<boolean>` | true while `enabled()` is `false` |
|
|
75
76
|
| `error` | `Signal<Error \| null>` | last failure (load / identify) |
|
|
76
77
|
| `refetch` | `() => Promise<void>` | re-run `agent.identify()` |
|
|
77
78
|
|
|
@@ -82,6 +83,19 @@ SSR-safe: `provideSignals()` gates `SignalsAgent.load()` behind
|
|
|
82
83
|
`isPlatformBrowser(PLATFORM_ID)`, so it never runs during Angular Universal /
|
|
83
84
|
prerender server rendering.
|
|
84
85
|
|
|
86
|
+
## Holding collection
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const enabled = signal(false);
|
|
90
|
+
provideSignals({ apiKey: "pk_live_...", endpoint: "https://ingest.example" }, { enabled });
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
While `enabled()` is `false` the client does not load the agent: the SDK registers no event listener, runs no collector and sends no request, and `injectVisitorData()` returns `isHeld() === true`. Setting it to `true` loads the agent and runs the first identify, exactly as if `enabled` had never been set. Setting it back to `false` stops the SDK's interaction listeners for the whole page and discards any identify result still in flight; a request that has already been sent is not recalled. Without the second argument, nothing is held. `SignalsClient` also exposes the same two transitions as `release()` and `hold()`.
|
|
94
|
+
|
|
95
|
+
Holding or releasing does not delete anything Signap has already received.
|
|
96
|
+
|
|
97
|
+
The hold applies to the agent this client loads. A copy of the SDK loaded another way, for example the CDN script, is not held.
|
|
98
|
+
|
|
85
99
|
## Endpoint (required)
|
|
86
100
|
|
|
87
101
|
`provideSignals()` takes the underlying `AgentLoadOptions`. `endpoint` is
|
package/dist/index.cjs
CHANGED
|
@@ -36,34 +36,109 @@ var import_signals_web = require("@signap/signals-web");
|
|
|
36
36
|
var SignalsClient = class {
|
|
37
37
|
agentState = (0, import_core.signal)(null);
|
|
38
38
|
loadErrorState = (0, import_core.signal)(null);
|
|
39
|
+
heldState;
|
|
39
40
|
/** Resolved agent, or `null` until `SignalsAgent.load()` settles. */
|
|
40
41
|
agent = this.agentState.asReadonly();
|
|
41
42
|
/** Non-null if `SignalsAgent.load()` rejected. */
|
|
42
43
|
loadError = this.loadErrorState.asReadonly();
|
|
44
|
+
/** True while held (`provideSignals(opts, { enabled })` with `enabled()` false): nothing is loaded. */
|
|
45
|
+
held;
|
|
43
46
|
/**
|
|
44
|
-
* Resolves once `load()` has settled (agent set OR loadError set)
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
+
* Resolves once the FIRST `load()` has settled (agent set OR loadError set), or
|
|
48
|
+
* at once outside a browser. While the client is held before its first release,
|
|
49
|
+
* it stays pending.
|
|
47
50
|
*/
|
|
48
51
|
ready;
|
|
52
|
+
opts;
|
|
53
|
+
browser;
|
|
54
|
+
resolveReady = () => {
|
|
55
|
+
};
|
|
56
|
+
started = false;
|
|
57
|
+
loaded = null;
|
|
58
|
+
// Bumped on every withdrawal; a load() that settles under an older generation is discarded.
|
|
59
|
+
generation = 0;
|
|
60
|
+
listeners = /* @__PURE__ */ new Set();
|
|
49
61
|
/**
|
|
50
|
-
* @param opts
|
|
51
|
-
* @param browser
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
62
|
+
* @param opts forwarded verbatim to `SignalsAgent.load()`.
|
|
63
|
+
* @param browser whether we're running in a browser; `load()` only fires
|
|
64
|
+
* client-side so SSR / prerender never touches
|
|
65
|
+
* window/document/WebCrypto (the Angular analog of the Vue
|
|
66
|
+
* plugin's `typeof window` guard).
|
|
67
|
+
* @param initiallyHeld start held (issue 2894): do not load until `release()`.
|
|
55
68
|
*/
|
|
56
|
-
constructor(opts, browser) {
|
|
69
|
+
constructor(opts, browser, initiallyHeld = false) {
|
|
70
|
+
this.opts = opts;
|
|
71
|
+
this.browser = browser;
|
|
72
|
+
this.heldState = (0, import_core.signal)(initiallyHeld);
|
|
73
|
+
this.held = this.heldState.asReadonly();
|
|
74
|
+
this.ready = new Promise((resolve) => {
|
|
75
|
+
this.resolveReady = resolve;
|
|
76
|
+
});
|
|
57
77
|
if (!browser) {
|
|
58
|
-
this.
|
|
78
|
+
this.resolveReady();
|
|
59
79
|
return;
|
|
60
80
|
}
|
|
61
|
-
|
|
81
|
+
if (!initiallyHeld) this.release();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Leave the held state: load the agent once (a no-op if already released). What
|
|
85
|
+
* `provideSignals()` calls when its `enabled` signal becomes true.
|
|
86
|
+
*/
|
|
87
|
+
release() {
|
|
88
|
+
this.heldState.set(false);
|
|
89
|
+
if (!this.browser || this.started) return;
|
|
90
|
+
this.started = true;
|
|
91
|
+
const mine = this.generation;
|
|
92
|
+
import_signals_web.SignalsAgent.load(this.opts).then((loaded) => {
|
|
93
|
+
if (mine !== this.generation) {
|
|
94
|
+
if (!this.started) loaded.stopObservers();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this.loaded = loaded;
|
|
62
98
|
this.agentState.set(loaded);
|
|
99
|
+
this.settled();
|
|
63
100
|
}).catch((err) => {
|
|
101
|
+
if (mine !== this.generation) return;
|
|
64
102
|
this.loadErrorState.set(err instanceof Error ? err : new Error(String(err)));
|
|
103
|
+
this.settled();
|
|
65
104
|
});
|
|
66
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Enter the held state. After a release this stops the SDK's interaction
|
|
108
|
+
* listeners (page-wide, for this copy of the SDK), drops the agent and discards
|
|
109
|
+
* a load still in flight; a request already sent is not recalled, and nothing
|
|
110
|
+
* already received is deleted. Before any release it only marks the client held.
|
|
111
|
+
*/
|
|
112
|
+
hold() {
|
|
113
|
+
this.heldState.set(true);
|
|
114
|
+
if (!this.started) {
|
|
115
|
+
this.notify();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
this.started = false;
|
|
119
|
+
this.generation++;
|
|
120
|
+
this.loaded?.stopObservers();
|
|
121
|
+
this.loaded = null;
|
|
122
|
+
this.agentState.set(null);
|
|
123
|
+
this.loadErrorState.set(null);
|
|
124
|
+
this.notify();
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* @internal Called after every state change (a load settling, a hold). Lets
|
|
128
|
+
* `injectVisitorData()` re-run without an Angular `effect`, so the auto-identify
|
|
129
|
+
* fires exactly once per release regardless of change-detection timing.
|
|
130
|
+
*/
|
|
131
|
+
onChange(listener) {
|
|
132
|
+
this.listeners.add(listener);
|
|
133
|
+
return () => this.listeners.delete(listener);
|
|
134
|
+
}
|
|
135
|
+
settled() {
|
|
136
|
+
this.resolveReady();
|
|
137
|
+
this.notify();
|
|
138
|
+
}
|
|
139
|
+
notify() {
|
|
140
|
+
for (const l of [...this.listeners]) l();
|
|
141
|
+
}
|
|
67
142
|
};
|
|
68
143
|
|
|
69
144
|
// src/provide-signals.ts
|
|
@@ -75,11 +150,21 @@ var import_core2 = require("@angular/core");
|
|
|
75
150
|
var SIGNALS_CLIENT = new import_core2.InjectionToken("signap.signals.client");
|
|
76
151
|
|
|
77
152
|
// src/provide-signals.ts
|
|
78
|
-
function provideSignals(opts) {
|
|
153
|
+
function provideSignals(opts, config = {}) {
|
|
79
154
|
return (0, import_core3.makeEnvironmentProviders)([
|
|
80
155
|
{
|
|
81
156
|
provide: SIGNALS_CLIENT,
|
|
82
|
-
useFactory: () =>
|
|
157
|
+
useFactory: () => {
|
|
158
|
+
const browser = (0, import_common.isPlatformBrowser)((0, import_core3.inject)(import_core3.PLATFORM_ID));
|
|
159
|
+
const { enabled } = config;
|
|
160
|
+
if (!enabled) return new SignalsClient(opts, browser);
|
|
161
|
+
const client = new SignalsClient(opts, browser, !(0, import_core3.untracked)(enabled));
|
|
162
|
+
(0, import_core3.effect)(() => {
|
|
163
|
+
const on = enabled();
|
|
164
|
+
(0, import_core3.untracked)(() => on ? client.release() : client.hold());
|
|
165
|
+
});
|
|
166
|
+
return client;
|
|
167
|
+
}
|
|
83
168
|
}
|
|
84
169
|
]);
|
|
85
170
|
}
|
|
@@ -96,10 +181,30 @@ function injectVisitorData(opts) {
|
|
|
96
181
|
}
|
|
97
182
|
const data = (0, import_core4.signal)(void 0);
|
|
98
183
|
const error = (0, import_core4.signal)(null);
|
|
99
|
-
const isLoading = (0, import_core4.signal)(
|
|
184
|
+
const isLoading = (0, import_core4.signal)(
|
|
185
|
+
!client.held() && client.agent() === null && client.loadError() === null
|
|
186
|
+
);
|
|
187
|
+
const isHeld = (0, import_core4.computed)(() => client.held());
|
|
100
188
|
let token = { cancelled: false };
|
|
189
|
+
let started = false;
|
|
190
|
+
const onChange = () => {
|
|
191
|
+
if (client.held()) {
|
|
192
|
+
token.cancelled = true;
|
|
193
|
+
started = false;
|
|
194
|
+
data.set(void 0);
|
|
195
|
+
error.set(null);
|
|
196
|
+
isLoading.set(false);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (started) return;
|
|
200
|
+
if (client.agent() === null && client.loadError() === null) return;
|
|
201
|
+
started = true;
|
|
202
|
+
void run();
|
|
203
|
+
};
|
|
204
|
+
const unsubscribe = client.onChange(onChange);
|
|
101
205
|
(0, import_core4.inject)(import_core4.DestroyRef).onDestroy(() => {
|
|
102
206
|
token.cancelled = true;
|
|
207
|
+
unsubscribe();
|
|
103
208
|
});
|
|
104
209
|
const run = async () => {
|
|
105
210
|
const agent = client.agent();
|
|
@@ -128,8 +233,8 @@ function injectVisitorData(opts) {
|
|
|
128
233
|
if (!my.cancelled) isLoading.set(false);
|
|
129
234
|
}
|
|
130
235
|
};
|
|
131
|
-
void
|
|
132
|
-
return { data, isLoading, error, refetch: run };
|
|
236
|
+
void Promise.resolve().then(onChange);
|
|
237
|
+
return { data, isLoading, isHeld, error, refetch: run };
|
|
133
238
|
}
|
|
134
239
|
// Annotate the CommonJS export names for ESM import in node:
|
|
135
240
|
0 && (module.exports = {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/provide-signals.ts","../src/tokens.ts","../src/inject-visitor-data.ts"],"sourcesContent":["export {\n type AgentLoadOptions,\n type IdentifyOptions,\n type IdentifyResult,\n SdkError,\n type SdkErrorCode,\n VERSION as WEB_SDK_VERSION,\n} from \"@signap/signals-web\";\n\nexport { SignalsClient } from \"./client\";\nexport { provideSignals } from \"./provide-signals\";\nexport { SIGNALS_CLIENT } from \"./tokens\";\nexport {\n injectVisitorData,\n type InjectVisitorDataOptions,\n type InjectVisitorDataResult,\n} from \"./inject-visitor-data\";\n","import { type Signal, signal } from \"@angular/core\";\nimport { type AgentLoadOptions, SignalsAgent } from \"@signap/signals-web\";\n\n/**\n * Holds a single lazily-loaded SignalsAgent and exposes its readiness as Angular\n * signals — the framework-native analog of the Vue wrapper's provide/inject refs\n * and the React wrapper's context. `agent()` is `null` until `SignalsAgent.load()`\n * settles; `loadError()` is non-null if it rejected (e.g. an invalid apiKey).\n *\n * Deliberately NOT `@Injectable`: a decorator-free class keeps this package\n * AOT-safe under a plain tsup build (no ngtsc / ng-packagr partial-Ivy metadata\n * step). It is wired into DI by the factory in `provideSignals()`.\n */\nexport class SignalsClient {\n private readonly agentState = signal<SignalsAgent | null>(null);\n private readonly loadErrorState = signal<Error | null>(null);\n\n /** Resolved agent, or `null` until `SignalsAgent.load()` settles. */\n readonly agent: Signal<SignalsAgent | null> = this.agentState.asReadonly();\n /** Non-null if `SignalsAgent.load()` rejected. */\n readonly loadError: Signal<Error | null> = this.loadErrorState.asReadonly();\n /**\n * Resolves once `load()` has settled (agent set OR loadError set). Lets\n * `injectVisitorData()` await readiness without an Angular `effect`, so the\n * auto-identify fires exactly once regardless of change-detection timing.\n */\n readonly ready: Promise<void>;\n\n /**\n * @param opts forwarded verbatim to `SignalsAgent.load()`.\n * @param browser whether we're running in a browser; `load()` only fires\n * client-side so SSR / prerender never touches\n * window/document/WebCrypto (the Angular analog of the Vue\n * plugin's `typeof window` guard).\n */\n constructor(opts: AgentLoadOptions, browser: boolean) {\n if (!browser) {\n this.ready = Promise.resolve();\n return;\n }\n this.ready = SignalsAgent.load(opts)\n .then((loaded) => {\n this.agentState.set(loaded);\n })\n .catch((err: unknown) => {\n this.loadErrorState.set(err instanceof Error ? err : new Error(String(err)));\n });\n }\n}\n","import { isPlatformBrowser } from \"@angular/common\";\nimport {\n type EnvironmentProviders,\n PLATFORM_ID,\n inject,\n makeEnvironmentProviders,\n} from \"@angular/core\";\nimport type { AgentLoadOptions } from \"@signap/signals-web\";\nimport { SignalsClient } from \"./client\";\nimport { SIGNALS_CLIENT } from \"./tokens\";\n\n/**\n * Registers a single app-wide SignalsAgent — the idiomatic standalone-Angular\n * entry point, analogous to React's `<SignalsProvider>` and Vue's\n * `app.use(SignalsPlugin)`:\n *\n * ```ts\n * bootstrapApplication(AppComponent, {\n * providers: [\n * provideSignals({ apiKey: \"pk_live_...\", endpoint: \"https://ingest.example\" }),\n * ],\n * });\n * ```\n *\n * SSR-safe: the factory injects `PLATFORM_ID` and only lets the client call\n * `SignalsAgent.load()` in a browser, so a server render / prerender never loads\n * the agent.\n *\n * `opts` is snapshotted here at bootstrap; to swap apiKey/endpoint, re-bootstrap\n * (same snapshot-at-init contract as the React/Vue wrappers).\n */\nexport function provideSignals(opts: AgentLoadOptions): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: SIGNALS_CLIENT,\n useFactory: () => new SignalsClient(opts, isPlatformBrowser(inject(PLATFORM_ID))),\n },\n ]);\n}\n","import { InjectionToken } from \"@angular/core\";\nimport type { SignalsClient } from \"./client\";\n\n/**\n * DI token the app-wide {@link SignalsClient} is registered under (by\n * `provideSignals()`) and that `injectVisitorData()` resolves. Most consumers\n * don't inject this directly — call `injectVisitorData()`. It is exported for\n * advanced use (e.g. `inject(SIGNALS_CLIENT).agent()` to read the raw agent).\n */\nexport const SIGNALS_CLIENT = new InjectionToken<SignalsClient>(\"signap.signals.client\");\n","import { DestroyRef, type Signal, assertInInjectionContext, inject, signal } from \"@angular/core\";\nimport type { IdentifyOptions, IdentifyResult } from \"@signap/signals-web\";\nimport { SIGNALS_CLIENT } from \"./tokens\";\n\nexport type InjectVisitorDataOptions = IdentifyOptions;\n\nexport interface InjectVisitorDataResult {\n /**\n * Resolved visitor from `agent.identify()`. Under ADR §A2 the call is a single\n * synchronous round-trip — `visitorId`/`confidence`/`identifiedAt` arrive\n * together, no polling. A `Signal`, so read it as `data()` (in a template or a\n * `computed`/`effect`).\n */\n data: Signal<IdentifyResult | undefined>;\n /** True between call time and the identify() resolution (or `error`). */\n isLoading: Signal<boolean>;\n error: Signal<Error | null>;\n /** Re-run `agent.identify()` (e.g. from a button click). */\n refetch: () => Promise<void>;\n}\n\n/**\n * Auto-identifies once the provider's agent is ready, then exposes the resolved\n * visitor as Angular signals — the framework-native analog of React's\n * `useVisitorData()` hook and Vue's `useVisitorData()` composable.\n *\n * Must be called in an injection context (a component/directive constructor or a\n * field initializer, or inside `runInInjectionContext`) below `provideSignals()`.\n */\nexport function injectVisitorData(opts?: InjectVisitorDataOptions): InjectVisitorDataResult {\n assertInInjectionContext(injectVisitorData);\n\n const client = inject(SIGNALS_CLIENT, { optional: true });\n if (!client) {\n throw new Error(\n \"[@signap/signals-angular] injectVisitorData() requires provideSignals() in the application (or component) providers\",\n );\n }\n\n const data = signal<IdentifyResult | undefined>(undefined);\n const error = signal<Error | null>(null);\n // Loading until the agent (or a load error) settles.\n const isLoading = signal(client.agent() === null && client.loadError() === null);\n\n // Supersede token so a slow response from a call that refetch() replaced (or a\n // response arriving after teardown) is discarded.\n let token = { cancelled: false };\n inject(DestroyRef).onDestroy(() => {\n token.cancelled = true;\n });\n\n const run = async (): Promise<void> => {\n const agent = client.agent();\n if (!agent) {\n const loadError = client.loadError();\n if (loadError) {\n error.set(loadError);\n isLoading.set(false);\n }\n return;\n }\n token.cancelled = true;\n const my = { cancelled: false };\n token = my;\n isLoading.set(true);\n error.set(null);\n data.set(undefined);\n try {\n const res = await agent.identify(opts);\n if (my.cancelled) return;\n data.set(res);\n } catch (err) {\n if (my.cancelled) return;\n error.set(err instanceof Error ? err : new Error(String(err)));\n } finally {\n if (!my.cancelled) isLoading.set(false);\n }\n };\n\n // Auto-run exactly once, after load() settles. `ready` resolves whether load()\n // succeeded or failed; even if it already resolved, `.then` defers to a\n // microtask so the returned signals are wired up before the first run().\n void client.ready.then(() => run());\n\n return { data, isLoading, error, refetch: run };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAAA,sBAOO;;;ACPP,kBAAoC;AACpC,yBAAoD;AAY7C,IAAM,gBAAN,MAAoB;AAAA,EACR,iBAAa,oBAA4B,IAAI;AAAA,EAC7C,qBAAiB,oBAAqB,IAAI;AAAA;AAAA,EAGlD,QAAqC,KAAK,WAAW,WAAW;AAAA;AAAA,EAEhE,YAAkC,KAAK,eAAe,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMjE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAST,YAAY,MAAwB,SAAkB;AACpD,QAAI,CAAC,SAAS;AACZ,WAAK,QAAQ,QAAQ,QAAQ;AAC7B;AAAA,IACF;AACA,SAAK,QAAQ,gCAAa,KAAK,IAAI,EAChC,KAAK,CAAC,WAAW;AAChB,WAAK,WAAW,IAAI,MAAM;AAAA,IAC5B,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,WAAK,eAAe,IAAI,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAAA,IAC7E,CAAC;AAAA,EACL;AACF;;;AChDA,oBAAkC;AAClC,IAAAC,eAKO;;;ACNP,IAAAC,eAA+B;AASxB,IAAM,iBAAiB,IAAI,4BAA8B,uBAAuB;;;ADsBhF,SAAS,eAAe,MAA8C;AAC3E,aAAO,uCAAyB;AAAA,IAC9B;AAAA,MACE,SAAS;AAAA,MACT,YAAY,MAAM,IAAI,cAAc,UAAM,qCAAkB,qBAAO,wBAAW,CAAC,CAAC;AAAA,IAClF;AAAA,EACF,CAAC;AACH;;;AEtCA,IAAAC,eAAkF;AA6B3E,SAAS,kBAAkB,MAA0D;AAC1F,6CAAyB,iBAAiB;AAE1C,QAAM,aAAS,qBAAO,gBAAgB,EAAE,UAAU,KAAK,CAAC;AACxD,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,WAAO,qBAAmC,MAAS;AACzD,QAAM,YAAQ,qBAAqB,IAAI;AAEvC,QAAM,gBAAY,qBAAO,OAAO,MAAM,MAAM,QAAQ,OAAO,UAAU,MAAM,IAAI;AAI/E,MAAI,QAAQ,EAAE,WAAW,MAAM;AAC/B,2BAAO,uBAAU,EAAE,UAAU,MAAM;AACjC,UAAM,YAAY;AAAA,EACpB,CAAC;AAED,QAAM,MAAM,YAA2B;AACrC,UAAM,QAAQ,OAAO,MAAM;AAC3B,QAAI,CAAC,OAAO;AACV,YAAM,YAAY,OAAO,UAAU;AACnC,UAAI,WAAW;AACb,cAAM,IAAI,SAAS;AACnB,kBAAU,IAAI,KAAK;AAAA,MACrB;AACA;AAAA,IACF;AACA,UAAM,YAAY;AAClB,UAAM,KAAK,EAAE,WAAW,MAAM;AAC9B,YAAQ;AACR,cAAU,IAAI,IAAI;AAClB,UAAM,IAAI,IAAI;AACd,SAAK,IAAI,MAAS;AAClB,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,SAAS,IAAI;AACrC,UAAI,GAAG,UAAW;AAClB,WAAK,IAAI,GAAG;AAAA,IACd,SAAS,KAAK;AACZ,UAAI,GAAG,UAAW;AAClB,YAAM,IAAI,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAAA,IAC/D,UAAE;AACA,UAAI,CAAC,GAAG,UAAW,WAAU,IAAI,KAAK;AAAA,IACxC;AAAA,EACF;AAKA,OAAK,OAAO,MAAM,KAAK,MAAM,IAAI,CAAC;AAElC,SAAO,EAAE,MAAM,WAAW,OAAO,SAAS,IAAI;AAChD;","names":["import_signals_web","import_core","import_core","import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/provide-signals.ts","../src/tokens.ts","../src/inject-visitor-data.ts"],"sourcesContent":["export {\n type AgentLoadOptions,\n type IdentifyOptions,\n type IdentifyResult,\n SdkError,\n type SdkErrorCode,\n VERSION as WEB_SDK_VERSION,\n} from \"@signap/signals-web\";\n\nexport { SignalsClient } from \"./client\";\nexport { provideSignals, type SignalsConfig } from \"./provide-signals\";\nexport { SIGNALS_CLIENT } from \"./tokens\";\nexport {\n injectVisitorData,\n type InjectVisitorDataOptions,\n type InjectVisitorDataResult,\n} from \"./inject-visitor-data\";\n","import { type Signal, type WritableSignal, signal } from \"@angular/core\";\nimport { type AgentLoadOptions, SignalsAgent } from \"@signap/signals-web\";\n\n/**\n * Holds a single lazily-loaded SignalsAgent and exposes its readiness as Angular\n * signals — the framework-native analog of the Vue wrapper's provide/inject refs\n * and the React wrapper's context. `agent()` is `null` until `SignalsAgent.load()`\n * settles; `loadError()` is non-null if it rejected (e.g. an invalid apiKey).\n *\n * Deliberately NOT `@Injectable`: a decorator-free class keeps this package\n * AOT-safe under a plain tsup build (no ngtsc / ng-packagr partial-Ivy metadata\n * step). It is wired into DI by the factory in `provideSignals()`.\n */\nexport class SignalsClient {\n private readonly agentState = signal<SignalsAgent | null>(null);\n private readonly loadErrorState = signal<Error | null>(null);\n private readonly heldState: WritableSignal<boolean>;\n\n /** Resolved agent, or `null` until `SignalsAgent.load()` settles. */\n readonly agent: Signal<SignalsAgent | null> = this.agentState.asReadonly();\n /** Non-null if `SignalsAgent.load()` rejected. */\n readonly loadError: Signal<Error | null> = this.loadErrorState.asReadonly();\n /** True while held (`provideSignals(opts, { enabled })` with `enabled()` false): nothing is loaded. */\n readonly held: Signal<boolean>;\n /**\n * Resolves once the FIRST `load()` has settled (agent set OR loadError set), or\n * at once outside a browser. While the client is held before its first release,\n * it stays pending.\n */\n readonly ready: Promise<void>;\n\n private readonly opts: AgentLoadOptions;\n private readonly browser: boolean;\n private resolveReady: () => void = () => {};\n private started = false;\n private loaded: SignalsAgent | null = null;\n // Bumped on every withdrawal; a load() that settles under an older generation is discarded.\n private generation = 0;\n private readonly listeners = new Set<() => void>();\n\n /**\n * @param opts forwarded verbatim to `SignalsAgent.load()`.\n * @param browser whether we're running in a browser; `load()` only fires\n * client-side so SSR / prerender never touches\n * window/document/WebCrypto (the Angular analog of the Vue\n * plugin's `typeof window` guard).\n * @param initiallyHeld start held (issue 2894): do not load until `release()`.\n */\n constructor(opts: AgentLoadOptions, browser: boolean, initiallyHeld = false) {\n this.opts = opts;\n this.browser = browser;\n this.heldState = signal(initiallyHeld);\n this.held = this.heldState.asReadonly();\n this.ready = new Promise<void>((resolve) => {\n this.resolveReady = resolve;\n });\n if (!browser) {\n this.resolveReady();\n return;\n }\n if (!initiallyHeld) this.release();\n }\n\n /**\n * Leave the held state: load the agent once (a no-op if already released). What\n * `provideSignals()` calls when its `enabled` signal becomes true.\n */\n release(): void {\n this.heldState.set(false);\n if (!this.browser || this.started) return;\n this.started = true;\n const mine = this.generation;\n SignalsAgent.load(this.opts)\n .then((loaded) => {\n if (mine !== this.generation) {\n // Withdrawn while in flight. load() started the observers synchronously;\n // stop them, unless a newer release is running them now (page-wide singletons).\n if (!this.started) loaded.stopObservers();\n return;\n }\n this.loaded = loaded;\n this.agentState.set(loaded);\n this.settled();\n })\n .catch((err: unknown) => {\n if (mine !== this.generation) return;\n this.loadErrorState.set(err instanceof Error ? err : new Error(String(err)));\n this.settled();\n });\n }\n\n /**\n * Enter the held state. After a release this stops the SDK's interaction\n * listeners (page-wide, for this copy of the SDK), drops the agent and discards\n * a load still in flight; a request already sent is not recalled, and nothing\n * already received is deleted. Before any release it only marks the client held.\n */\n hold(): void {\n this.heldState.set(true);\n if (!this.started) {\n this.notify();\n return;\n }\n this.started = false;\n this.generation++;\n this.loaded?.stopObservers();\n this.loaded = null;\n this.agentState.set(null);\n this.loadErrorState.set(null);\n this.notify();\n }\n\n /**\n * @internal Called after every state change (a load settling, a hold). Lets\n * `injectVisitorData()` re-run without an Angular `effect`, so the auto-identify\n * fires exactly once per release regardless of change-detection timing.\n */\n onChange(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private settled(): void {\n this.resolveReady();\n this.notify();\n }\n\n private notify(): void {\n for (const l of [...this.listeners]) l();\n }\n}\n","import { isPlatformBrowser } from \"@angular/common\";\nimport {\n type EnvironmentProviders,\n PLATFORM_ID,\n type Signal,\n effect,\n inject,\n makeEnvironmentProviders,\n untracked,\n} from \"@angular/core\";\nimport type { AgentLoadOptions } from \"@signap/signals-web\";\nimport { SignalsClient } from \"./client\";\nimport { SIGNALS_CLIENT } from \"./tokens\";\n\n/**\n * Registers a single app-wide SignalsAgent — the idiomatic standalone-Angular\n * entry point, analogous to React's `<SignalsProvider>` and Vue's\n * `app.use(SignalsPlugin)`:\n *\n * ```ts\n * bootstrapApplication(AppComponent, {\n * providers: [\n * provideSignals({ apiKey: \"pk_live_...\", endpoint: \"https://ingest.example\" }),\n * ],\n * });\n * ```\n *\n * SSR-safe: the factory injects `PLATFORM_ID` and only lets the client call\n * `SignalsAgent.load()` in a browser, so a server render / prerender never loads\n * the agent.\n *\n * `opts` is snapshotted here at bootstrap; to swap apiKey/endpoint, re-bootstrap\n * (same snapshot-at-init contract as the React/Vue wrappers).\n */\nexport function provideSignals(\n opts: AgentLoadOptions,\n config: SignalsConfig = {},\n): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: SIGNALS_CLIENT,\n useFactory: () => {\n const browser = isPlatformBrowser(inject(PLATFORM_ID));\n const { enabled } = config;\n if (!enabled) return new SignalsClient(opts, browser);\n const client = new SignalsClient(opts, browser, !untracked(enabled));\n // The factory runs in an injection context, so the effect lives as long as\n // the environment injector. The client's writes are untracked so they are\n // not writes \"inside\" the effect (Angular 17/18 reject those by default).\n effect(() => {\n const on = enabled();\n untracked(() => (on ? client.release() : client.hold()));\n });\n return client;\n },\n },\n ]);\n}\n\n/** Second argument of `provideSignals()`. */\nexport interface SignalsConfig {\n /**\n * While this signal is `false` the client is held: it does not load the agent, so\n * the SDK registers no event listener, runs no collector and sends no request, and\n * `injectVisitorData()` returns `isHeld() === true`. When it becomes `true` the\n * agent loads and the first identify runs, as if `enabled` had never been set.\n * When it goes back to `false` the SDK's interaction listeners stop (page-wide, for\n * this copy of the SDK) and any identify result still in flight is discarded; a\n * request already sent is not recalled, and nothing already received is deleted.\n * Omitted = never held.\n */\n enabled?: Signal<boolean>;\n}\n","import { InjectionToken } from \"@angular/core\";\nimport type { SignalsClient } from \"./client\";\n\n/**\n * DI token the app-wide {@link SignalsClient} is registered under (by\n * `provideSignals()`) and that `injectVisitorData()` resolves. Most consumers\n * don't inject this directly — call `injectVisitorData()`. It is exported for\n * advanced use (e.g. `inject(SIGNALS_CLIENT).agent()` to read the raw agent).\n */\nexport const SIGNALS_CLIENT = new InjectionToken<SignalsClient>(\"signap.signals.client\");\n","import {\n DestroyRef,\n type Signal,\n assertInInjectionContext,\n computed,\n inject,\n signal,\n} from \"@angular/core\";\nimport type { IdentifyOptions, IdentifyResult } from \"@signap/signals-web\";\nimport { SIGNALS_CLIENT } from \"./tokens\";\n\nexport type InjectVisitorDataOptions = IdentifyOptions;\n\nexport interface InjectVisitorDataResult {\n /**\n * Resolved visitor from `agent.identify()`. Under ADR §A2 the call is a single\n * synchronous round-trip — `visitorId`/`confidence`/`identifiedAt` arrive\n * together, no polling. A `Signal`, so read it as `data()` (in a template or a\n * `computed`/`effect`).\n */\n data: Signal<IdentifyResult | undefined>;\n /** True between call time and the identify() resolution (or `error`). False while held. */\n isLoading: Signal<boolean>;\n /**\n * True while `provideSignals(opts, { enabled })` is held. Nothing is loaded or sent,\n * `data()` is `undefined` and `refetch()` does nothing. Always false without `enabled`.\n */\n isHeld: Signal<boolean>;\n error: Signal<Error | null>;\n /** Re-run `agent.identify()` (e.g. from a button click). */\n refetch: () => Promise<void>;\n}\n\n/**\n * Auto-identifies once the provider's agent is ready, then exposes the resolved\n * visitor as Angular signals — the framework-native analog of React's\n * `useVisitorData()` hook and Vue's `useVisitorData()` composable.\n *\n * Must be called in an injection context (a component/directive constructor or a\n * field initializer, or inside `runInInjectionContext`) below `provideSignals()`.\n */\nexport function injectVisitorData(opts?: InjectVisitorDataOptions): InjectVisitorDataResult {\n assertInInjectionContext(injectVisitorData);\n\n const client = inject(SIGNALS_CLIENT, { optional: true });\n if (!client) {\n throw new Error(\n \"[@signap/signals-angular] injectVisitorData() requires provideSignals() in the application (or component) providers\",\n );\n }\n\n const data = signal<IdentifyResult | undefined>(undefined);\n const error = signal<Error | null>(null);\n // Loading until the agent (or a load error) settles — unless held.\n const isLoading = signal(\n !client.held() && client.agent() === null && client.loadError() === null,\n );\n const isHeld = computed(() => client.held());\n\n // Supersede token so a slow response from a call that refetch() replaced (or a\n // response arriving after teardown) is discarded.\n let token = { cancelled: false };\n let started = false;\n const onChange = (): void => {\n if (client.held()) {\n // Held (issue 2894): discard any identify in flight and any previous result, and\n // re-arm so a later release identifies once, like a fresh call.\n token.cancelled = true;\n started = false;\n data.set(undefined);\n error.set(null);\n isLoading.set(false);\n return;\n }\n if (started) return;\n if (client.agent() === null && client.loadError() === null) return; // not settled yet\n started = true;\n void run();\n };\n const unsubscribe = client.onChange(onChange);\n inject(DestroyRef).onDestroy(() => {\n token.cancelled = true;\n unsubscribe();\n });\n\n const run = async (): Promise<void> => {\n const agent = client.agent();\n if (!agent) {\n const loadError = client.loadError();\n if (loadError) {\n error.set(loadError);\n isLoading.set(false);\n }\n return;\n }\n token.cancelled = true;\n const my = { cancelled: false };\n token = my;\n isLoading.set(true);\n error.set(null);\n data.set(undefined);\n try {\n const res = await agent.identify(opts);\n if (my.cancelled) return;\n data.set(res);\n } catch (err) {\n if (my.cancelled) return;\n error.set(err instanceof Error ? err : new Error(String(err)));\n } finally {\n if (!my.cancelled) isLoading.set(false);\n }\n };\n\n // Auto-run once per release, after load() settles. The client calls onChange on\n // every settle and hold; the first look is deferred to a microtask (as the old\n // `ready.then` was) so the returned signals are wired up before the first run().\n void Promise.resolve().then(onChange);\n\n return { data, isLoading, isHeld, error, refetch: run };\n}\n"],"mappings":"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","names":["import_signals_web","import_core","import_core","import_core"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -15,24 +15,55 @@ import { Signal, EnvironmentProviders, InjectionToken } from '@angular/core';
|
|
|
15
15
|
declare class SignalsClient {
|
|
16
16
|
private readonly agentState;
|
|
17
17
|
private readonly loadErrorState;
|
|
18
|
+
private readonly heldState;
|
|
18
19
|
/** Resolved agent, or `null` until `SignalsAgent.load()` settles. */
|
|
19
20
|
readonly agent: Signal<SignalsAgent | null>;
|
|
20
21
|
/** Non-null if `SignalsAgent.load()` rejected. */
|
|
21
22
|
readonly loadError: Signal<Error | null>;
|
|
23
|
+
/** True while held (`provideSignals(opts, { enabled })` with `enabled()` false): nothing is loaded. */
|
|
24
|
+
readonly held: Signal<boolean>;
|
|
22
25
|
/**
|
|
23
|
-
* Resolves once `load()` has settled (agent set OR loadError set)
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
+
* Resolves once the FIRST `load()` has settled (agent set OR loadError set), or
|
|
27
|
+
* at once outside a browser. While the client is held before its first release,
|
|
28
|
+
* it stays pending.
|
|
26
29
|
*/
|
|
27
30
|
readonly ready: Promise<void>;
|
|
31
|
+
private readonly opts;
|
|
32
|
+
private readonly browser;
|
|
33
|
+
private resolveReady;
|
|
34
|
+
private started;
|
|
35
|
+
private loaded;
|
|
36
|
+
private generation;
|
|
37
|
+
private readonly listeners;
|
|
28
38
|
/**
|
|
29
|
-
* @param opts
|
|
30
|
-
* @param browser
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
39
|
+
* @param opts forwarded verbatim to `SignalsAgent.load()`.
|
|
40
|
+
* @param browser whether we're running in a browser; `load()` only fires
|
|
41
|
+
* client-side so SSR / prerender never touches
|
|
42
|
+
* window/document/WebCrypto (the Angular analog of the Vue
|
|
43
|
+
* plugin's `typeof window` guard).
|
|
44
|
+
* @param initiallyHeld start held (issue 2894): do not load until `release()`.
|
|
34
45
|
*/
|
|
35
|
-
constructor(opts: AgentLoadOptions, browser: boolean);
|
|
46
|
+
constructor(opts: AgentLoadOptions, browser: boolean, initiallyHeld?: boolean);
|
|
47
|
+
/**
|
|
48
|
+
* Leave the held state: load the agent once (a no-op if already released). What
|
|
49
|
+
* `provideSignals()` calls when its `enabled` signal becomes true.
|
|
50
|
+
*/
|
|
51
|
+
release(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Enter the held state. After a release this stops the SDK's interaction
|
|
54
|
+
* listeners (page-wide, for this copy of the SDK), drops the agent and discards
|
|
55
|
+
* a load still in flight; a request already sent is not recalled, and nothing
|
|
56
|
+
* already received is deleted. Before any release it only marks the client held.
|
|
57
|
+
*/
|
|
58
|
+
hold(): void;
|
|
59
|
+
/**
|
|
60
|
+
* @internal Called after every state change (a load settling, a hold). Lets
|
|
61
|
+
* `injectVisitorData()` re-run without an Angular `effect`, so the auto-identify
|
|
62
|
+
* fires exactly once per release regardless of change-detection timing.
|
|
63
|
+
*/
|
|
64
|
+
onChange(listener: () => void): () => void;
|
|
65
|
+
private settled;
|
|
66
|
+
private notify;
|
|
36
67
|
}
|
|
37
68
|
|
|
38
69
|
/**
|
|
@@ -55,7 +86,21 @@ declare class SignalsClient {
|
|
|
55
86
|
* `opts` is snapshotted here at bootstrap; to swap apiKey/endpoint, re-bootstrap
|
|
56
87
|
* (same snapshot-at-init contract as the React/Vue wrappers).
|
|
57
88
|
*/
|
|
58
|
-
declare function provideSignals(opts: AgentLoadOptions): EnvironmentProviders;
|
|
89
|
+
declare function provideSignals(opts: AgentLoadOptions, config?: SignalsConfig): EnvironmentProviders;
|
|
90
|
+
/** Second argument of `provideSignals()`. */
|
|
91
|
+
interface SignalsConfig {
|
|
92
|
+
/**
|
|
93
|
+
* While this signal is `false` the client is held: it does not load the agent, so
|
|
94
|
+
* the SDK registers no event listener, runs no collector and sends no request, and
|
|
95
|
+
* `injectVisitorData()` returns `isHeld() === true`. When it becomes `true` the
|
|
96
|
+
* agent loads and the first identify runs, as if `enabled` had never been set.
|
|
97
|
+
* When it goes back to `false` the SDK's interaction listeners stop (page-wide, for
|
|
98
|
+
* this copy of the SDK) and any identify result still in flight is discarded; a
|
|
99
|
+
* request already sent is not recalled, and nothing already received is deleted.
|
|
100
|
+
* Omitted = never held.
|
|
101
|
+
*/
|
|
102
|
+
enabled?: Signal<boolean>;
|
|
103
|
+
}
|
|
59
104
|
|
|
60
105
|
/**
|
|
61
106
|
* DI token the app-wide {@link SignalsClient} is registered under (by
|
|
@@ -74,8 +119,13 @@ interface InjectVisitorDataResult {
|
|
|
74
119
|
* `computed`/`effect`).
|
|
75
120
|
*/
|
|
76
121
|
data: Signal<IdentifyResult | undefined>;
|
|
77
|
-
/** True between call time and the identify() resolution (or `error`). */
|
|
122
|
+
/** True between call time and the identify() resolution (or `error`). False while held. */
|
|
78
123
|
isLoading: Signal<boolean>;
|
|
124
|
+
/**
|
|
125
|
+
* True while `provideSignals(opts, { enabled })` is held. Nothing is loaded or sent,
|
|
126
|
+
* `data()` is `undefined` and `refetch()` does nothing. Always false without `enabled`.
|
|
127
|
+
*/
|
|
128
|
+
isHeld: Signal<boolean>;
|
|
79
129
|
error: Signal<Error | null>;
|
|
80
130
|
/** Re-run `agent.identify()` (e.g. from a button click). */
|
|
81
131
|
refetch: () => Promise<void>;
|
|
@@ -90,4 +140,4 @@ interface InjectVisitorDataResult {
|
|
|
90
140
|
*/
|
|
91
141
|
declare function injectVisitorData(opts?: InjectVisitorDataOptions): InjectVisitorDataResult;
|
|
92
142
|
|
|
93
|
-
export { type InjectVisitorDataOptions, type InjectVisitorDataResult, SIGNALS_CLIENT, SignalsClient, injectVisitorData, provideSignals };
|
|
143
|
+
export { type InjectVisitorDataOptions, type InjectVisitorDataResult, SIGNALS_CLIENT, SignalsClient, type SignalsConfig, injectVisitorData, provideSignals };
|
package/dist/index.d.ts
CHANGED
|
@@ -15,24 +15,55 @@ import { Signal, EnvironmentProviders, InjectionToken } from '@angular/core';
|
|
|
15
15
|
declare class SignalsClient {
|
|
16
16
|
private readonly agentState;
|
|
17
17
|
private readonly loadErrorState;
|
|
18
|
+
private readonly heldState;
|
|
18
19
|
/** Resolved agent, or `null` until `SignalsAgent.load()` settles. */
|
|
19
20
|
readonly agent: Signal<SignalsAgent | null>;
|
|
20
21
|
/** Non-null if `SignalsAgent.load()` rejected. */
|
|
21
22
|
readonly loadError: Signal<Error | null>;
|
|
23
|
+
/** True while held (`provideSignals(opts, { enabled })` with `enabled()` false): nothing is loaded. */
|
|
24
|
+
readonly held: Signal<boolean>;
|
|
22
25
|
/**
|
|
23
|
-
* Resolves once `load()` has settled (agent set OR loadError set)
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
+
* Resolves once the FIRST `load()` has settled (agent set OR loadError set), or
|
|
27
|
+
* at once outside a browser. While the client is held before its first release,
|
|
28
|
+
* it stays pending.
|
|
26
29
|
*/
|
|
27
30
|
readonly ready: Promise<void>;
|
|
31
|
+
private readonly opts;
|
|
32
|
+
private readonly browser;
|
|
33
|
+
private resolveReady;
|
|
34
|
+
private started;
|
|
35
|
+
private loaded;
|
|
36
|
+
private generation;
|
|
37
|
+
private readonly listeners;
|
|
28
38
|
/**
|
|
29
|
-
* @param opts
|
|
30
|
-
* @param browser
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
39
|
+
* @param opts forwarded verbatim to `SignalsAgent.load()`.
|
|
40
|
+
* @param browser whether we're running in a browser; `load()` only fires
|
|
41
|
+
* client-side so SSR / prerender never touches
|
|
42
|
+
* window/document/WebCrypto (the Angular analog of the Vue
|
|
43
|
+
* plugin's `typeof window` guard).
|
|
44
|
+
* @param initiallyHeld start held (issue 2894): do not load until `release()`.
|
|
34
45
|
*/
|
|
35
|
-
constructor(opts: AgentLoadOptions, browser: boolean);
|
|
46
|
+
constructor(opts: AgentLoadOptions, browser: boolean, initiallyHeld?: boolean);
|
|
47
|
+
/**
|
|
48
|
+
* Leave the held state: load the agent once (a no-op if already released). What
|
|
49
|
+
* `provideSignals()` calls when its `enabled` signal becomes true.
|
|
50
|
+
*/
|
|
51
|
+
release(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Enter the held state. After a release this stops the SDK's interaction
|
|
54
|
+
* listeners (page-wide, for this copy of the SDK), drops the agent and discards
|
|
55
|
+
* a load still in flight; a request already sent is not recalled, and nothing
|
|
56
|
+
* already received is deleted. Before any release it only marks the client held.
|
|
57
|
+
*/
|
|
58
|
+
hold(): void;
|
|
59
|
+
/**
|
|
60
|
+
* @internal Called after every state change (a load settling, a hold). Lets
|
|
61
|
+
* `injectVisitorData()` re-run without an Angular `effect`, so the auto-identify
|
|
62
|
+
* fires exactly once per release regardless of change-detection timing.
|
|
63
|
+
*/
|
|
64
|
+
onChange(listener: () => void): () => void;
|
|
65
|
+
private settled;
|
|
66
|
+
private notify;
|
|
36
67
|
}
|
|
37
68
|
|
|
38
69
|
/**
|
|
@@ -55,7 +86,21 @@ declare class SignalsClient {
|
|
|
55
86
|
* `opts` is snapshotted here at bootstrap; to swap apiKey/endpoint, re-bootstrap
|
|
56
87
|
* (same snapshot-at-init contract as the React/Vue wrappers).
|
|
57
88
|
*/
|
|
58
|
-
declare function provideSignals(opts: AgentLoadOptions): EnvironmentProviders;
|
|
89
|
+
declare function provideSignals(opts: AgentLoadOptions, config?: SignalsConfig): EnvironmentProviders;
|
|
90
|
+
/** Second argument of `provideSignals()`. */
|
|
91
|
+
interface SignalsConfig {
|
|
92
|
+
/**
|
|
93
|
+
* While this signal is `false` the client is held: it does not load the agent, so
|
|
94
|
+
* the SDK registers no event listener, runs no collector and sends no request, and
|
|
95
|
+
* `injectVisitorData()` returns `isHeld() === true`. When it becomes `true` the
|
|
96
|
+
* agent loads and the first identify runs, as if `enabled` had never been set.
|
|
97
|
+
* When it goes back to `false` the SDK's interaction listeners stop (page-wide, for
|
|
98
|
+
* this copy of the SDK) and any identify result still in flight is discarded; a
|
|
99
|
+
* request already sent is not recalled, and nothing already received is deleted.
|
|
100
|
+
* Omitted = never held.
|
|
101
|
+
*/
|
|
102
|
+
enabled?: Signal<boolean>;
|
|
103
|
+
}
|
|
59
104
|
|
|
60
105
|
/**
|
|
61
106
|
* DI token the app-wide {@link SignalsClient} is registered under (by
|
|
@@ -74,8 +119,13 @@ interface InjectVisitorDataResult {
|
|
|
74
119
|
* `computed`/`effect`).
|
|
75
120
|
*/
|
|
76
121
|
data: Signal<IdentifyResult | undefined>;
|
|
77
|
-
/** True between call time and the identify() resolution (or `error`). */
|
|
122
|
+
/** True between call time and the identify() resolution (or `error`). False while held. */
|
|
78
123
|
isLoading: Signal<boolean>;
|
|
124
|
+
/**
|
|
125
|
+
* True while `provideSignals(opts, { enabled })` is held. Nothing is loaded or sent,
|
|
126
|
+
* `data()` is `undefined` and `refetch()` does nothing. Always false without `enabled`.
|
|
127
|
+
*/
|
|
128
|
+
isHeld: Signal<boolean>;
|
|
79
129
|
error: Signal<Error | null>;
|
|
80
130
|
/** Re-run `agent.identify()` (e.g. from a button click). */
|
|
81
131
|
refetch: () => Promise<void>;
|
|
@@ -90,4 +140,4 @@ interface InjectVisitorDataResult {
|
|
|
90
140
|
*/
|
|
91
141
|
declare function injectVisitorData(opts?: InjectVisitorDataOptions): InjectVisitorDataResult;
|
|
92
142
|
|
|
93
|
-
export { type InjectVisitorDataOptions, type InjectVisitorDataResult, SIGNALS_CLIENT, SignalsClient, injectVisitorData, provideSignals };
|
|
143
|
+
export { type InjectVisitorDataOptions, type InjectVisitorDataResult, SIGNALS_CLIENT, SignalsClient, type SignalsConfig, injectVisitorData, provideSignals };
|
package/dist/index.js
CHANGED
|
@@ -10,42 +10,119 @@ import { SignalsAgent } from "@signap/signals-web";
|
|
|
10
10
|
var SignalsClient = class {
|
|
11
11
|
agentState = signal(null);
|
|
12
12
|
loadErrorState = signal(null);
|
|
13
|
+
heldState;
|
|
13
14
|
/** Resolved agent, or `null` until `SignalsAgent.load()` settles. */
|
|
14
15
|
agent = this.agentState.asReadonly();
|
|
15
16
|
/** Non-null if `SignalsAgent.load()` rejected. */
|
|
16
17
|
loadError = this.loadErrorState.asReadonly();
|
|
18
|
+
/** True while held (`provideSignals(opts, { enabled })` with `enabled()` false): nothing is loaded. */
|
|
19
|
+
held;
|
|
17
20
|
/**
|
|
18
|
-
* Resolves once `load()` has settled (agent set OR loadError set)
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
+
* Resolves once the FIRST `load()` has settled (agent set OR loadError set), or
|
|
22
|
+
* at once outside a browser. While the client is held before its first release,
|
|
23
|
+
* it stays pending.
|
|
21
24
|
*/
|
|
22
25
|
ready;
|
|
26
|
+
opts;
|
|
27
|
+
browser;
|
|
28
|
+
resolveReady = () => {
|
|
29
|
+
};
|
|
30
|
+
started = false;
|
|
31
|
+
loaded = null;
|
|
32
|
+
// Bumped on every withdrawal; a load() that settles under an older generation is discarded.
|
|
33
|
+
generation = 0;
|
|
34
|
+
listeners = /* @__PURE__ */ new Set();
|
|
23
35
|
/**
|
|
24
|
-
* @param opts
|
|
25
|
-
* @param browser
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
36
|
+
* @param opts forwarded verbatim to `SignalsAgent.load()`.
|
|
37
|
+
* @param browser whether we're running in a browser; `load()` only fires
|
|
38
|
+
* client-side so SSR / prerender never touches
|
|
39
|
+
* window/document/WebCrypto (the Angular analog of the Vue
|
|
40
|
+
* plugin's `typeof window` guard).
|
|
41
|
+
* @param initiallyHeld start held (issue 2894): do not load until `release()`.
|
|
29
42
|
*/
|
|
30
|
-
constructor(opts, browser) {
|
|
43
|
+
constructor(opts, browser, initiallyHeld = false) {
|
|
44
|
+
this.opts = opts;
|
|
45
|
+
this.browser = browser;
|
|
46
|
+
this.heldState = signal(initiallyHeld);
|
|
47
|
+
this.held = this.heldState.asReadonly();
|
|
48
|
+
this.ready = new Promise((resolve) => {
|
|
49
|
+
this.resolveReady = resolve;
|
|
50
|
+
});
|
|
31
51
|
if (!browser) {
|
|
32
|
-
this.
|
|
52
|
+
this.resolveReady();
|
|
33
53
|
return;
|
|
34
54
|
}
|
|
35
|
-
|
|
55
|
+
if (!initiallyHeld) this.release();
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Leave the held state: load the agent once (a no-op if already released). What
|
|
59
|
+
* `provideSignals()` calls when its `enabled` signal becomes true.
|
|
60
|
+
*/
|
|
61
|
+
release() {
|
|
62
|
+
this.heldState.set(false);
|
|
63
|
+
if (!this.browser || this.started) return;
|
|
64
|
+
this.started = true;
|
|
65
|
+
const mine = this.generation;
|
|
66
|
+
SignalsAgent.load(this.opts).then((loaded) => {
|
|
67
|
+
if (mine !== this.generation) {
|
|
68
|
+
if (!this.started) loaded.stopObservers();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
this.loaded = loaded;
|
|
36
72
|
this.agentState.set(loaded);
|
|
73
|
+
this.settled();
|
|
37
74
|
}).catch((err) => {
|
|
75
|
+
if (mine !== this.generation) return;
|
|
38
76
|
this.loadErrorState.set(err instanceof Error ? err : new Error(String(err)));
|
|
77
|
+
this.settled();
|
|
39
78
|
});
|
|
40
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Enter the held state. After a release this stops the SDK's interaction
|
|
82
|
+
* listeners (page-wide, for this copy of the SDK), drops the agent and discards
|
|
83
|
+
* a load still in flight; a request already sent is not recalled, and nothing
|
|
84
|
+
* already received is deleted. Before any release it only marks the client held.
|
|
85
|
+
*/
|
|
86
|
+
hold() {
|
|
87
|
+
this.heldState.set(true);
|
|
88
|
+
if (!this.started) {
|
|
89
|
+
this.notify();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.started = false;
|
|
93
|
+
this.generation++;
|
|
94
|
+
this.loaded?.stopObservers();
|
|
95
|
+
this.loaded = null;
|
|
96
|
+
this.agentState.set(null);
|
|
97
|
+
this.loadErrorState.set(null);
|
|
98
|
+
this.notify();
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* @internal Called after every state change (a load settling, a hold). Lets
|
|
102
|
+
* `injectVisitorData()` re-run without an Angular `effect`, so the auto-identify
|
|
103
|
+
* fires exactly once per release regardless of change-detection timing.
|
|
104
|
+
*/
|
|
105
|
+
onChange(listener) {
|
|
106
|
+
this.listeners.add(listener);
|
|
107
|
+
return () => this.listeners.delete(listener);
|
|
108
|
+
}
|
|
109
|
+
settled() {
|
|
110
|
+
this.resolveReady();
|
|
111
|
+
this.notify();
|
|
112
|
+
}
|
|
113
|
+
notify() {
|
|
114
|
+
for (const l of [...this.listeners]) l();
|
|
115
|
+
}
|
|
41
116
|
};
|
|
42
117
|
|
|
43
118
|
// src/provide-signals.ts
|
|
44
119
|
import { isPlatformBrowser } from "@angular/common";
|
|
45
120
|
import {
|
|
46
121
|
PLATFORM_ID,
|
|
122
|
+
effect,
|
|
47
123
|
inject,
|
|
48
|
-
makeEnvironmentProviders
|
|
124
|
+
makeEnvironmentProviders,
|
|
125
|
+
untracked
|
|
49
126
|
} from "@angular/core";
|
|
50
127
|
|
|
51
128
|
// src/tokens.ts
|
|
@@ -53,17 +130,33 @@ import { InjectionToken } from "@angular/core";
|
|
|
53
130
|
var SIGNALS_CLIENT = new InjectionToken("signap.signals.client");
|
|
54
131
|
|
|
55
132
|
// src/provide-signals.ts
|
|
56
|
-
function provideSignals(opts) {
|
|
133
|
+
function provideSignals(opts, config = {}) {
|
|
57
134
|
return makeEnvironmentProviders([
|
|
58
135
|
{
|
|
59
136
|
provide: SIGNALS_CLIENT,
|
|
60
|
-
useFactory: () =>
|
|
137
|
+
useFactory: () => {
|
|
138
|
+
const browser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
139
|
+
const { enabled } = config;
|
|
140
|
+
if (!enabled) return new SignalsClient(opts, browser);
|
|
141
|
+
const client = new SignalsClient(opts, browser, !untracked(enabled));
|
|
142
|
+
effect(() => {
|
|
143
|
+
const on = enabled();
|
|
144
|
+
untracked(() => on ? client.release() : client.hold());
|
|
145
|
+
});
|
|
146
|
+
return client;
|
|
147
|
+
}
|
|
61
148
|
}
|
|
62
149
|
]);
|
|
63
150
|
}
|
|
64
151
|
|
|
65
152
|
// src/inject-visitor-data.ts
|
|
66
|
-
import {
|
|
153
|
+
import {
|
|
154
|
+
DestroyRef,
|
|
155
|
+
assertInInjectionContext,
|
|
156
|
+
computed,
|
|
157
|
+
inject as inject2,
|
|
158
|
+
signal as signal2
|
|
159
|
+
} from "@angular/core";
|
|
67
160
|
function injectVisitorData(opts) {
|
|
68
161
|
assertInInjectionContext(injectVisitorData);
|
|
69
162
|
const client = inject2(SIGNALS_CLIENT, { optional: true });
|
|
@@ -74,10 +167,30 @@ function injectVisitorData(opts) {
|
|
|
74
167
|
}
|
|
75
168
|
const data = signal2(void 0);
|
|
76
169
|
const error = signal2(null);
|
|
77
|
-
const isLoading = signal2(
|
|
170
|
+
const isLoading = signal2(
|
|
171
|
+
!client.held() && client.agent() === null && client.loadError() === null
|
|
172
|
+
);
|
|
173
|
+
const isHeld = computed(() => client.held());
|
|
78
174
|
let token = { cancelled: false };
|
|
175
|
+
let started = false;
|
|
176
|
+
const onChange = () => {
|
|
177
|
+
if (client.held()) {
|
|
178
|
+
token.cancelled = true;
|
|
179
|
+
started = false;
|
|
180
|
+
data.set(void 0);
|
|
181
|
+
error.set(null);
|
|
182
|
+
isLoading.set(false);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (started) return;
|
|
186
|
+
if (client.agent() === null && client.loadError() === null) return;
|
|
187
|
+
started = true;
|
|
188
|
+
void run();
|
|
189
|
+
};
|
|
190
|
+
const unsubscribe = client.onChange(onChange);
|
|
79
191
|
inject2(DestroyRef).onDestroy(() => {
|
|
80
192
|
token.cancelled = true;
|
|
193
|
+
unsubscribe();
|
|
81
194
|
});
|
|
82
195
|
const run = async () => {
|
|
83
196
|
const agent = client.agent();
|
|
@@ -106,8 +219,8 @@ function injectVisitorData(opts) {
|
|
|
106
219
|
if (!my.cancelled) isLoading.set(false);
|
|
107
220
|
}
|
|
108
221
|
};
|
|
109
|
-
void
|
|
110
|
-
return { data, isLoading, error, refetch: run };
|
|
222
|
+
void Promise.resolve().then(onChange);
|
|
223
|
+
return { data, isLoading, isHeld, error, refetch: run };
|
|
111
224
|
}
|
|
112
225
|
export {
|
|
113
226
|
SIGNALS_CLIENT,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/provide-signals.ts","../src/tokens.ts","../src/inject-visitor-data.ts"],"sourcesContent":["export {\n type AgentLoadOptions,\n type IdentifyOptions,\n type IdentifyResult,\n SdkError,\n type SdkErrorCode,\n VERSION as WEB_SDK_VERSION,\n} from \"@signap/signals-web\";\n\nexport { SignalsClient } from \"./client\";\nexport { provideSignals } from \"./provide-signals\";\nexport { SIGNALS_CLIENT } from \"./tokens\";\nexport {\n injectVisitorData,\n type InjectVisitorDataOptions,\n type InjectVisitorDataResult,\n} from \"./inject-visitor-data\";\n","import { type Signal, signal } from \"@angular/core\";\nimport { type AgentLoadOptions, SignalsAgent } from \"@signap/signals-web\";\n\n/**\n * Holds a single lazily-loaded SignalsAgent and exposes its readiness as Angular\n * signals — the framework-native analog of the Vue wrapper's provide/inject refs\n * and the React wrapper's context. `agent()` is `null` until `SignalsAgent.load()`\n * settles; `loadError()` is non-null if it rejected (e.g. an invalid apiKey).\n *\n * Deliberately NOT `@Injectable`: a decorator-free class keeps this package\n * AOT-safe under a plain tsup build (no ngtsc / ng-packagr partial-Ivy metadata\n * step). It is wired into DI by the factory in `provideSignals()`.\n */\nexport class SignalsClient {\n private readonly agentState = signal<SignalsAgent | null>(null);\n private readonly loadErrorState = signal<Error | null>(null);\n\n /** Resolved agent, or `null` until `SignalsAgent.load()` settles. */\n readonly agent: Signal<SignalsAgent | null> = this.agentState.asReadonly();\n /** Non-null if `SignalsAgent.load()` rejected. */\n readonly loadError: Signal<Error | null> = this.loadErrorState.asReadonly();\n /**\n * Resolves once `load()` has settled (agent set OR loadError set). Lets\n * `injectVisitorData()` await readiness without an Angular `effect`, so the\n * auto-identify fires exactly once regardless of change-detection timing.\n */\n readonly ready: Promise<void>;\n\n /**\n * @param opts forwarded verbatim to `SignalsAgent.load()`.\n * @param browser whether we're running in a browser; `load()` only fires\n * client-side so SSR / prerender never touches\n * window/document/WebCrypto (the Angular analog of the Vue\n * plugin's `typeof window` guard).\n */\n constructor(opts: AgentLoadOptions, browser: boolean) {\n if (!browser) {\n this.ready = Promise.resolve();\n return;\n }\n this.ready = SignalsAgent.load(opts)\n .then((loaded) => {\n this.agentState.set(loaded);\n })\n .catch((err: unknown) => {\n this.loadErrorState.set(err instanceof Error ? err : new Error(String(err)));\n });\n }\n}\n","import { isPlatformBrowser } from \"@angular/common\";\nimport {\n type EnvironmentProviders,\n PLATFORM_ID,\n inject,\n makeEnvironmentProviders,\n} from \"@angular/core\";\nimport type { AgentLoadOptions } from \"@signap/signals-web\";\nimport { SignalsClient } from \"./client\";\nimport { SIGNALS_CLIENT } from \"./tokens\";\n\n/**\n * Registers a single app-wide SignalsAgent — the idiomatic standalone-Angular\n * entry point, analogous to React's `<SignalsProvider>` and Vue's\n * `app.use(SignalsPlugin)`:\n *\n * ```ts\n * bootstrapApplication(AppComponent, {\n * providers: [\n * provideSignals({ apiKey: \"pk_live_...\", endpoint: \"https://ingest.example\" }),\n * ],\n * });\n * ```\n *\n * SSR-safe: the factory injects `PLATFORM_ID` and only lets the client call\n * `SignalsAgent.load()` in a browser, so a server render / prerender never loads\n * the agent.\n *\n * `opts` is snapshotted here at bootstrap; to swap apiKey/endpoint, re-bootstrap\n * (same snapshot-at-init contract as the React/Vue wrappers).\n */\nexport function provideSignals(opts: AgentLoadOptions): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: SIGNALS_CLIENT,\n useFactory: () => new SignalsClient(opts, isPlatformBrowser(inject(PLATFORM_ID))),\n },\n ]);\n}\n","import { InjectionToken } from \"@angular/core\";\nimport type { SignalsClient } from \"./client\";\n\n/**\n * DI token the app-wide {@link SignalsClient} is registered under (by\n * `provideSignals()`) and that `injectVisitorData()` resolves. Most consumers\n * don't inject this directly — call `injectVisitorData()`. It is exported for\n * advanced use (e.g. `inject(SIGNALS_CLIENT).agent()` to read the raw agent).\n */\nexport const SIGNALS_CLIENT = new InjectionToken<SignalsClient>(\"signap.signals.client\");\n","import { DestroyRef, type Signal, assertInInjectionContext, inject, signal } from \"@angular/core\";\nimport type { IdentifyOptions, IdentifyResult } from \"@signap/signals-web\";\nimport { SIGNALS_CLIENT } from \"./tokens\";\n\nexport type InjectVisitorDataOptions = IdentifyOptions;\n\nexport interface InjectVisitorDataResult {\n /**\n * Resolved visitor from `agent.identify()`. Under ADR §A2 the call is a single\n * synchronous round-trip — `visitorId`/`confidence`/`identifiedAt` arrive\n * together, no polling. A `Signal`, so read it as `data()` (in a template or a\n * `computed`/`effect`).\n */\n data: Signal<IdentifyResult | undefined>;\n /** True between call time and the identify() resolution (or `error`). */\n isLoading: Signal<boolean>;\n error: Signal<Error | null>;\n /** Re-run `agent.identify()` (e.g. from a button click). */\n refetch: () => Promise<void>;\n}\n\n/**\n * Auto-identifies once the provider's agent is ready, then exposes the resolved\n * visitor as Angular signals — the framework-native analog of React's\n * `useVisitorData()` hook and Vue's `useVisitorData()` composable.\n *\n * Must be called in an injection context (a component/directive constructor or a\n * field initializer, or inside `runInInjectionContext`) below `provideSignals()`.\n */\nexport function injectVisitorData(opts?: InjectVisitorDataOptions): InjectVisitorDataResult {\n assertInInjectionContext(injectVisitorData);\n\n const client = inject(SIGNALS_CLIENT, { optional: true });\n if (!client) {\n throw new Error(\n \"[@signap/signals-angular] injectVisitorData() requires provideSignals() in the application (or component) providers\",\n );\n }\n\n const data = signal<IdentifyResult | undefined>(undefined);\n const error = signal<Error | null>(null);\n // Loading until the agent (or a load error) settles.\n const isLoading = signal(client.agent() === null && client.loadError() === null);\n\n // Supersede token so a slow response from a call that refetch() replaced (or a\n // response arriving after teardown) is discarded.\n let token = { cancelled: false };\n inject(DestroyRef).onDestroy(() => {\n token.cancelled = true;\n });\n\n const run = async (): Promise<void> => {\n const agent = client.agent();\n if (!agent) {\n const loadError = client.loadError();\n if (loadError) {\n error.set(loadError);\n isLoading.set(false);\n }\n return;\n }\n token.cancelled = true;\n const my = { cancelled: false };\n token = my;\n isLoading.set(true);\n error.set(null);\n data.set(undefined);\n try {\n const res = await agent.identify(opts);\n if (my.cancelled) return;\n data.set(res);\n } catch (err) {\n if (my.cancelled) return;\n error.set(err instanceof Error ? err : new Error(String(err)));\n } finally {\n if (!my.cancelled) isLoading.set(false);\n }\n };\n\n // Auto-run exactly once, after load() settles. `ready` resolves whether load()\n // succeeded or failed; even if it already resolved, `.then` defers to a\n // microtask so the returned signals are wired up before the first run().\n void client.ready.then(() => run());\n\n return { data, isLoading, error, refetch: run };\n}\n"],"mappings":";AAAA;AAAA,EAIE;AAAA,EAEW;AAAA,OACN;;;ACPP,SAAsB,cAAc;AACpC,SAAgC,oBAAoB;AAY7C,IAAM,gBAAN,MAAoB;AAAA,EACR,aAAa,OAA4B,IAAI;AAAA,EAC7C,iBAAiB,OAAqB,IAAI;AAAA;AAAA,EAGlD,QAAqC,KAAK,WAAW,WAAW;AAAA;AAAA,EAEhE,YAAkC,KAAK,eAAe,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMjE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAST,YAAY,MAAwB,SAAkB;AACpD,QAAI,CAAC,SAAS;AACZ,WAAK,QAAQ,QAAQ,QAAQ;AAC7B;AAAA,IACF;AACA,SAAK,QAAQ,aAAa,KAAK,IAAI,EAChC,KAAK,CAAC,WAAW;AAChB,WAAK,WAAW,IAAI,MAAM;AAAA,IAC5B,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,WAAK,eAAe,IAAI,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAAA,IAC7E,CAAC;AAAA,EACL;AACF;;;AChDA,SAAS,yBAAyB;AAClC;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACNP,SAAS,sBAAsB;AASxB,IAAM,iBAAiB,IAAI,eAA8B,uBAAuB;;;ADsBhF,SAAS,eAAe,MAA8C;AAC3E,SAAO,yBAAyB;AAAA,IAC9B;AAAA,MACE,SAAS;AAAA,MACT,YAAY,MAAM,IAAI,cAAc,MAAM,kBAAkB,OAAO,WAAW,CAAC,CAAC;AAAA,IAClF;AAAA,EACF,CAAC;AACH;;;AEtCA,SAAS,YAAyB,0BAA0B,UAAAA,SAAQ,UAAAC,eAAc;AA6B3E,SAAS,kBAAkB,MAA0D;AAC1F,2BAAyB,iBAAiB;AAE1C,QAAM,SAASC,QAAO,gBAAgB,EAAE,UAAU,KAAK,CAAC;AACxD,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAOC,QAAmC,MAAS;AACzD,QAAM,QAAQA,QAAqB,IAAI;AAEvC,QAAM,YAAYA,QAAO,OAAO,MAAM,MAAM,QAAQ,OAAO,UAAU,MAAM,IAAI;AAI/E,MAAI,QAAQ,EAAE,WAAW,MAAM;AAC/B,EAAAD,QAAO,UAAU,EAAE,UAAU,MAAM;AACjC,UAAM,YAAY;AAAA,EACpB,CAAC;AAED,QAAM,MAAM,YAA2B;AACrC,UAAM,QAAQ,OAAO,MAAM;AAC3B,QAAI,CAAC,OAAO;AACV,YAAM,YAAY,OAAO,UAAU;AACnC,UAAI,WAAW;AACb,cAAM,IAAI,SAAS;AACnB,kBAAU,IAAI,KAAK;AAAA,MACrB;AACA;AAAA,IACF;AACA,UAAM,YAAY;AAClB,UAAM,KAAK,EAAE,WAAW,MAAM;AAC9B,YAAQ;AACR,cAAU,IAAI,IAAI;AAClB,UAAM,IAAI,IAAI;AACd,SAAK,IAAI,MAAS;AAClB,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,SAAS,IAAI;AACrC,UAAI,GAAG,UAAW;AAClB,WAAK,IAAI,GAAG;AAAA,IACd,SAAS,KAAK;AACZ,UAAI,GAAG,UAAW;AAClB,YAAM,IAAI,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAAA,IAC/D,UAAE;AACA,UAAI,CAAC,GAAG,UAAW,WAAU,IAAI,KAAK;AAAA,IACxC;AAAA,EACF;AAKA,OAAK,OAAO,MAAM,KAAK,MAAM,IAAI,CAAC;AAElC,SAAO,EAAE,MAAM,WAAW,OAAO,SAAS,IAAI;AAChD;","names":["inject","signal","inject","signal"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/provide-signals.ts","../src/tokens.ts","../src/inject-visitor-data.ts"],"sourcesContent":["export {\n type AgentLoadOptions,\n type IdentifyOptions,\n type IdentifyResult,\n SdkError,\n type SdkErrorCode,\n VERSION as WEB_SDK_VERSION,\n} from \"@signap/signals-web\";\n\nexport { SignalsClient } from \"./client\";\nexport { provideSignals, type SignalsConfig } from \"./provide-signals\";\nexport { SIGNALS_CLIENT } from \"./tokens\";\nexport {\n injectVisitorData,\n type InjectVisitorDataOptions,\n type InjectVisitorDataResult,\n} from \"./inject-visitor-data\";\n","import { type Signal, type WritableSignal, signal } from \"@angular/core\";\nimport { type AgentLoadOptions, SignalsAgent } from \"@signap/signals-web\";\n\n/**\n * Holds a single lazily-loaded SignalsAgent and exposes its readiness as Angular\n * signals — the framework-native analog of the Vue wrapper's provide/inject refs\n * and the React wrapper's context. `agent()` is `null` until `SignalsAgent.load()`\n * settles; `loadError()` is non-null if it rejected (e.g. an invalid apiKey).\n *\n * Deliberately NOT `@Injectable`: a decorator-free class keeps this package\n * AOT-safe under a plain tsup build (no ngtsc / ng-packagr partial-Ivy metadata\n * step). It is wired into DI by the factory in `provideSignals()`.\n */\nexport class SignalsClient {\n private readonly agentState = signal<SignalsAgent | null>(null);\n private readonly loadErrorState = signal<Error | null>(null);\n private readonly heldState: WritableSignal<boolean>;\n\n /** Resolved agent, or `null` until `SignalsAgent.load()` settles. */\n readonly agent: Signal<SignalsAgent | null> = this.agentState.asReadonly();\n /** Non-null if `SignalsAgent.load()` rejected. */\n readonly loadError: Signal<Error | null> = this.loadErrorState.asReadonly();\n /** True while held (`provideSignals(opts, { enabled })` with `enabled()` false): nothing is loaded. */\n readonly held: Signal<boolean>;\n /**\n * Resolves once the FIRST `load()` has settled (agent set OR loadError set), or\n * at once outside a browser. While the client is held before its first release,\n * it stays pending.\n */\n readonly ready: Promise<void>;\n\n private readonly opts: AgentLoadOptions;\n private readonly browser: boolean;\n private resolveReady: () => void = () => {};\n private started = false;\n private loaded: SignalsAgent | null = null;\n // Bumped on every withdrawal; a load() that settles under an older generation is discarded.\n private generation = 0;\n private readonly listeners = new Set<() => void>();\n\n /**\n * @param opts forwarded verbatim to `SignalsAgent.load()`.\n * @param browser whether we're running in a browser; `load()` only fires\n * client-side so SSR / prerender never touches\n * window/document/WebCrypto (the Angular analog of the Vue\n * plugin's `typeof window` guard).\n * @param initiallyHeld start held (issue 2894): do not load until `release()`.\n */\n constructor(opts: AgentLoadOptions, browser: boolean, initiallyHeld = false) {\n this.opts = opts;\n this.browser = browser;\n this.heldState = signal(initiallyHeld);\n this.held = this.heldState.asReadonly();\n this.ready = new Promise<void>((resolve) => {\n this.resolveReady = resolve;\n });\n if (!browser) {\n this.resolveReady();\n return;\n }\n if (!initiallyHeld) this.release();\n }\n\n /**\n * Leave the held state: load the agent once (a no-op if already released). What\n * `provideSignals()` calls when its `enabled` signal becomes true.\n */\n release(): void {\n this.heldState.set(false);\n if (!this.browser || this.started) return;\n this.started = true;\n const mine = this.generation;\n SignalsAgent.load(this.opts)\n .then((loaded) => {\n if (mine !== this.generation) {\n // Withdrawn while in flight. load() started the observers synchronously;\n // stop them, unless a newer release is running them now (page-wide singletons).\n if (!this.started) loaded.stopObservers();\n return;\n }\n this.loaded = loaded;\n this.agentState.set(loaded);\n this.settled();\n })\n .catch((err: unknown) => {\n if (mine !== this.generation) return;\n this.loadErrorState.set(err instanceof Error ? err : new Error(String(err)));\n this.settled();\n });\n }\n\n /**\n * Enter the held state. After a release this stops the SDK's interaction\n * listeners (page-wide, for this copy of the SDK), drops the agent and discards\n * a load still in flight; a request already sent is not recalled, and nothing\n * already received is deleted. Before any release it only marks the client held.\n */\n hold(): void {\n this.heldState.set(true);\n if (!this.started) {\n this.notify();\n return;\n }\n this.started = false;\n this.generation++;\n this.loaded?.stopObservers();\n this.loaded = null;\n this.agentState.set(null);\n this.loadErrorState.set(null);\n this.notify();\n }\n\n /**\n * @internal Called after every state change (a load settling, a hold). Lets\n * `injectVisitorData()` re-run without an Angular `effect`, so the auto-identify\n * fires exactly once per release regardless of change-detection timing.\n */\n onChange(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private settled(): void {\n this.resolveReady();\n this.notify();\n }\n\n private notify(): void {\n for (const l of [...this.listeners]) l();\n }\n}\n","import { isPlatformBrowser } from \"@angular/common\";\nimport {\n type EnvironmentProviders,\n PLATFORM_ID,\n type Signal,\n effect,\n inject,\n makeEnvironmentProviders,\n untracked,\n} from \"@angular/core\";\nimport type { AgentLoadOptions } from \"@signap/signals-web\";\nimport { SignalsClient } from \"./client\";\nimport { SIGNALS_CLIENT } from \"./tokens\";\n\n/**\n * Registers a single app-wide SignalsAgent — the idiomatic standalone-Angular\n * entry point, analogous to React's `<SignalsProvider>` and Vue's\n * `app.use(SignalsPlugin)`:\n *\n * ```ts\n * bootstrapApplication(AppComponent, {\n * providers: [\n * provideSignals({ apiKey: \"pk_live_...\", endpoint: \"https://ingest.example\" }),\n * ],\n * });\n * ```\n *\n * SSR-safe: the factory injects `PLATFORM_ID` and only lets the client call\n * `SignalsAgent.load()` in a browser, so a server render / prerender never loads\n * the agent.\n *\n * `opts` is snapshotted here at bootstrap; to swap apiKey/endpoint, re-bootstrap\n * (same snapshot-at-init contract as the React/Vue wrappers).\n */\nexport function provideSignals(\n opts: AgentLoadOptions,\n config: SignalsConfig = {},\n): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: SIGNALS_CLIENT,\n useFactory: () => {\n const browser = isPlatformBrowser(inject(PLATFORM_ID));\n const { enabled } = config;\n if (!enabled) return new SignalsClient(opts, browser);\n const client = new SignalsClient(opts, browser, !untracked(enabled));\n // The factory runs in an injection context, so the effect lives as long as\n // the environment injector. The client's writes are untracked so they are\n // not writes \"inside\" the effect (Angular 17/18 reject those by default).\n effect(() => {\n const on = enabled();\n untracked(() => (on ? client.release() : client.hold()));\n });\n return client;\n },\n },\n ]);\n}\n\n/** Second argument of `provideSignals()`. */\nexport interface SignalsConfig {\n /**\n * While this signal is `false` the client is held: it does not load the agent, so\n * the SDK registers no event listener, runs no collector and sends no request, and\n * `injectVisitorData()` returns `isHeld() === true`. When it becomes `true` the\n * agent loads and the first identify runs, as if `enabled` had never been set.\n * When it goes back to `false` the SDK's interaction listeners stop (page-wide, for\n * this copy of the SDK) and any identify result still in flight is discarded; a\n * request already sent is not recalled, and nothing already received is deleted.\n * Omitted = never held.\n */\n enabled?: Signal<boolean>;\n}\n","import { InjectionToken } from \"@angular/core\";\nimport type { SignalsClient } from \"./client\";\n\n/**\n * DI token the app-wide {@link SignalsClient} is registered under (by\n * `provideSignals()`) and that `injectVisitorData()` resolves. Most consumers\n * don't inject this directly — call `injectVisitorData()`. It is exported for\n * advanced use (e.g. `inject(SIGNALS_CLIENT).agent()` to read the raw agent).\n */\nexport const SIGNALS_CLIENT = new InjectionToken<SignalsClient>(\"signap.signals.client\");\n","import {\n DestroyRef,\n type Signal,\n assertInInjectionContext,\n computed,\n inject,\n signal,\n} from \"@angular/core\";\nimport type { IdentifyOptions, IdentifyResult } from \"@signap/signals-web\";\nimport { SIGNALS_CLIENT } from \"./tokens\";\n\nexport type InjectVisitorDataOptions = IdentifyOptions;\n\nexport interface InjectVisitorDataResult {\n /**\n * Resolved visitor from `agent.identify()`. Under ADR §A2 the call is a single\n * synchronous round-trip — `visitorId`/`confidence`/`identifiedAt` arrive\n * together, no polling. A `Signal`, so read it as `data()` (in a template or a\n * `computed`/`effect`).\n */\n data: Signal<IdentifyResult | undefined>;\n /** True between call time and the identify() resolution (or `error`). False while held. */\n isLoading: Signal<boolean>;\n /**\n * True while `provideSignals(opts, { enabled })` is held. Nothing is loaded or sent,\n * `data()` is `undefined` and `refetch()` does nothing. Always false without `enabled`.\n */\n isHeld: Signal<boolean>;\n error: Signal<Error | null>;\n /** Re-run `agent.identify()` (e.g. from a button click). */\n refetch: () => Promise<void>;\n}\n\n/**\n * Auto-identifies once the provider's agent is ready, then exposes the resolved\n * visitor as Angular signals — the framework-native analog of React's\n * `useVisitorData()` hook and Vue's `useVisitorData()` composable.\n *\n * Must be called in an injection context (a component/directive constructor or a\n * field initializer, or inside `runInInjectionContext`) below `provideSignals()`.\n */\nexport function injectVisitorData(opts?: InjectVisitorDataOptions): InjectVisitorDataResult {\n assertInInjectionContext(injectVisitorData);\n\n const client = inject(SIGNALS_CLIENT, { optional: true });\n if (!client) {\n throw new Error(\n \"[@signap/signals-angular] injectVisitorData() requires provideSignals() in the application (or component) providers\",\n );\n }\n\n const data = signal<IdentifyResult | undefined>(undefined);\n const error = signal<Error | null>(null);\n // Loading until the agent (or a load error) settles — unless held.\n const isLoading = signal(\n !client.held() && client.agent() === null && client.loadError() === null,\n );\n const isHeld = computed(() => client.held());\n\n // Supersede token so a slow response from a call that refetch() replaced (or a\n // response arriving after teardown) is discarded.\n let token = { cancelled: false };\n let started = false;\n const onChange = (): void => {\n if (client.held()) {\n // Held (issue 2894): discard any identify in flight and any previous result, and\n // re-arm so a later release identifies once, like a fresh call.\n token.cancelled = true;\n started = false;\n data.set(undefined);\n error.set(null);\n isLoading.set(false);\n return;\n }\n if (started) return;\n if (client.agent() === null && client.loadError() === null) return; // not settled yet\n started = true;\n void run();\n };\n const unsubscribe = client.onChange(onChange);\n inject(DestroyRef).onDestroy(() => {\n token.cancelled = true;\n unsubscribe();\n });\n\n const run = async (): Promise<void> => {\n const agent = client.agent();\n if (!agent) {\n const loadError = client.loadError();\n if (loadError) {\n error.set(loadError);\n isLoading.set(false);\n }\n return;\n }\n token.cancelled = true;\n const my = { cancelled: false };\n token = my;\n isLoading.set(true);\n error.set(null);\n data.set(undefined);\n try {\n const res = await agent.identify(opts);\n if (my.cancelled) return;\n data.set(res);\n } catch (err) {\n if (my.cancelled) return;\n error.set(err instanceof Error ? err : new Error(String(err)));\n } finally {\n if (!my.cancelled) isLoading.set(false);\n }\n };\n\n // Auto-run once per release, after load() settles. The client calls onChange on\n // every settle and hold; the first look is deferred to a microtask (as the old\n // `ready.then` was) so the returned signals are wired up before the first run().\n void Promise.resolve().then(onChange);\n\n return { data, isLoading, isHeld, error, refetch: run };\n}\n"],"mappings":"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","names":["inject","signal","inject","signal"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signap/signals-angular",
|
|
3
|
-
"version": "2.2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Angular provider + inject() helper wrapper for @signap/signals-web",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"@angular/core": ">=17 <21"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@signap/signals-web": "2.3.0
|
|
25
|
+
"@signap/signals-web": "^2.3.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@angular/common": "^19.0.0",
|