@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.
Files changed (53) hide show
  1. package/README.md +112 -40
  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/embed/README.md +82 -367
  9. package/examples/ios-webview/EncoreURLBuilder.swift +179 -61
  10. package/examples/ios-webview/README.md +75 -354
  11. package/examples/ios-webview/SimpleEncoreView.swift +49 -36
  12. package/examples/ios-webview/SimpleExample.swift +30 -86
  13. package/package.json +2 -7
  14. package/types/api/APIClient.d.ts +3 -1
  15. package/types/api/APIClient.d.ts.map +1 -1
  16. package/types/api/errorReportLimits.d.ts +50 -0
  17. package/types/api/errorReportLimits.d.ts.map +1 -0
  18. package/types/api/generated/types.d.ts +54 -7
  19. package/types/api/generated/types.d.ts.map +1 -1
  20. package/types/core/Encore.d.ts +25 -2
  21. package/types/core/Encore.d.ts.map +1 -1
  22. package/types/core/PlacementBuilder.d.ts +14 -2
  23. package/types/core/PlacementBuilder.d.ts.map +1 -1
  24. package/types/core/TransactionStore.d.ts +57 -13
  25. package/types/core/TransactionStore.d.ts.map +1 -1
  26. package/types/core/VerificationPoller.d.ts.map +1 -1
  27. package/types/core/claimBehavior.d.ts +28 -0
  28. package/types/core/claimBehavior.d.ts.map +1 -0
  29. package/types/core/errorContext.d.ts +18 -0
  30. package/types/core/errorContext.d.ts.map +1 -0
  31. package/types/core/errorType.d.ts +59 -0
  32. package/types/core/errorType.d.ts.map +1 -0
  33. package/types/core/hostDisplay.d.ts +50 -0
  34. package/types/core/hostDisplay.d.ts.map +1 -0
  35. package/types/core/version.d.ts +1 -1
  36. package/types/types.d.ts +146 -1
  37. package/types/types.d.ts.map +1 -1
  38. package/types/ui/OfferModal.d.ts +20 -0
  39. package/types/ui/OfferModal.d.ts.map +1 -1
  40. package/types/ui/PrimerScreen.d.ts +18 -11
  41. package/types/ui/PrimerScreen.d.ts.map +1 -1
  42. package/types/ui/RedemptionScreen.d.ts +15 -19
  43. package/types/ui/RedemptionScreen.d.ts.map +1 -1
  44. package/types/ui/RewardActions.d.ts +26 -0
  45. package/types/ui/RewardActions.d.ts.map +1 -1
  46. package/types/ui/brandComponents.d.ts.map +1 -1
  47. package/types/ui/styles.d.ts.map +1 -1
  48. package/types/utils/logger.d.ts +11 -1
  49. package/types/utils/logger.d.ts.map +1 -1
  50. package/embed/index.html +0 -57
  51. package/embed/styles.css +0 -154
  52. package/examples/ios-webview/EncoreWebViewBridge.swift +0 -419
  53. 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 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.
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. Redirect-on-claim is the only flow. |
37
- | `Encore.redeem()`, `placement(id).redeem()`, `getPendingTransaction()`, `RedeemOptions`, `RedeemResult` | None. There is no pending transaction to redeem. |
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. It only ever fed the deferred-flow screens; on the immediate path it resolved to nothing. |
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 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.
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 HTML Tag Integration
413
+ ## iOS WebView Integration
355
414
 
356
- The **simplest possible** integration - just build a URL and open it in a WKWebView. No SDK, no dependencies, no complexity.
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 (3 Lines of Code!)
419
+ ### Quick Start
359
420
 
360
421
  ```swift
361
- // 1. Build URL
362
- let url = EncoreURL.build(apiKey: "your_key", userId: "user_123")!
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 WebView and load it
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
- "email": currentUser.email,
385
- "subscriptionTier": currentUser.tier
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
- - ✅ **Zero dependencies** - Just one Swift file
414
- - ✅ **3 lines of code** - Build URL, create WebView, load
415
- - ✅ **Auto-initialization** - Embed handles everything
416
- - ✅ **Full features** - Offers, entitlements, tracking
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
- ### Documentation
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
- ### Advanced: With Callbacks
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
- Need callbacks when offers complete? Use the [full bridge integration](examples/ios-webview/README.md).
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 | Simple (URL) | [Native iOS SDK](https://github.com/encore/swift-sdk) |
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
- | Callbacks | No | Yes |
434
- | Works? | ✅ | ✅ |
504
+ | Native entitlement callbacks | No, poll from your backend | Yes |
505
+ | StoreKit and offline handling | No | Yes |
435
506
 
436
- **Use simple integration** for 99% of cases. Only need the full bridge if you require granular callbacks.
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