@encorekit/web-sdk 2.0.4 → 2.0.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/README.md +112 -40
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/umd/encore.min.js +1 -1
- package/dist/umd/encore.min.js.map +1 -1
- package/embed/README.md +82 -367
- package/examples/ios-webview/EncoreURLBuilder.swift +179 -61
- package/examples/ios-webview/README.md +75 -354
- package/examples/ios-webview/SimpleEncoreView.swift +49 -36
- package/examples/ios-webview/SimpleExample.swift +30 -86
- package/package.json +2 -7
- package/types/api/APIClient.d.ts +3 -1
- package/types/api/APIClient.d.ts.map +1 -1
- package/types/api/errorReportLimits.d.ts +50 -0
- package/types/api/errorReportLimits.d.ts.map +1 -0
- package/types/api/generated/types.d.ts +54 -7
- package/types/api/generated/types.d.ts.map +1 -1
- package/types/core/Encore.d.ts +25 -2
- package/types/core/Encore.d.ts.map +1 -1
- package/types/core/PlacementBuilder.d.ts +14 -2
- package/types/core/PlacementBuilder.d.ts.map +1 -1
- package/types/core/TransactionStore.d.ts +57 -13
- package/types/core/TransactionStore.d.ts.map +1 -1
- package/types/core/VerificationPoller.d.ts.map +1 -1
- package/types/core/claimBehavior.d.ts +28 -0
- package/types/core/claimBehavior.d.ts.map +1 -0
- package/types/core/errorContext.d.ts +18 -0
- package/types/core/errorContext.d.ts.map +1 -0
- package/types/core/errorType.d.ts +59 -0
- package/types/core/errorType.d.ts.map +1 -0
- package/types/core/hostDisplay.d.ts +50 -0
- package/types/core/hostDisplay.d.ts.map +1 -0
- package/types/core/version.d.ts +1 -1
- package/types/types.d.ts +146 -1
- package/types/types.d.ts.map +1 -1
- package/types/ui/OfferModal.d.ts +20 -0
- package/types/ui/OfferModal.d.ts.map +1 -1
- package/types/ui/PrimerScreen.d.ts +18 -11
- package/types/ui/PrimerScreen.d.ts.map +1 -1
- package/types/ui/RedemptionScreen.d.ts +15 -19
- package/types/ui/RedemptionScreen.d.ts.map +1 -1
- package/types/ui/RewardActions.d.ts +26 -0
- package/types/ui/RewardActions.d.ts.map +1 -1
- package/types/ui/brandComponents.d.ts.map +1 -1
- package/types/ui/styles.d.ts.map +1 -1
- package/types/utils/logger.d.ts +11 -1
- package/types/utils/logger.d.ts.map +1 -1
- package/embed/index.html +0 -57
- package/embed/styles.css +0 -154
- package/examples/ios-webview/EncoreWebViewBridge.swift +0 -419
- package/examples/ios-webview/ExampleViewController.swift +0 -233
package/README.md
CHANGED
|
@@ -27,19 +27,20 @@ Or via CDN:
|
|
|
27
27
|
|
|
28
28
|
## Upgrading to 2.0.2
|
|
29
29
|
|
|
30
|
-
2.0.2
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
2.0.2 removed the deferred redemption flow and the layout system. The layout
|
|
31
|
+
removal stands. **The deferred-flow removal was a regression and is undone in
|
|
32
|
+
2.0.5** — see *Deferred redemption* below; if you are on 2.0.2–2.0.4 and never set
|
|
33
|
+
`redemptionMode`, that is the section that concerns you.
|
|
33
34
|
|
|
34
35
|
| Removed | Replacement |
|
|
35
36
|
|:--------|:------------|
|
|
36
|
-
| `redemptionMode` — on `configure()` and on placement options | None.
|
|
37
|
-
| `Encore.redeem()`, `placement(id).redeem()`, `getPendingTransaction()`, `RedeemOptions`, `RedeemResult` |
|
|
37
|
+
| `redemptionMode` — on `configure()` and on placement options | None, and none is coming. **2.0.5 restores the deferred flow itself** (see below), but what a claim does with the handoff is data on the variant, never a publisher option. |
|
|
38
|
+
| `Encore.redeem()`, `placement(id).redeem()`, `getPendingTransaction()`, `RedeemOptions`, `RedeemResult` | **Restored in 2.0.5** — see *Deferred redemption* below. |
|
|
38
39
|
| `layout: 'list' \| 'thankYou'` | `useCase` — `'reduceChurn'` renders the offer carousel, `'rewardUsers'` the reward composition. |
|
|
39
40
|
| `header.title`, `header.subtitle` | `headline` / `subheadline` on `PlacementOptions`. |
|
|
40
41
|
| `header.icon` | `statusIcon` on `PlacementOptions` — **the default flips from `'success'` to `'none'`**, so the check no longer renders unless you ask for it. |
|
|
41
42
|
| `offerContext`, `footer`, `receipt` / `ShowOptions`, `Receipt` | None. Render any other copy yourself. |
|
|
42
|
-
| `display` / `HostDisplay` | None
|
|
43
|
+
| `display` / `HostDisplay` | None as a publisher option. The deferred screens restored in 2.0.5 resolve the host's name, trial period and icon from remote config instead, and degrade when they are unset. |
|
|
43
44
|
| `.headline()` / `.subheadline()` builder steps | The flat `headline` / `subheadline` options. `.useCase()` stays. |
|
|
44
45
|
| `'churn-intervention'` / `'post-action-reward'` | `'reduceChurn'` / `'rewardUsers'` — a rename of the TypeScript symbols only; the values sent over HTTP are unchanged. |
|
|
45
46
|
|
|
@@ -64,10 +65,14 @@ Encore.identify('user-123', {
|
|
|
64
65
|
// Present a placement — defaults to the 'reduceChurn' use case
|
|
65
66
|
const result = await Encore.show();
|
|
66
67
|
if (result.status === 'claimed') {
|
|
67
|
-
// The user claimed an offer
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
68
|
+
// The user claimed an offer. Keep result.transactionId — it is how the
|
|
69
|
+
// conversion is traced back to this user and app, and what
|
|
70
|
+
// waitForVerification() / didGrant() take.
|
|
71
|
+
//
|
|
72
|
+
// WHEN the advertiser opens depends on the variant's claim behaviour:
|
|
73
|
+
// 'reduceChurn' defaults to deferred, so the claim is banked and you call
|
|
74
|
+
// Encore.redeem() after your own flow. 'rewardUsers' defaults to immediate,
|
|
75
|
+
// so the advertiser already opened in a new tab.
|
|
71
76
|
} else {
|
|
72
77
|
// 'dismissed' (with an optional reason) or 'unavailable' — nothing was claimed.
|
|
73
78
|
console.log('Nothing claimed:', result);
|
|
@@ -344,6 +349,60 @@ and it has several causes — so do not read it as "not enabled":
|
|
|
344
349
|
See [the use-case serving model](https://github.com/EncoreKit/web-sdk/blob/main/docs/architecture/use-case-serving.md)
|
|
345
350
|
for the full design note.
|
|
346
351
|
|
|
352
|
+
## Deferred redemption
|
|
353
|
+
|
|
354
|
+
A claim and the advertiser handoff are two moments, and a placement decides
|
|
355
|
+
whether they are the same moment. That is `claimBehavior`, and it is **data on the
|
|
356
|
+
variant** — Encore configures it per placement. There is no publisher option for
|
|
357
|
+
it, deliberately: `redemptionMode` was one until 2.0.2, and its default flipping
|
|
358
|
+
underneath integrations that never set it is exactly the failure this replaces.
|
|
359
|
+
|
|
360
|
+
| Behaviour | On claim | `redeem()` | Survives a reload |
|
|
361
|
+
|:----------|:---------|:-----------|:------------------|
|
|
362
|
+
| `immediate` | Redirects to the advertiser inside the tap gesture. Nothing is stored. | Resolves `unavailable` | n/a |
|
|
363
|
+
| `deferred` | Banks the claim and shows a primer. No redirect. | Presents the claim, redirects on the CTA, marks it redeemed | Yes |
|
|
364
|
+
| `deferredWhileAlive` | Holds the claim in memory for this page. | Presents it while the page lives | No, by design |
|
|
365
|
+
|
|
366
|
+
Defaults: **`reduceChurn` is `deferred`**, **`rewardUsers` is `immediate`**.
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
// A deferred placement: show() resolves once the user passes the primer.
|
|
370
|
+
// Nothing has opened yet — the claim is banked.
|
|
371
|
+
const result = await Encore.placement('cancel_flow').show();
|
|
372
|
+
|
|
373
|
+
// ...your own cancellation flow runs, possibly on a later page load...
|
|
374
|
+
|
|
375
|
+
// Phase 2. Presents the claim and sends the user to the advertiser.
|
|
376
|
+
const redemption = await Encore.placement('cancel_flow').redeem();
|
|
377
|
+
if (redemption.status === 'redeemed') {
|
|
378
|
+
// The advertiser opened inside the CTA gesture and the claim is consumed.
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
`redeem()` never rejects. It resolves:
|
|
383
|
+
|
|
384
|
+
| Result | Meaning |
|
|
385
|
+
|:-------|:--------|
|
|
386
|
+
| `{ status: 'redeemed' }` | The handoff happened and the claim is consumed. |
|
|
387
|
+
| `{ status: 'dismissed', reason? }` | The user closed the sheet, or an internal failure occurred (`reason.type === 'error'`). The claim is untouched, so retrying later is safe. |
|
|
388
|
+
| `{ status: 'unavailable' }` | Nothing is pending. Strictly that — never an error in disguise. Every `immediate` placement always resolves this. |
|
|
389
|
+
|
|
390
|
+
To render your own CTA instead of the SDK's, read the pending claim first:
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
const pending = Encore.getPendingTransaction('cancel_flow'); // or () for the latest
|
|
394
|
+
if (pending) showMyOwnBanner(pending.offer.advertiserName);
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Scoping is strict: `placement(id).redeem()` and `getPendingTransaction(id)` resolve
|
|
398
|
+
only the claim made at that placement and never cross to another's. The bare forms
|
|
399
|
+
take the most recent claim for the user.
|
|
400
|
+
|
|
401
|
+
> **The provisional grant follows the handoff, not the claim.** On a deferred
|
|
402
|
+
> placement it is sent when `redeem()` performs the redirect, not when the user
|
|
403
|
+
> taps Claim. Verified access is server-side either way — check it on your backend,
|
|
404
|
+
> never from the client.
|
|
405
|
+
|
|
347
406
|
## Browser Support
|
|
348
407
|
|
|
349
408
|
- Chrome/Edge (last 2 versions)
|
|
@@ -351,22 +410,22 @@ for the full design note.
|
|
|
351
410
|
- Safari (last 2 versions)
|
|
352
411
|
- iOS Safari 15+
|
|
353
412
|
|
|
354
|
-
## iOS
|
|
413
|
+
## iOS WebView Integration
|
|
355
414
|
|
|
356
|
-
The **simplest possible** integration
|
|
415
|
+
The **simplest possible** integration: build a URL and open it in a `WKWebView`.
|
|
416
|
+
No SDK, no dependencies, nothing to implement. Encore hosts the offer page and
|
|
417
|
+
its query string is the entire configuration surface.
|
|
357
418
|
|
|
358
|
-
### Quick Start
|
|
419
|
+
### Quick Start
|
|
359
420
|
|
|
360
421
|
```swift
|
|
361
|
-
// 1. Build URL
|
|
362
|
-
let url = EncoreURL.build(apiKey: "
|
|
422
|
+
// 1. Build the URL (copy EncoreURLBuilder.swift into your project)
|
|
423
|
+
guard let url = EncoreURL.build(apiKey: "pk_live_...", userId: "user_123") else { return }
|
|
363
424
|
|
|
364
|
-
// 2. Create
|
|
425
|
+
// 2. Create a webview and load it
|
|
365
426
|
let webView = WKWebView(frame: view.bounds)
|
|
366
427
|
view.addSubview(webView)
|
|
367
428
|
webView.load(URLRequest(url: url))
|
|
368
|
-
|
|
369
|
-
// That's it! 🎉
|
|
370
429
|
```
|
|
371
430
|
|
|
372
431
|
### Complete Example
|
|
@@ -375,24 +434,26 @@ webView.load(URLRequest(url: url))
|
|
|
375
434
|
import UIKit
|
|
376
435
|
import WebKit
|
|
377
436
|
|
|
378
|
-
// Copy EncoreURLBuilder.swift to your project (one file!)
|
|
379
437
|
func showEncoreOffer() {
|
|
380
|
-
let url = EncoreURL.build(
|
|
438
|
+
guard let url = EncoreURL.build(
|
|
381
439
|
apiKey: "pk_live_...",
|
|
382
440
|
userId: currentUser.id,
|
|
383
441
|
attributes: [
|
|
384
|
-
"
|
|
385
|
-
"
|
|
442
|
+
"subscriptionTier": currentUser.tier,
|
|
443
|
+
"countryCode": currentUser.countryCode
|
|
386
444
|
]
|
|
387
|
-
)
|
|
388
|
-
|
|
445
|
+
) else { return }
|
|
446
|
+
|
|
389
447
|
let webView = WKWebView(frame: view.bounds)
|
|
390
|
-
webView.backgroundColor = .clear
|
|
391
448
|
view.addSubview(webView)
|
|
392
449
|
webView.load(URLRequest(url: url))
|
|
393
450
|
}
|
|
394
451
|
```
|
|
395
452
|
|
|
453
|
+
Targeting attributes are coarse by design. `email`, `phoneNumber`, names,
|
|
454
|
+
`dateOfBirth`, `gender` and precise location are refused, because a query string
|
|
455
|
+
is written to webview history and to hosting access logs.
|
|
456
|
+
|
|
396
457
|
### SwiftUI Version
|
|
397
458
|
|
|
398
459
|
```swift
|
|
@@ -410,30 +471,41 @@ struct MyView: View {
|
|
|
410
471
|
|
|
411
472
|
### What You Get
|
|
412
473
|
|
|
413
|
-
-
|
|
414
|
-
-
|
|
415
|
-
-
|
|
416
|
-
-
|
|
417
|
-
- ✅ **Works everywhere** - UIKit, SwiftUI, any iOS app
|
|
474
|
+
- **Zero dependencies**: one Swift file to copy
|
|
475
|
+
- **Three lines of code**: build URL, create webview, load
|
|
476
|
+
- **Works everywhere**: UIKit, SwiftUI, and any other platform with a webview
|
|
477
|
+
- **Nothing to maintain**: the page is hosted, so the contract is the URL
|
|
418
478
|
|
|
419
|
-
###
|
|
420
|
-
- **[EncoreURLBuilder.swift](examples/ios-webview/EncoreURLBuilder.swift)** - URL builder (copy this!)
|
|
421
|
-
- **[SimpleExample.swift](examples/ios-webview/SimpleExample.swift)** - Working examples
|
|
479
|
+
### Knowing when a user earned something
|
|
422
480
|
|
|
423
|
-
|
|
481
|
+
The page does not call your app back. There is no native bridge on any platform.
|
|
482
|
+
Your backend asks Encore instead:
|
|
424
483
|
|
|
425
|
-
|
|
484
|
+
```
|
|
485
|
+
GET https://api.encorekit.com/encore/publisher/sdk/v1/entitlements/server?userId=<userId>
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
authenticated with your API key plus an HMAC signature you compute with your
|
|
489
|
+
API secret. The secret itself is never sent.
|
|
490
|
+
|
|
491
|
+
### Documentation
|
|
492
|
+
|
|
493
|
+
- **[EncoreURLBuilder.swift](examples/ios-webview/EncoreURLBuilder.swift)** - URL builder (copy this!)
|
|
494
|
+
- **[SimpleExample.swift](examples/ios-webview/SimpleExample.swift)** - UIKit usage
|
|
495
|
+
- **[SimpleEncoreView.swift](examples/ios-webview/SimpleEncoreView.swift)** - SwiftUI wrapper
|
|
496
|
+
- **[examples/ios-webview/README.md](examples/ios-webview/README.md)** - full parameter reference
|
|
426
497
|
|
|
427
498
|
### When to Use
|
|
428
499
|
|
|
429
|
-
| Feature |
|
|
430
|
-
|
|
500
|
+
| Feature | Hosted page (URL) | [Native iOS SDK](https://github.com/EncoreKit/swift) |
|
|
501
|
+
|---------|-------------------|------------|
|
|
431
502
|
| Integration | 3 lines | ~50 lines |
|
|
432
503
|
| Dependencies | None | Requires SPM |
|
|
433
|
-
|
|
|
434
|
-
|
|
|
504
|
+
| Native entitlement callbacks | No, poll from your backend | Yes |
|
|
505
|
+
| StoreKit and offline handling | No | Yes |
|
|
435
506
|
|
|
436
|
-
|
|
507
|
+
Use the hosted page when you want the smallest integration. Use the native SDK
|
|
508
|
+
when you need entitlement callbacks inside the app.
|
|
437
509
|
|
|
438
510
|
## Documentation
|
|
439
511
|
|