@revturbine/sdk 0.11.4 → 0.11.6
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/CHANGELOG.md +131 -0
- package/dist/headless.d.ts +176 -17
- package/dist/headless.js +2 -2
- package/dist/headless.js.map +1 -1
- package/dist/index.d.ts +176 -17
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/types/web-sdk/controllers.d.ts +44 -4
- package/dist/types/web-sdk/controllers.d.ts.map +1 -1
- package/dist/types/web-sdk/customer-side.d.ts +124 -8
- package/dist/types/web-sdk/customer-side.d.ts.map +1 -1
- package/dist/types/web-sdk/react/usePlacement.d.ts.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -47,6 +47,137 @@ also require a changelog entry.
|
|
|
47
47
|
|
|
48
48
|
---
|
|
49
49
|
|
|
50
|
+
## 0.11.6
|
|
51
|
+
|
|
52
|
+
### `sdk.convert()` reloads the UserContext, so a mounted slot re-decides (BL-0004)
|
|
53
|
+
|
|
54
|
+
**What changed.** `sdk.convert(outputId)` used to record the conversion and stop
|
|
55
|
+
there. Conversion writes **no** cooldown and **no** permanent retirement — by
|
|
56
|
+
design (Kent's plan 254 D-9 ruling; the `convert` row of the spec's interaction
|
|
57
|
+
table) — so eligibility is a function of the user's CURRENT plan and targeting.
|
|
58
|
+
That left a real gap: a host that called the public convert path while the
|
|
59
|
+
placement was mounted saw the upgrade banner stay on screen until the slot next
|
|
60
|
+
happened to resolve a decision. The documented workaround was a manual
|
|
61
|
+
`refresh()` or the component's `ctaComplete()`. Plan 236 TASK-13 recorded it as
|
|
62
|
+
an open product call rather than asserting it away.
|
|
63
|
+
|
|
64
|
+
Kent ruled on it, 2026-09-25: *"Converting should trigger a reloading of
|
|
65
|
+
UserContext with the new plan/billing state the user has converted to."*
|
|
66
|
+
|
|
67
|
+
So `convert()` now, after recording the interaction:
|
|
68
|
+
|
|
69
|
+
1. **Optimistically** moves the held plan when the converted output's CTA names
|
|
70
|
+
one (`cta_path.plan_handle` — e.g. `open_checkout` with `config.purchase`).
|
|
71
|
+
The Playbook supplies the plan's display name when it knows it.
|
|
72
|
+
2. Notifies user-context subscribers **either way** — a CTA that names no plan
|
|
73
|
+
still re-decides mounted surfaces.
|
|
74
|
+
3. **Then** refreshes from `GET /api/sdk/client-context`, which overlays the
|
|
75
|
+
RevTurbine-authoritative trial / billing-health / plan fields. Best-effort and
|
|
76
|
+
never throwing, exactly as before; a no-op when no client-session minter is
|
|
77
|
+
configured.
|
|
78
|
+
|
|
79
|
+
Optimistic-first because billing truth arrives by Stripe webhook: a
|
|
80
|
+
`client-context` read that races the webhook still reports the OLD plan, and
|
|
81
|
+
waiting for it would leave the upgrade prompt up at the exact moment the user
|
|
82
|
+
paid. For the same reason, while a conversion is pending a server plan **equal to
|
|
83
|
+
the plan converted away from** is dropped (plan fields only — trial and
|
|
84
|
+
billing-health always apply). Any other value clears the pending state and
|
|
85
|
+
applies, so the suppression cannot outlive the lag it covers.
|
|
86
|
+
|
|
87
|
+
Mounted `PlacementController`s re-decide and mounted `EntitlementGate`s re-check
|
|
88
|
+
through `watchUserContext()`. The React bindings attach it for you
|
|
89
|
+
(`usePlacement`, and so every surface slot built on it; `useEntitlement` /
|
|
90
|
+
`useCan` already did), and `SdkSession.placement()` / `.entitlement()` attach it
|
|
91
|
+
for headless consumers. `dispose()` drops it. `PlacementController.ctaComplete()`
|
|
92
|
+
is unchanged.
|
|
93
|
+
|
|
94
|
+
**What did NOT change.** `convert()`'s signature. Conversion still writes no
|
|
95
|
+
suppression and no retirement: back on the old plan, the placement is eligible
|
|
96
|
+
again. The optimistic plan move is a session-local overlay, not a new source of
|
|
97
|
+
plan truth — a host that re-declares `plan_handle` on mount wins, and that is the
|
|
98
|
+
intended precedence.
|
|
99
|
+
|
|
100
|
+
**Landed in.** `0.11.6` (BL-0004; refs BL-0177, BL-0179 for the re-decide
|
|
101
|
+
mechanism this reuses).
|
|
102
|
+
|
|
103
|
+
**Fail-closed in.** `0.11.6` — same version. Nothing was silently tolerated
|
|
104
|
+
before: the old behaviour was a placement that stayed visible, which is why the
|
|
105
|
+
limitation was documented rather than dated.
|
|
106
|
+
|
|
107
|
+
**Proving test.** `web-sdk/convert-reloads-user-context.test.ts` (headless: the
|
|
108
|
+
mounted controller's decision goes invisible, a failed conversion moves nothing,
|
|
109
|
+
a CTA with no plan still notifies, and the stale-server-plan guard both
|
|
110
|
+
suppresses and releases); `web-sdk/react/convert-reloads-user-context.test.tsx`
|
|
111
|
+
(a mounted `usePlacement` banner disappears in place and a mounted `useCan` gate
|
|
112
|
+
flips denied → granted, with no remount); `e2e/journey.spec.ts` BL-0004 legs (a
|
|
113
|
+
real browser: the converted placement leaves the screen with **no** page
|
|
114
|
+
refresh, and the optimistic overlay does not outrank the host's declaration).
|
|
115
|
+
|
|
116
|
+
**Ports.** No change. The Python and Rust SDKs are synchronous-with-config and
|
|
117
|
+
have no mounted surfaces to notify; they expose no `convert()` and no
|
|
118
|
+
UserContext-reload API, so there is no port-side behaviour to match. The
|
|
119
|
+
decision-level contract they do share — a context carrying the new plan yields
|
|
120
|
+
decisions for that plan — is unchanged, so no parity fixture moves.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
## 0.11.5
|
|
124
|
+
|
|
125
|
+
### `registerSurfaceSlot()` no longer writes to the control plane (BL-0197)
|
|
126
|
+
|
|
127
|
+
**What changed.** Outside `local_only`, `registerSurfaceSlot()` used to mirror
|
|
128
|
+
every slot to the server: `upsertSurfaceSlot()` PUT a legacy surface-slot body
|
|
129
|
+
(`{name, slug, slot_type, status, targeting_rules, content, priority,
|
|
130
|
+
metadata}`) to `/api/placements/<slot id>`, then POSTed it to `/api/placements`
|
|
131
|
+
on the 404. `/api/placements` is RevTurbine's **authored-config** CRUD; it
|
|
132
|
+
validates against `PlacementSchema`, which requires `handle` and `category`, so
|
|
133
|
+
that body could only ever be rejected. In production every non-`local_only`
|
|
134
|
+
slot registration failed with `surface_slot_create_failed:422` and the React
|
|
135
|
+
runtime turned the rejection into a `slot_error`
|
|
136
|
+
(`dogfood_event_explorer`, 2026-09-24 18:36 UTC, request
|
|
137
|
+
`030ccf51-d74b-4d55-bf4e-2a929a4ab297`).
|
|
138
|
+
|
|
139
|
+
The write was also wrong in principle. On a same-origin integration the
|
|
140
|
+
signed-in user's session cookie authenticates the request, so a body the route
|
|
141
|
+
*did* accept would have created a draft placement in the tenant's Playbook just
|
|
142
|
+
from loading a gated page. Only the 422 prevented that.
|
|
143
|
+
|
|
144
|
+
`registerSurfaceSlot()` is now **client-local**: it records the slot in the
|
|
145
|
+
in-process registry and resolves, with no network write of any kind. A write
|
|
146
|
+
happens only when you configure `endpointOverrides.surfaceSlots` (or
|
|
147
|
+
`custom_endpoints.surfaceSlots`) to point at a slot-inventory service you run —
|
|
148
|
+
the same shape `persistPlacementTypes()` already uses. There is **no fallback
|
|
149
|
+
to `/api/placements`** under any configuration.
|
|
150
|
+
|
|
151
|
+
Nothing is lost by removing it. Surface-slot discovery is **ingestion-driven**:
|
|
152
|
+
the SDK already emits `slot_evaluated` / `slot_filled` / `slot_empty` /
|
|
153
|
+
`slot_suppressed` / `slot_error` and the `placement_*` lifecycle through
|
|
154
|
+
`/api/track`, carrying `surface_slot_id`, `slot_name`, `template_ids`,
|
|
155
|
+
`surface_type`, `category`, `decision_source` and `reason_codes`, and the
|
|
156
|
+
pipeline derives discovered slots from that telemetry. `SurfaceSlotSchema` is a
|
|
157
|
+
runtime-source (DISCOVERED) schema with `first_seen` / `last_seen` — it was
|
|
158
|
+
never meant to be written by a browser.
|
|
159
|
+
|
|
160
|
+
**Landed in.** `0.11.5` (BL-0197).
|
|
161
|
+
|
|
162
|
+
**Fail-closed in.** `0.11.5` — same version. The removed path had no silent
|
|
163
|
+
mode: before this release it threw `surface_slot_create_failed:<status>` on
|
|
164
|
+
every default-transport registration, so there is no window where the old
|
|
165
|
+
behaviour was quietly tolerated. An integration that genuinely relied on the
|
|
166
|
+
write must set `endpointOverrides.surfaceSlots`; one that did not was already
|
|
167
|
+
seeing `slot_error`.
|
|
168
|
+
|
|
169
|
+
**Proving test.** `web-sdk/surface-slot-registration-no-write.test.ts` — the
|
|
170
|
+
default transport performs zero writes (fetch spy, no `/api/placements` call)
|
|
171
|
+
and resolves even when the route replays the production 404/422; the override
|
|
172
|
+
path still PUTs, and falls back to POST on the override base, never on
|
|
173
|
+
`/api/placements`. `e2e/journey.transport.spec.ts` no longer stubs
|
|
174
|
+
`/api/placements` with a 200 (the stub that hid this): it records and aborts,
|
|
175
|
+
and `assertNoPlacementWrites()` fails the test if anything calls it.
|
|
176
|
+
|
|
177
|
+
**Ports.** No change — the Python and Rust SDKs never had a slot-registration
|
|
178
|
+
write.
|
|
179
|
+
|
|
180
|
+
---
|
|
50
181
|
## 0.11.4
|
|
51
182
|
|
|
52
183
|
### The treatment-interaction wire record now carries `rule_handle` (BL-0200)
|
package/dist/headless.d.ts
CHANGED
|
@@ -17171,11 +17171,14 @@ interface RevTurbineTreatmentInteractionInput {
|
|
|
17171
17171
|
* `PlacementOutput.rule_id` — projected onto the `placement_interaction`
|
|
17172
17172
|
* clickstream payload as `rule_handle` (BL-0182).
|
|
17173
17173
|
*
|
|
17174
|
-
*
|
|
17175
|
-
*
|
|
17176
|
-
*
|
|
17177
|
-
* carries payload fields inside a JSON column
|
|
17178
|
-
*
|
|
17174
|
+
* It reaches the wire `TreatmentInteractionInputSchema` too, as of BL-0200.
|
|
17175
|
+
* It deliberately did not under BL-0182: that path is column-shaped and the
|
|
17176
|
+
* rule would have needed a datasource migration, while the clickstream
|
|
17177
|
+
* carries payload fields inside a JSON column and was readable at once.
|
|
17178
|
+
* `placement_exposure_attribution.rule_handle` exists now (web ledger 015),
|
|
17179
|
+
* so the base exposure row records the rule the exposure was DECIDED by
|
|
17180
|
+
* rather than waiting for attribution to reconstruct one when a conversion
|
|
17181
|
+
* lands — which is what makes presentation-grain rule cuts servable.
|
|
17179
17182
|
*
|
|
17180
17183
|
* Omitted, never sent empty, when no decision is in scope — a bare
|
|
17181
17184
|
* `trackTreatmentInteraction` caller supplies no rule, and absence is what
|
|
@@ -20481,6 +20484,16 @@ interface RevTurbineEndpointOverrides {
|
|
|
20481
20484
|
*/
|
|
20482
20485
|
legacyInteractions?: string;
|
|
20483
20486
|
placementTypes: string;
|
|
20487
|
+
/**
|
|
20488
|
+
* Optional sink for registered surface slots.
|
|
20489
|
+
*
|
|
20490
|
+
* RevTurbine hosts **no default route** for this verb (BL-0197). Slot
|
|
20491
|
+
* registration is client-local and discovery is ingestion-driven — the
|
|
20492
|
+
* pipeline derives discovered slots from the `slot_*` telemetry the SDK
|
|
20493
|
+
* already emits through `/api/track`. Set this only when you run your own
|
|
20494
|
+
* slot-inventory service; unset, `registerSurfaceSlot()` performs no
|
|
20495
|
+
* network write.
|
|
20496
|
+
*/
|
|
20484
20497
|
surfaceSlots: string;
|
|
20485
20498
|
}
|
|
20486
20499
|
/** Client-side clickstream batching policy (plan 95 TASK-6). */
|
|
@@ -21137,6 +21150,12 @@ declare class RevTurbineCustomerSdk {
|
|
|
21137
21150
|
* redundant segment re-evaluation.
|
|
21138
21151
|
*/
|
|
21139
21152
|
private lastServerContextHash?;
|
|
21153
|
+
/**
|
|
21154
|
+
* The plan move an optimistic `convert()` made ahead of the server (BL-0004),
|
|
21155
|
+
* held only until a `client-context` read stops reporting the old plan. See
|
|
21156
|
+
* {@link dropStalePostConversionPlan}.
|
|
21157
|
+
*/
|
|
21158
|
+
private pendingConversionPlan?;
|
|
21140
21159
|
private usageBalances;
|
|
21141
21160
|
private localDecisionsByPlacementId;
|
|
21142
21161
|
private localPlacementsByLookupKey;
|
|
@@ -21934,6 +21953,30 @@ declare class RevTurbineCustomerSdk {
|
|
|
21934
21953
|
* @public
|
|
21935
21954
|
*/
|
|
21936
21955
|
registerPlacement(config: RevTurbinePlacementConfig): Promise<string>;
|
|
21956
|
+
/**
|
|
21957
|
+
* Mirror a registered surface slot to an explicitly configured backend.
|
|
21958
|
+
*
|
|
21959
|
+
* **Requires an explicit `surfaceSlots` endpoint override** (BL-0197).
|
|
21960
|
+
* There is no default route for this verb and there never was a correct
|
|
21961
|
+
* one: the previous default pointed at `/api/placements`, RevTurbine's
|
|
21962
|
+
* *authored-config* CRUD. That route validates against `PlacementSchema`
|
|
21963
|
+
* (which requires `handle` and `category`), so the legacy surface-slot body
|
|
21964
|
+
* this method sends could only ever be rejected — in production every
|
|
21965
|
+
* non-`local_only` registration failed with
|
|
21966
|
+
* `surface_slot_create_failed:422` and the React runtime turned that into a
|
|
21967
|
+
* `slot_error`.
|
|
21968
|
+
*
|
|
21969
|
+
* It was also wrong in principle. On a same-origin integration the signed-in
|
|
21970
|
+
* user's session cookie authenticates the request, so a body the route *did*
|
|
21971
|
+
* accept would have let merely loading a gated page create a draft placement
|
|
21972
|
+
* in the tenant's Playbook. Only the 422 prevented that.
|
|
21973
|
+
*
|
|
21974
|
+
* Surface-slot **discovery is ingestion-driven**: the SDK emits its
|
|
21975
|
+
* `slot_*` / `placement_*` telemetry through `/api/track` and the pipeline
|
|
21976
|
+
* derives discovered slots from it. Registration itself is client-local.
|
|
21977
|
+
*
|
|
21978
|
+
* @internal
|
|
21979
|
+
*/
|
|
21937
21980
|
private upsertSurfaceSlot;
|
|
21938
21981
|
/**
|
|
21939
21982
|
* Build the internal placement record for a surface-slot config.
|
|
@@ -21956,6 +21999,18 @@ declare class RevTurbineCustomerSdk {
|
|
|
21956
21999
|
* @internal
|
|
21957
22000
|
*/
|
|
21958
22001
|
private commitSurfaceSlotRecord;
|
|
22002
|
+
/**
|
|
22003
|
+
* Register a surface slot with the local runtime and return its placement id.
|
|
22004
|
+
*
|
|
22005
|
+
* **Client-local by default** (BL-0197): this performs no network write. The
|
|
22006
|
+
* slot is recorded in the in-process registry, and the pipeline discovers it
|
|
22007
|
+
* from the `slot_*` / `placement_*` telemetry the SDK emits through
|
|
22008
|
+
* `/api/track`. A write happens only when `custom_endpoints.surfaceSlots`
|
|
22009
|
+
* points at a slot-inventory service you run; there is no fallback to
|
|
22010
|
+
* `/api/placements`.
|
|
22011
|
+
*
|
|
22012
|
+
* @public
|
|
22013
|
+
*/
|
|
21959
22014
|
registerSurfaceSlot(config: RevTurbineSurfaceSlotConfig): Promise<string>;
|
|
21960
22015
|
/**
|
|
21961
22016
|
* Persist authored placement types to a configured backend.
|
|
@@ -22245,12 +22300,16 @@ declare class RevTurbineCustomerSdk {
|
|
|
22245
22300
|
/** Map the client-safe context response into a UserContext patch (plan 157). */
|
|
22246
22301
|
private mapClientSafeContext;
|
|
22247
22302
|
/**
|
|
22248
|
-
* Apply a
|
|
22249
|
-
*
|
|
22250
|
-
*
|
|
22251
|
-
*
|
|
22303
|
+
* Apply a UserContext patch — mirrors setUserContext but accepts a partial.
|
|
22304
|
+
* RevTurbine-authoritative fields (trial, billing signals, server plan)
|
|
22305
|
+
* overlay the held context; undefined fields are skipped by the merge, so
|
|
22306
|
+
* app-set values are never nulled.
|
|
22307
|
+
*
|
|
22308
|
+
* Two callers: the server client-context fetch (plan 157) and the optimistic
|
|
22309
|
+
* post-conversion plan move (BL-0004). Both notify subscribers, which is what
|
|
22310
|
+
* makes mounted slots and gates re-resolve.
|
|
22252
22311
|
*/
|
|
22253
|
-
private
|
|
22312
|
+
private applyUserContextPatch;
|
|
22254
22313
|
/**
|
|
22255
22314
|
* Record that a placement was rendered to the user. Plan 43 TASK-9.
|
|
22256
22315
|
*
|
|
@@ -22427,13 +22486,73 @@ declare class RevTurbineCustomerSdk {
|
|
|
22427
22486
|
*/
|
|
22428
22487
|
snooze(outputId: string, seconds?: number): Promise<void>;
|
|
22429
22488
|
/**
|
|
22430
|
-
* Record a confirmed CTA completion
|
|
22431
|
-
*
|
|
22432
|
-
*
|
|
22489
|
+
* Record a confirmed CTA completion, then reload the UserContext so every
|
|
22490
|
+
* mounted surface re-decides against the plan the user just converted to.
|
|
22491
|
+
*
|
|
22492
|
+
* Conversion is **not** a suppression: it writes no cooldown and no permanent
|
|
22493
|
+
* retirement (spec §"interaction" table; Kent's plan 254 D-9 ruling). What
|
|
22494
|
+
* makes a converted upgrade banner leave the screen is the user no longer
|
|
22495
|
+
* matching it — so a conversion that did not refresh the context left the
|
|
22496
|
+
* placement mounted until something else happened to re-resolve it. Kent,
|
|
22497
|
+
* 2026-09-25 (BL-0004): *"Converting should trigger a reloading of UserContext
|
|
22498
|
+
* with the new plan/billing state the user has converted to."*
|
|
22499
|
+
*
|
|
22500
|
+
* Ordering, and why it is this way round:
|
|
22501
|
+
*
|
|
22502
|
+
* 1. The interaction is recorded (analytics + attribution), as before.
|
|
22503
|
+
* 2. **Optimistically**, locally: when the converted output's CTA names a
|
|
22504
|
+
* target plan (`cta_path.plan_handle`), the held context moves to that plan
|
|
22505
|
+
* immediately. Billing truth arrives by Stripe webhook, so
|
|
22506
|
+
* `GET /api/sdk/client-context` can still report the OLD plan for seconds
|
|
22507
|
+
* after a successful checkout — waiting for it would leave the upgrade
|
|
22508
|
+
* prompt on screen exactly when the user has just paid.
|
|
22509
|
+
* 3. Subscribers are notified either way — a conversion whose CTA names no
|
|
22510
|
+
* plan still re-decides mounted slots and re-checks mounted gates, which is
|
|
22511
|
+
* what turns a plan change the host applied itself into a re-render.
|
|
22512
|
+
* 4. **Then** the server refresh (`fetchClientContext()`), which overlays the
|
|
22513
|
+
* RevTurbine-authoritative trial / billing-health / plan fields and
|
|
22514
|
+
* notifies again if it changed anything. It is best-effort and never
|
|
22515
|
+
* throws, so awaiting it cannot fail a conversion; with no client-session
|
|
22516
|
+
* minter configured it is a no-op and the optimistic state stands.
|
|
22517
|
+
*
|
|
22518
|
+
* Mounted `PlacementController`s re-decide and mounted `EntitlementGate`s
|
|
22519
|
+
* re-check through their `watchUserContext()` subscriptions — which the React
|
|
22520
|
+
* bindings attach for you. Re-resolution emits the normal lifecycle events
|
|
22521
|
+
* once per final decision; the transient states emit nothing.
|
|
22522
|
+
* `PlacementController.ctaComplete()` is unchanged and remains the
|
|
22523
|
+
* component-level path.
|
|
22433
22524
|
*
|
|
22434
22525
|
* @public
|
|
22435
22526
|
*/
|
|
22436
22527
|
convert(outputId: string): Promise<void>;
|
|
22528
|
+
/**
|
|
22529
|
+
* The BL-0004 post-conversion context reload: optimistic local plan move,
|
|
22530
|
+
* then the server refresh. See {@link convert} for the ordering rationale.
|
|
22531
|
+
*/
|
|
22532
|
+
private reloadUserContextAfterConversion;
|
|
22533
|
+
/**
|
|
22534
|
+
* Keep a stale server plan from undoing an optimistic conversion (BL-0004).
|
|
22535
|
+
*
|
|
22536
|
+
* Billing truth arrives by Stripe webhook, so a `client-context` read that
|
|
22537
|
+
* races the webhook reports the PRE-conversion plan — and the server plan
|
|
22538
|
+
* deliberately overlays the app-supplied one (plan 179 TASK-1), so applying
|
|
22539
|
+
* that read verbatim would put the upgrade prompt straight back on screen
|
|
22540
|
+
* seconds after the user paid.
|
|
22541
|
+
*
|
|
22542
|
+
* So while a conversion is pending, a server plan equal to the plan converted
|
|
22543
|
+
* AWAY from is dropped — only the plan fields, never the trial or billing-health
|
|
22544
|
+
* signals, which are authoritative regardless. Any other value (the new plan,
|
|
22545
|
+
* or a third one the user was moved to out-of-band) clears the pending state
|
|
22546
|
+
* and applies, so this can suppress at most the one value it was created for
|
|
22547
|
+
* and cannot outlive the lag it exists to cover.
|
|
22548
|
+
*/
|
|
22549
|
+
private dropStalePostConversionPlan;
|
|
22550
|
+
/**
|
|
22551
|
+
* The display name the loaded Playbook gives a plan handle, else the handle.
|
|
22552
|
+
* Keeps an optimistic plan move from putting a raw handle in front of a user
|
|
22553
|
+
* when the Playbook already knows the plan's name.
|
|
22554
|
+
*/
|
|
22555
|
+
private planNameForHandle;
|
|
22437
22556
|
/**
|
|
22438
22557
|
* Emit a canonical trigger event. There is no in-SDK consumer that matches
|
|
22439
22558
|
* on trigger names — the shipped behavior is ingest to `events_clickstream`
|
|
@@ -22897,12 +23016,41 @@ declare class PlacementController {
|
|
|
22897
23016
|
private _awaitingConfig;
|
|
22898
23017
|
private _configRetries;
|
|
22899
23018
|
private _playbookUnsubscribe;
|
|
23019
|
+
private _userContextUnsubscribe;
|
|
22900
23020
|
constructor(sdk: RevTurbineCustomerSdk, options: PlacementControllerOptions);
|
|
22901
23021
|
/**
|
|
22902
|
-
* Stop listening for Playbook loads. Idempotent; call
|
|
22903
|
-
* component unmounts (`usePlacement` does).
|
|
23022
|
+
* Stop listening for Playbook loads and user-context changes. Idempotent; call
|
|
23023
|
+
* it when the owning component unmounts (`usePlacement` does).
|
|
22904
23024
|
*/
|
|
22905
23025
|
dispose(): void;
|
|
23026
|
+
/**
|
|
23027
|
+
* Drop only the Playbook-load subscription — the within-load-cycle teardown
|
|
23028
|
+
* (BL-0177). A mounted controller keeps watching the user context (BL-0004).
|
|
23029
|
+
*/
|
|
23030
|
+
private unsubscribePlaybook;
|
|
23031
|
+
/**
|
|
23032
|
+
* Re-decide whenever the user context changes (BL-0004).
|
|
23033
|
+
*
|
|
23034
|
+
* The counterpart to {@link EntitlementGate.watchUserContext}, and the reason
|
|
23035
|
+
* `sdk.convert()` now removes a converted placement in place: conversion writes
|
|
23036
|
+
* no suppression — eligibility is a function of the user's CURRENT plan and
|
|
23037
|
+
* targeting — so the only thing that can take a converted upgrade banner off
|
|
23038
|
+
* screen is a re-decide against the reloaded context. Without this the
|
|
23039
|
+
* placement stayed mounted until something else happened to re-resolve it, and
|
|
23040
|
+
* the host had to call `refresh()` itself or use the component's
|
|
23041
|
+
* `ctaComplete()` (the limitation plan 236 TASK-13 recorded).
|
|
23042
|
+
*
|
|
23043
|
+
* Only re-decides a controller that has already resolved something: before the
|
|
23044
|
+
* first `load()` there is nothing on screen to be stale, and firing then would
|
|
23045
|
+
* turn every startup `identify()` into a duplicate decision.
|
|
23046
|
+
*
|
|
23047
|
+
* Calls `load()` rather than `refresh()` on purpose — `refresh()` clears
|
|
23048
|
+
* impression/exposure tracking, so a context change would re-credit a
|
|
23049
|
+
* presentation the user never saw again.
|
|
23050
|
+
*
|
|
23051
|
+
* Returns an unsubscribe function; {@link dispose} also drops it.
|
|
23052
|
+
*/
|
|
23053
|
+
watchUserContext(): () => void;
|
|
22906
23054
|
/**
|
|
22907
23055
|
* Is this decision a race against a Playbook load rather than a verdict?
|
|
22908
23056
|
*
|
|
@@ -23120,12 +23268,18 @@ declare class EntitlementGate {
|
|
|
23120
23268
|
private _awaitedResult;
|
|
23121
23269
|
private _configRetries;
|
|
23122
23270
|
private _playbookUnsubscribe;
|
|
23271
|
+
private _userContextUnsubscribe;
|
|
23123
23272
|
constructor(sdk: RevTurbineCustomerSdk, options: EntitlementGateOptions);
|
|
23124
23273
|
/**
|
|
23125
|
-
* Stop listening for Playbook loads. Idempotent; call
|
|
23126
|
-
* component unmounts (`useEntitlement` does).
|
|
23274
|
+
* Stop listening for Playbook loads and user-context changes. Idempotent; call
|
|
23275
|
+
* it when the owning component unmounts (`useEntitlement` does).
|
|
23127
23276
|
*/
|
|
23128
23277
|
dispose(): void;
|
|
23278
|
+
/**
|
|
23279
|
+
* Drop only the Playbook-load subscription — the within-check-cycle teardown
|
|
23280
|
+
* (BL-0179). A mounted gate keeps watching the user context (BL-0004).
|
|
23281
|
+
*/
|
|
23282
|
+
private unsubscribePlaybook;
|
|
23129
23283
|
/** Current state snapshot. */
|
|
23130
23284
|
get state(): EntitlementGateState;
|
|
23131
23285
|
/** Convenience: `true` when the entitlement is allowed. */
|
|
@@ -23225,8 +23379,13 @@ declare class EntitlementGate {
|
|
|
23225
23379
|
* `check()` there is nothing on screen to be stale, and firing then would
|
|
23226
23380
|
* turn every `identify()` at startup into a redundant evaluation.
|
|
23227
23381
|
*
|
|
23382
|
+
* `sdk.convert()` reloads the UserContext (BL-0004), so this is also what
|
|
23383
|
+
* flips a mounted `useCan` gate to allowed after a conversion.
|
|
23384
|
+
*
|
|
23228
23385
|
* Returns an unsubscribe function. Call it when the gate is discarded —
|
|
23229
23386
|
* without that, a gate outlives its consumer and keeps re-checking.
|
|
23387
|
+
* Idempotent: a second call returns the first subscription rather than
|
|
23388
|
+
* orphaning it, and {@link dispose} drops it too.
|
|
23230
23389
|
*/
|
|
23231
23390
|
watchUserContext(): () => void;
|
|
23232
23391
|
private notify;
|