@encorekit/web-sdk 1.1.21 → 2.0.1

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.
Files changed (52) hide show
  1. package/README.md +190 -46
  2. package/dist/cjs/index.cjs +1 -1
  3. package/dist/cjs/index.cjs.map +1 -1
  4. package/dist/esm/index.js +1 -1
  5. package/dist/esm/index.js.map +1 -1
  6. package/dist/umd/encore.min.js +1 -1
  7. package/dist/umd/encore.min.js.map +1 -1
  8. package/package.json +4 -3
  9. package/types/analytics/AnalyticsClient.d.ts +11 -1
  10. package/types/analytics/AnalyticsClient.d.ts.map +1 -1
  11. package/types/analytics/events.d.ts +192 -19
  12. package/types/analytics/events.d.ts.map +1 -1
  13. package/types/analytics/transport.d.ts.map +1 -1
  14. package/types/api/APIClient.d.ts +36 -7
  15. package/types/api/APIClient.d.ts.map +1 -1
  16. package/types/api/generated/types.d.ts +548 -1
  17. package/types/api/generated/types.d.ts.map +1 -1
  18. package/types/api/models.d.ts +8 -3
  19. package/types/api/models.d.ts.map +1 -1
  20. package/types/core/Configuration.d.ts +0 -4
  21. package/types/core/Configuration.d.ts.map +1 -1
  22. package/types/core/Encore.d.ts +8 -33
  23. package/types/core/Encore.d.ts.map +1 -1
  24. package/types/core/OfferManager.d.ts +50 -9
  25. package/types/core/OfferManager.d.ts.map +1 -1
  26. package/types/core/PlacementBuilder.d.ts +11 -12
  27. package/types/core/PlacementBuilder.d.ts.map +1 -1
  28. package/types/core/useCase.d.ts +62 -0
  29. package/types/core/useCase.d.ts.map +1 -0
  30. package/types/core/version.d.ts +1 -1
  31. package/types/core/version.d.ts.map +1 -1
  32. package/types/types.d.ts +92 -223
  33. package/types/types.d.ts.map +1 -1
  34. package/types/ui/OfferCarousel.d.ts +81 -0
  35. package/types/ui/OfferCarousel.d.ts.map +1 -1
  36. package/types/ui/RewardActions.d.ts +60 -0
  37. package/types/ui/RewardActions.d.ts.map +1 -0
  38. package/types/ui/RewardCard.d.ts +49 -0
  39. package/types/ui/RewardCard.d.ts.map +1 -0
  40. package/types/ui/RewardConfetti.d.ts +46 -0
  41. package/types/ui/RewardConfetti.d.ts.map +1 -0
  42. package/types/ui/RewardLayout.d.ts +55 -0
  43. package/types/ui/RewardLayout.d.ts.map +1 -0
  44. package/types/ui/ThankYouLayout.d.ts +11 -0
  45. package/types/ui/ThankYouLayout.d.ts.map +1 -1
  46. package/types/ui/brandTint.d.ts +61 -0
  47. package/types/ui/brandTint.d.ts.map +1 -0
  48. package/types/ui/styles.d.ts +86 -0
  49. package/types/ui/styles.d.ts.map +1 -1
  50. package/types/utils/text.d.ts +24 -0
  51. package/types/utils/text.d.ts.map +1 -0
  52. package/examples/react/.env +0 -1
package/README.md CHANGED
@@ -5,6 +5,7 @@ A JavaScript/TypeScript SDK that enables web applications to display targeted of
5
5
  ## Features
6
6
 
7
7
  - 🎯 **Targeted Offer Presentation** - Display offers at critical moments (cancellation flows, paywalls, onboarding)
8
+ - 🎁 **Use Cases** - `reduceChurn` (the default) or `rewardUsers` — see [Use Cases](#use-cases)
8
9
  - 🔐 **Entitlement Management** - Two-tier system (provisional + verified) for instant UX and reliable billing
9
10
  - 📱 **Framework Agnostic** - Works with React, Vue, Angular, Svelte, vanilla JavaScript, or any web framework
10
11
  - 🎨 **Responsive UI** - Beautiful, accessible modal interface that works on desktop and mobile
@@ -15,7 +16,7 @@ A JavaScript/TypeScript SDK that enables web applications to display targeted of
15
16
  ## Installation
16
17
 
17
18
  ```bash
18
- npm install @encorekit/web-sdk
19
+ npm install @encorekit/web-sdk@3.0.0
19
20
  ```
20
21
 
21
22
  Or via CDN:
@@ -24,6 +25,24 @@ Or via CDN:
24
25
  <script src="https://encorekit.com/encore.min.js"></script>
25
26
  ```
26
27
 
28
+ ## Upgrading to 3.0.0
29
+
30
+ 3.0.0 removes the deferred redemption flow and the layout system. Claiming an
31
+ offer now always redirects to the advertiser immediately, and the composition is
32
+ derived from the placement's use case.
33
+
34
+ | Removed | Replacement |
35
+ |:--------|:------------|
36
+ | `redemptionMode` — on `configure()` and on placement options | None. Redirect-on-claim is the only flow. |
37
+ | `Encore.redeem()`, `placement(id).redeem()`, `getPendingTransaction()`, `RedeemOptions`, `RedeemResult` | None. There is no pending transaction to redeem. |
38
+ | `layout: 'list' \| 'thankYou'` | `useCase` — `'reduceChurn'` renders the offer carousel, `'rewardUsers'` the reward composition. |
39
+ | `header.title`, `header.subtitle` | `headline` / `subheadline` on `PlacementOptions`. |
40
+ | `header.icon` | `statusIcon` on `PlacementOptions` — **the default flips from `'success'` to `'none'`**, so the check no longer renders unless you ask for it. |
41
+ | `offerContext`, `footer`, `receipt` / `ShowOptions`, `Receipt` | None. Render any other copy yourself. |
42
+ | `display` / `HostDisplay` | None. It only ever fed the deferred-flow screens; on the immediate path it resolved to nothing. |
43
+ | `.headline()` / `.subheadline()` builder steps | The flat `headline` / `subheadline` options. `.useCase()` stays. |
44
+ | `'churn-intervention'` / `'post-action-reward'` | `'reduceChurn'` / `'rewardUsers'` — a rename of the TypeScript symbols only; the values sent over HTTP are unchanged. |
45
+
27
46
  ## Quick Start
28
47
 
29
48
  ```typescript
@@ -42,65 +61,190 @@ Encore.identify('user-123', {
42
61
  subscriptionTier: 'free'
43
62
  });
44
63
 
45
- // Present an offer
46
- const result = await Encore.presentOffer();
47
- if (result.granted) {
48
- console.log('User granted entitlement:', result.entitlement);
49
- console.log('Offer granted:', result.entitlement);
50
- // Handle user completed Encore offer
64
+ // Present a placement — defaults to the 'reduceChurn' use case
65
+ const result = await Encore.show();
66
+ if (result.status === 'claimed') {
67
+ // The user claimed an offer and the advertiser opened in a new tab.
68
+ // Keep result.transactionId — it is how the conversion is traced back to
69
+ // this user and app, and what waitForVerification() / didGrant() take.
70
+ // The SDK renders nothing after a claim — show your own confirmation here.
51
71
  } else {
52
- console.log('User declined:', result.reason);
72
+ // 'dismissed' (with an optional reason) or 'unavailable' — nothing was claimed.
73
+ console.log('Nothing claimed:', result);
53
74
  }
54
75
 
55
76
  // Check entitlement on YOUR server (not client-side)
56
77
  // See: https://docs.encorekit.com/server-side-validation
57
78
  ```
58
79
 
59
- ## Layout Templates
80
+ ## Placements
60
81
 
61
- `show()` renders the default `'list'` layout. For post-purchase surfaces (a payment
62
- success page), use the `'thankYou'` layout — a complete success view: status check,
63
- your payment copy, the offer carousel, and an optional trust footer.
82
+ A **placement** is one presentation of offers. There are two call forms, and they
83
+ take the same options:
64
84
 
65
85
  ```typescript
66
- const placement = Encore.placement('post-purchase', {
67
- layout: 'thankYou',
86
+ // Builder form
87
+ const result = await Encore.placement('milestone_reached', {
88
+ headline: 'Nice work, Sam!',
89
+ subheadline: 'That is your fifth week running',
68
90
  appearance: { accentColor: '#0A6' },
69
- header: { title: 'Payment Successful' }, // constants — plain strings
70
- offerContext: { title: 'A little thank you, just for you.' },
71
- footer: { text: '🔒 Secured by Acme' },
72
- });
91
+ })
92
+ .useCase('rewardUsers')
93
+ .onNotGranted((reason) => console.log('closed without a claim', reason))
94
+ .onLoadingStateChange((isLoading) => setSpinner(isLoading))
95
+ .show(); // takes no arguments
96
+
97
+ // Flat form — callbacks first, then the same options, then the placement id
98
+ await Encore.show(
99
+ { onGranted, onNotGranted, onError },
100
+ { useCase: 'rewardUsers', headline: 'Nice work, Sam!' },
101
+ 'milestone_reached'
102
+ );
103
+ ```
104
+
105
+ `Encore.show()`'s first argument is **callbacks only** — `onGranted`,
106
+ `onNotGranted`, `onError`. Every other option is a placement option.
107
+
108
+ ### `PlacementOptions`
109
+
110
+ | Option | Type | Meaning |
111
+ |:-------|:-----|:--------|
112
+ | `useCase` | `'reduceChurn' \| 'rewardUsers'` | Why the placement presents, and which composition it renders. Defaults to `'reduceChurn'`. |
113
+ | `headline` | `string` | Per-call override of the sheet's primary heading. |
114
+ | `subheadline` | `string` | Per-call override of the line under it. |
115
+ | `appearance` | `{ backgroundColor?, textColor?, accentColor? }` | Colour overrides for the sheet. |
116
+ | `statusIcon` | `'success' \| 'none'` | Status icon above the headline. Defaults to `'none'` — nothing renders unless you ask for `'success'`, which draws a green-circle check. Only `'rewardUsers'` has a status block; `'reduceChurn'` ignores it. The default matches iOS and Android, whose reward variant renders no checkmark. |
117
+
118
+ ### The result
119
+
120
+ `show()` **never rejects**. It resolves one of three statuses:
121
+
122
+ | Status | Meaning |
123
+ |:-------|:--------|
124
+ | `{ status: 'claimed', offerId, campaignId, advertiserName, transactionId? }` | The user claimed an offer. The advertiser opened in a new tab inside the tap gesture and the SDK renders nothing afterwards — the host owns that moment, so render your own confirmation if you want one. The payload mirrors iOS's and Android's `ClaimedOffer` field-for-field. **`transactionId` carries the attribution** — it is how a completion that lands days later is traced back to this user and app, and it is the argument `didGrant()` and `waitForVerification()` take. It is optional because the transaction write can fail while the claim genuinely happened; the SDK logs a warning when that occurs. |
125
+ | `{ status: 'dismissed', reason? }` | The sheet closed without a claim. Failures land here too, carrying `reason.error`. |
126
+ | `{ status: 'unavailable' }` | Nothing was presented — no eligible offers, or the surface did not resolve. |
127
+
128
+ `claimed` deliberately makes no assertion about advertiser conversion: that is
129
+ asynchronous and server-authoritative. Verify entitlements and conversions
130
+ server-side, by user, never from the client.
73
131
 
74
- // Per transaction, pass the typed receipt — the SDK renders
75
- // "You paid $9.99 for Pro Plan" under the header:
76
- await placement.show({ receipt: { amount: '$9.99', purpose: 'Pro Plan' } });
132
+ ## Use Cases
133
+
134
+ A placement's **use case** says *why* it presents — and it is what selects the
135
+ composition. It defaults to `'reduceChurn'`.
136
+
137
+ | Value | Composition | Meaning |
138
+ |:------|:------------|:--------|
139
+ | `'reduceChurn'` | Offer carousel | The default. A user is cancelling or downgrading; the offer is an alternative to leaving. |
140
+ | `'rewardUsers'` | Reward composition | The user completed something worth celebrating — a purchase, a milestone, a streak, a level. The offer is a gift. |
141
+
142
+ It is both a plain placement option and a builder step — use whichever reads
143
+ better:
144
+
145
+ ```typescript
146
+ Encore.placement('milestone_reached', { useCase: 'rewardUsers' });
147
+ Encore.placement('milestone_reached').useCase('rewardUsers');
148
+ ```
149
+
150
+ > **The wire values are unchanged.** `reduceChurn` / `rewardUsers` are the
151
+ > TypeScript symbols. Over HTTP the SDK still sends `churn-intervention` /
152
+ > `post-action-reward` (`GET /config?useCase=`, `POST /transactions`, and the
153
+ > `use_case` analytics property), so existing dashboards and attribution are
154
+ > unaffected by the rename.
155
+
156
+ ### Supplying your own headline and subheadline
157
+
158
+ Pass your copy at presentation time. This is the intended way to set it — you
159
+ know what the user just did, so you can name it:
160
+
161
+ ```typescript
162
+ await Encore.placement('milestone_reached', {
163
+ useCase: 'rewardUsers',
164
+ headline: 'Nice work, Sam!',
165
+ subheadline: 'That is your fifth week running',
166
+ }).show();
77
167
  ```
78
168
 
79
- Notes:
80
-
81
- - `header` is the sheet's top heading block in EVERY layout — the modal
82
- title/subtitle in `'list'` (a local override of the remote copy) and the
83
- payment-status block in `'thankYou'`. One name, one meaning.
84
- - `receipt` values are rendered verbatim as plain text (pre-format currency yourself),
85
- never sent to Encore servers, and override `header.subtitle` when provided.
86
- - `offerContext` is `'thankYou'`-only priming copy ABOVE the offers (why they're
87
- being shown), not any offer's own copy — `'list'` ignores it. When the active
88
- offer's campaign carries a `perk` line (e.g. "Enjoy 30 days of Apple Music free")
89
- the perk takes the subtitle slot and follows the carousel as it pages;
90
- `offerContext.subtitle` is the static fallback.
91
- - Need a custom confirmation line? Skip `receipt` and pass your own pre-built string as
92
- `header.subtitle` — placement options are per-call.
93
- - Unknown `layout` values warn and fall back to `'list'`, so older SDK versions degrade
94
- gracefully when layout selection later moves to remote config.
95
- - In `'thankYou'` the carousel pages one card per view at every breakpoint, with round
96
- chevron navigation bubbles when there is more than one offer (hidden for a single offer).
97
- - Pair with `redemptionMode: 'immediate'` for post-purchase: Claim opens the advertiser
98
- in a new tab within the tap gesture and `show()` resolves `{ status: 'claimed' }` —
99
- the SDK renders nothing after a claim. The host owns that moment: render your own
100
- confirmation if you want one, keyed off the result. The result deliberately makes no
101
- claim about advertiser conversion (it is asynchronous and server-authoritative —
102
- verify entitlements/conversions server-side by user, never from the client).
103
- `show()` never rejects: failures resolve as `{ status: 'dismissed', reason }`.
169
+ Both are **per-call overrides**: they beat whatever copy Encore resolved for the
170
+ surface, and they work on either use case. `headline` fills the sheet's primary
171
+ heading and `subheadline` the line under it.
172
+
173
+ > **Blank falls through — it does not clear the copy.** A blank or
174
+ > whitespace-only value is treated as "not supplied" and falls through to the
175
+ > copy Encore resolved, so passing `''` is never a way to blank the sheet. Omit
176
+ > the option instead. This keeps a value that is empty because an interpolation
177
+ > came back empty — a `headline` built from a template literal whose `name` was
178
+ > undefined — from rendering a headless sheet.
179
+
180
+ If you supply neither, the sheet falls back to Encore's resolved copy for the
181
+ surface, and finally to a built-in string, so the heading is never empty.
182
+
183
+ ### `rewardUsers` grants no entitlement
184
+
185
+ A reward claim is a gift the brand funds directly, not a subscription unlock. So
186
+ `show()` resolves `{ status: 'claimed' }` and **no entitlement is granted** — on
187
+ this path the SDK skips the automatic provisional grant it performs for
188
+ `'reduceChurn'`, so no grant signal reaches Encore. Nothing was promised, so
189
+ nothing is unlocked. (Reward activation is confirmed server-side via the
190
+ offer-completed webhook, an entirely separate mechanism.)
191
+
192
+ This is why `claimed` is its own result and not folded into `dismissed`: a user
193
+ who successfully claimed a reward must never run your "user declined" branch.
194
+
195
+ ### Unrecognised values fail closed
196
+
197
+ Anything that is not one of the two values above is rejected before anything is
198
+ presented, rather than quietly falling back to the churn surface. `show()` still
199
+ never rejects — it resolves:
200
+
201
+ ```typescript
202
+ { status: 'dismissed', reason: { type: 'error', error: { code: 'INVALID_REQUEST', … } } }
203
+ ```
204
+
205
+ If you call `Encore.show({ onError, onNotGranted }, { useCase })`, both callbacks
206
+ fire. Via `Encore.placement(...)` only the builder's `.onNotGranted()` exists —
207
+ there is no `.onError()` on the builder — so read the returned `reason.error`.
208
+
209
+ ### Enablement affects SPEED, not availability
210
+
211
+ Requesting a use case is all it takes to be served it. There is no setup call,
212
+ and you do not need Encore to switch anything on first:
213
+
214
+ | Your app | What happens |
215
+ |:---------|:-------------|
216
+ | Enabled for the use case | Its config arrives with the one `/config` fetch at `configure()` — the first presentation is **instant** |
217
+ | Not enabled | The SDK fetches that one surface on the **first** present, then caches it — **slower once**, instant thereafter |
218
+
219
+ When the second row applies the SDK logs a one-off `[INTEGRATION]` notice saying
220
+ the surface was not prefetched. It is a **speed hint, not an error** — nothing has
221
+ failed. Ask Encore to enable the use case for your app to remove the first-present
222
+ round trip.
223
+
224
+ > **Encore can still switch a surface off.** An explicit kill switch is the one
225
+ > thing that blocks serving, and it is deliberate — it is how a live surface is
226
+ > stopped without a deploy. A switched-off surface no-ops exactly as below.
227
+
228
+ ### When nothing presents
229
+
230
+ A use case that cannot be served resolves `{ status: 'unavailable' }`, reports
231
+ `sdk_offer_presentation_failed` with reason `no_template`, and **never** falls back
232
+ to another use case's offers or copy. It does not throw.
233
+
234
+ `unavailable` is the same result you get when there are simply no eligible offers,
235
+ and it has several causes — so do not read it as "not enabled":
236
+
237
+ - Encore has switched the surface off, or no variant was eligible for it;
238
+ - the `/config` response had not arrived within ~2.5s of the call, or failed;
239
+ - the on-demand fetch for the surface failed.
240
+
241
+ > ⚠️ **This is silent by default.** The explanatory log is emitted at `info`, and
242
+ > every log level is gated behind `logLevel: 'debug'` — which is not the default.
243
+ > When integrating a reward placement, configure `logLevel: 'debug'` and confirm
244
+ > you see an actual presentation. A clean console is not evidence that it worked.
245
+
246
+ See [the use-case serving model](https://github.com/EncoreKit/web-sdk/blob/main/docs/architecture/use-case-serving.md)
247
+ for the full design note.
104
248
 
105
249
  ## Browser Support
106
250