@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
@@ -1,419 +0,0 @@
1
- //
2
- // EncoreWebViewBridge.swift
3
- // Encore iOS WebView Bridge
4
- //
5
- // A Swift bridge for integrating Encore via WKWebView
6
- // instead of the native iOS SDK.
7
- //
8
-
9
- import Foundation
10
- import WebKit
11
-
12
- /// Entitlement types that can be granted to users
13
- public enum EncoreEntitlementType: String, Codable {
14
- case freeTrial = "free_trial"
15
- case discount = "discount"
16
- case credit = "credit"
17
- }
18
-
19
- /// Entitlement granted to a user
20
- public struct EncoreEntitlement: Codable {
21
- public let type: String
22
- public let value: Int?
23
- public let unit: String?
24
-
25
- public init(type: String, value: Int? = nil, unit: String? = nil) {
26
- self.type = type
27
- self.value = value
28
- self.unit = unit
29
- }
30
- }
31
-
32
- /// Reason why an entitlement was not granted
33
- public enum EncoreDeclineReason: String {
34
- case userClosedModal
35
- case userClickedOutside
36
- case userDeclinedLastOffer
37
- case noOffersAvailable
38
- case error
39
- case unknown
40
- }
41
-
42
- /// Error from Encore SDK
43
- public struct EncoreError: Error, Codable {
44
- public let code: String
45
- public let message: String
46
- public let context: String?
47
-
48
- public init(code: String, message: String, context: String? = nil) {
49
- self.code = code
50
- self.message = message
51
- self.context = context
52
- }
53
- }
54
-
55
- /// Delegate protocol for Encore events
56
- public protocol EncoreWebViewBridgeDelegate: AnyObject {
57
- /// Called when the embed page is ready
58
- func encoreBridgeDidBecomeReady(_ bridge: EncoreWebViewBridge)
59
-
60
- /// Called when SDK is initialized successfully
61
- func encoreBridge(_ bridge: EncoreWebViewBridge, didInitializeWithUserId userId: String)
62
-
63
- /// Called when an entitlement is granted
64
- func encoreBridge(_ bridge: EncoreWebViewBridge, didGrantEntitlement entitlement: EncoreEntitlement)
65
-
66
- /// Called when user declines an offer
67
- func encoreBridge(_ bridge: EncoreWebViewBridge, userDeclinedWithReason reason: EncoreDeclineReason)
68
-
69
- /// Called when an error occurs
70
- func encoreBridge(_ bridge: EncoreWebViewBridge, didEncounterError error: EncoreError)
71
- }
72
-
73
- /// Configuration for Encore SDK
74
- public struct EncoreConfiguration {
75
- public let apiKey: String
76
- public let environment: String
77
- public let userId: String?
78
- public let attributes: [String: String]?
79
- public let logLevel: String?
80
-
81
- public init(
82
- apiKey: String,
83
- environment: String = "production",
84
- userId: String? = nil,
85
- attributes: [String: String]? = nil,
86
- logLevel: String? = nil
87
- ) {
88
- self.apiKey = apiKey
89
- self.environment = environment
90
- self.userId = userId
91
- self.attributes = attributes
92
- self.logLevel = logLevel
93
- }
94
- }
95
-
96
- /// Bridge class for communicating with Encore Web SDK in WKWebView
97
- public class EncoreWebViewBridge: NSObject {
98
-
99
- // MARK: - Properties
100
-
101
- public weak var delegate: EncoreWebViewBridgeDelegate?
102
- public private(set) var webView: WKWebView?
103
-
104
- /// Indicates if the JavaScript embed is ready to receive messages
105
- /// Reset to false when navigation starts (page reload/navigation)
106
- public private(set) var isReady = false
107
-
108
- /// Indicates if the Encore SDK has been initialized in JavaScript
109
- /// Reset to false when navigation starts to handle page reloads properly
110
- public private(set) var isInitialized = false
111
-
112
- public private(set) var currentUserId: String?
113
-
114
- private let embedURL: URL
115
- private let configuration: EncoreConfiguration?
116
-
117
- /// Message queue for storing messages sent before the embed is ready
118
- /// Cleared on each navigation to prevent stale messages from being sent
119
- private var messageQueue: [[String: Any]] = []
120
-
121
- // MARK: - Initialization
122
-
123
- /// Initialize with a custom embed URL
124
- /// - Parameters:
125
- /// - embedURL: The URL of the Encore embed page
126
- /// - configuration: Optional configuration to auto-initialize SDK
127
- public init(embedURL: URL, configuration: EncoreConfiguration? = nil) {
128
- self.embedURL = embedURL
129
- self.configuration = configuration
130
- super.init()
131
- }
132
-
133
- /// Initialize with default Encore embed URL
134
- /// - Parameter configuration: Optional configuration to auto-initialize SDK
135
- public convenience init(configuration: EncoreConfiguration? = nil) {
136
- // Default to production embed URL
137
- let url = URL(string: "https://encorekit.com/embed/")!
138
- self.init(embedURL: url, configuration: configuration)
139
- }
140
-
141
- // MARK: - Setup
142
-
143
- /// Setup and configure the WKWebView
144
- /// Call this method and add the returned webView to your view hierarchy
145
- /// - Returns: Configured WKWebView instance
146
- public func setupWebView() -> WKWebView {
147
- let config = WKWebViewConfiguration()
148
-
149
- // Enable JavaScript
150
- config.preferences.javaScriptEnabled = true
151
-
152
- // Add message handler for Encore messages
153
- let contentController = WKUserContentController()
154
- contentController.add(self, name: "encore")
155
- config.userContentController = contentController
156
-
157
- // Create webView with transparent background
158
- let webView = WKWebView(frame: .zero, configuration: config)
159
- webView.backgroundColor = .clear
160
- webView.isOpaque = false
161
- webView.scrollView.isScrollEnabled = false
162
- webView.navigationDelegate = self
163
-
164
- self.webView = webView
165
-
166
- return webView
167
- }
168
-
169
- /// Load the Encore embed page
170
- public func loadEmbed() {
171
- guard let webView = webView else {
172
- print("[Encore] Error: WebView not setup. Call setupWebView() first.")
173
- return
174
- }
175
-
176
- var urlComponents = URLComponents(url: embedURL, resolvingAgainstBaseURL: true)
177
-
178
- // If configuration provided, add as URL parameters for quick initialization
179
- if let config = configuration {
180
- var queryItems: [URLQueryItem] = [
181
- URLQueryItem(name: "apiKey", value: config.apiKey),
182
- URLQueryItem(name: "environment", value: config.environment)
183
- ]
184
-
185
- if let userId = config.userId {
186
- queryItems.append(URLQueryItem(name: "userId", value: userId))
187
- }
188
-
189
- urlComponents?.queryItems = queryItems
190
- }
191
-
192
- guard let finalURL = urlComponents?.url else {
193
- print("[Encore] Error: Invalid embed URL")
194
- return
195
- }
196
-
197
- let request = URLRequest(url: finalURL)
198
- webView.load(request)
199
- }
200
-
201
- // MARK: - Public Methods
202
-
203
- /// Configure the Encore SDK
204
- /// - Parameter configuration: Configuration object
205
- public func configure(_ configuration: EncoreConfiguration) {
206
- var payload: [String: Any] = [
207
- "apiKey": configuration.apiKey,
208
- "environment": configuration.environment
209
- ]
210
-
211
- if let userId = configuration.userId {
212
- payload["userId"] = userId
213
- }
214
-
215
- if let attributes = configuration.attributes {
216
- payload["attributes"] = attributes
217
- }
218
-
219
- if let logLevel = configuration.logLevel {
220
- payload["logLevel"] = logLevel
221
- }
222
-
223
- sendMessage(type: "configure", payload: payload)
224
- }
225
-
226
- /// Present an offer to the user
227
- public func presentOffer() {
228
- sendMessage(type: "presentOffer", payload: [:])
229
- }
230
-
231
- /// Identify a user
232
- /// - Parameters:
233
- /// - userId: User identifier
234
- /// - attributes: Optional user attributes
235
- public func identify(userId: String, attributes: [String: String]? = nil) {
236
- var payload: [String: Any] = ["userId": userId]
237
- if let attributes = attributes {
238
- payload["attributes"] = attributes
239
- }
240
- sendMessage(type: "identify", payload: payload)
241
- }
242
-
243
- /// Set user attributes
244
- /// - Parameter attributes: User attributes dictionary
245
- public func setUserAttributes(_ attributes: [String: String]) {
246
- sendMessage(type: "setUserAttributes", payload: ["attributes": attributes])
247
- }
248
-
249
- /// Reset the SDK state
250
- /// This sends a reset message to the JS SDK and clears the local state.
251
- /// Note: Page reloads automatically reset state in didStartProvisionalNavigation
252
- public func reset() {
253
- sendMessage(type: "reset", payload: [:])
254
- isInitialized = false
255
- currentUserId = nil
256
- }
257
-
258
- /// Get current SDK state
259
- public func getState() {
260
- sendMessage(type: "getState", payload: [:])
261
- }
262
-
263
- // MARK: - Private Methods
264
-
265
- private func sendMessage(type: String, payload: [String: Any]) {
266
- let message: [String: Any] = [
267
- "type": type,
268
- "payload": payload
269
- ]
270
-
271
- if isReady {
272
- postMessageToWebView(message)
273
- } else {
274
- // Queue messages until webview is ready
275
- messageQueue.append(message)
276
- }
277
- }
278
-
279
- private func postMessageToWebView(_ message: [String: Any]) {
280
- guard let webView = webView else { return }
281
-
282
- do {
283
- let jsonData = try JSONSerialization.data(withJSONObject: message)
284
- let jsonString = String(data: jsonData, encoding: .utf8) ?? "{}"
285
-
286
- let script = """
287
- window.postMessage(\(jsonString), '*');
288
- """
289
-
290
- webView.evaluateJavaScript(script) { _, error in
291
- if let error = error {
292
- print("[Encore] Error posting message: \(error)")
293
- }
294
- }
295
- } catch {
296
- print("[Encore] Error serializing message: \(error)")
297
- }
298
- }
299
-
300
- private func flushMessageQueue() {
301
- while !messageQueue.isEmpty {
302
- let message = messageQueue.removeFirst()
303
- postMessageToWebView(message)
304
- }
305
- }
306
-
307
- private func handleMessage(_ body: Any) {
308
- guard let dict = body as? [String: Any],
309
- let type = dict["type"] as? String else {
310
- return
311
- }
312
-
313
- let payload = dict["payload"] as? [String: Any] ?? [:]
314
-
315
- print("[Encore] Received message: \(type)")
316
-
317
- switch type {
318
- case "encore:ready":
319
- isReady = true
320
- flushMessageQueue()
321
- delegate?.encoreBridgeDidBecomeReady(self)
322
-
323
- case "encore:initialized":
324
- isInitialized = true
325
- if let userId = payload["userId"] as? String {
326
- currentUserId = userId
327
- delegate?.encoreBridge(self, didInitializeWithUserId: userId)
328
- }
329
-
330
- case "encore:entitlementGranted":
331
- if let entitlementData = payload["entitlement"] as? [String: Any],
332
- let jsonData = try? JSONSerialization.data(withJSONObject: entitlementData),
333
- let entitlement = try? JSONDecoder().decode(EncoreEntitlement.self, from: jsonData) {
334
- delegate?.encoreBridge(self, didGrantEntitlement: entitlement)
335
- }
336
-
337
- case "encore:userDeclined":
338
- if let reasonString = payload["reason"] as? String,
339
- let reason = EncoreDeclineReason(rawValue: reasonString) {
340
- delegate?.encoreBridge(self, userDeclinedWithReason: reason)
341
- } else {
342
- delegate?.encoreBridge(self, userDeclinedWithReason: .unknown)
343
- }
344
-
345
- case "encore:error":
346
- if let jsonData = try? JSONSerialization.data(withJSONObject: payload),
347
- let error = try? JSONDecoder().decode(EncoreError.self, from: jsonData) {
348
- delegate?.encoreBridge(self, didEncounterError: error)
349
- }
350
-
351
- case "encore:state":
352
- // Handle state response if needed
353
- print("[Encore] Current state: \(payload)")
354
-
355
- default:
356
- print("[Encore] Unknown message type: \(type)")
357
- }
358
- }
359
- }
360
-
361
- // MARK: - WKScriptMessageHandler
362
-
363
- extension EncoreWebViewBridge: WKScriptMessageHandler {
364
- public func userContentController(
365
- _ userContentController: WKUserContentController,
366
- didReceive message: WKScriptMessage
367
- ) {
368
- guard message.name == "encore" else { return }
369
- handleMessage(message.body)
370
- }
371
- }
372
-
373
- // MARK: - WKNavigationDelegate
374
-
375
- extension EncoreWebViewBridge: WKNavigationDelegate {
376
- public func webView(
377
- _ webView: WKWebView,
378
- didStartProvisionalNavigation navigation: WKNavigation!
379
- ) {
380
- print("[Encore] WebView started loading")
381
-
382
- // Reset state when navigation starts - the JS environment will be reloaded
383
- // This ensures we re-initialize even if the user refreshes or navigates
384
- isReady = false
385
- isInitialized = false
386
- messageQueue.removeAll()
387
-
388
- print("[Encore] State reset for new navigation")
389
- }
390
-
391
- public func webView(
392
- _ webView: WKWebView,
393
- didFinish navigation: WKNavigation!
394
- ) {
395
- print("[Encore] WebView finished loading")
396
-
397
- // If we have initial configuration and haven't initialized yet, do it now
398
- // Note: isInitialized will be false after didStartProvisionalNavigation reset,
399
- // so this will properly re-initialize on page reloads
400
- if !isInitialized, let config = configuration {
401
- print("[Encore] Auto-configuring SDK with stored configuration")
402
- configure(config)
403
- }
404
- }
405
-
406
- public func webView(
407
- _ webView: WKWebView,
408
- didFail navigation: WKNavigation!,
409
- withError error: Error
410
- ) {
411
- print("[Encore] WebView navigation failed: \(error.localizedDescription)")
412
-
413
- // Reset state on navigation failure as well
414
- isReady = false
415
- isInitialized = false
416
- messageQueue.removeAll()
417
- }
418
- }
419
-
@@ -1,233 +0,0 @@
1
- //
2
- // ExampleViewController.swift
3
- // Encore iOS WebView Example
4
- //
5
- // Example implementation showing how to integrate Encore via WKWebView
6
- //
7
-
8
- import UIKit
9
- import WebKit
10
-
11
- class ExampleViewController: UIViewController {
12
-
13
- // MARK: - Properties
14
-
15
- private var encoreBridge: EncoreWebViewBridge!
16
- private var webView: WKWebView!
17
-
18
- // UI Elements
19
- private let statusLabel: UILabel = {
20
- let label = UILabel()
21
- label.textAlignment = .center
22
- label.numberOfLines = 0
23
- label.font = .systemFont(ofSize: 14)
24
- label.textColor = .secondaryLabel
25
- label.text = "Initializing Encore..."
26
- return label
27
- }()
28
-
29
- private let presentOfferButton: UIButton = {
30
- let button = UIButton(type: .system)
31
- button.setTitle("Present Offer", for: .normal)
32
- button.titleLabel?.font = .systemFont(ofSize: 18, weight: .semibold)
33
- button.backgroundColor = .systemBlue
34
- button.setTitleColor(.white, for: .normal)
35
- button.layer.cornerRadius = 12
36
- button.isEnabled = false
37
- return button
38
- }()
39
-
40
- private let resultLabel: UILabel = {
41
- let label = UILabel()
42
- label.textAlignment = .center
43
- label.numberOfLines = 0
44
- label.font = .systemFont(ofSize: 16, weight: .medium)
45
- label.textColor = .label
46
- label.text = ""
47
- return label
48
- }()
49
-
50
- // MARK: - Lifecycle
51
-
52
- override func viewDidLoad() {
53
- super.viewDidLoad()
54
-
55
- title = "Encore WebView Example"
56
- view.backgroundColor = .systemBackground
57
-
58
- setupEncoreBridge()
59
- setupUI()
60
- }
61
-
62
- // MARK: - Setup
63
-
64
- private func setupEncoreBridge() {
65
- // Create configuration
66
- let config = EncoreConfiguration(
67
- apiKey: "your_api_key_here", // Replace with your actual API key
68
- environment: "production", // or "localhost" for testing
69
- userId: "user_123", // Optional: your user's ID
70
- attributes: [ // Optional: user attributes for targeting
71
- "email": "user@example.com",
72
- "subscriptionTier": "free"
73
- ]
74
- )
75
-
76
- // Initialize bridge
77
- encoreBridge = EncoreWebViewBridge(configuration: config)
78
- encoreBridge.delegate = self
79
-
80
- // Setup webView
81
- webView = encoreBridge.setupWebView()
82
-
83
- // Add webView to view hierarchy (invisible)
84
- webView.translatesAutoresizingMaskIntoConstraints = false
85
- view.addSubview(webView)
86
-
87
- NSLayoutConstraint.activate([
88
- // Make webView fill the entire screen
89
- webView.topAnchor.constraint(equalTo: view.topAnchor),
90
- webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
91
- webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
92
- webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
93
- ])
94
-
95
- // Load the embed page
96
- encoreBridge.loadEmbed()
97
- }
98
-
99
- private func setupUI() {
100
- // Add UI elements on top of webView
101
- let stackView = UIStackView(arrangedSubviews: [
102
- statusLabel,
103
- presentOfferButton,
104
- resultLabel
105
- ])
106
- stackView.axis = .vertical
107
- stackView.spacing = 20
108
- stackView.translatesAutoresizingMaskIntoConstraints = false
109
- view.addSubview(stackView)
110
-
111
- NSLayoutConstraint.activate([
112
- stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
113
- stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40),
114
- stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40),
115
-
116
- presentOfferButton.heightAnchor.constraint(equalToConstant: 50)
117
- ])
118
-
119
- presentOfferButton.addTarget(self, action: #selector(presentOfferTapped), for: .touchUpInside)
120
- }
121
-
122
- // MARK: - Actions
123
-
124
- @objc private func presentOfferTapped() {
125
- resultLabel.text = ""
126
- encoreBridge.presentOffer()
127
- }
128
- }
129
-
130
- // MARK: - EncoreWebViewBridgeDelegate
131
-
132
- extension ExampleViewController: EncoreWebViewBridgeDelegate {
133
-
134
- func encoreBridgeDidBecomeReady(_ bridge: EncoreWebViewBridge) {
135
- print("✅ Encore bridge is ready")
136
- statusLabel.text = "Encore is ready"
137
- }
138
-
139
- func encoreBridge(_ bridge: EncoreWebViewBridge, didInitializeWithUserId userId: String) {
140
- print("✅ Encore initialized with user ID: \(userId)")
141
- statusLabel.text = "Initialized (User: \(userId))"
142
- presentOfferButton.isEnabled = true
143
- }
144
-
145
- func encoreBridge(_ bridge: EncoreWebViewBridge, didGrantEntitlement entitlement: EncoreEntitlement) {
146
- print("🎉 Entitlement granted: \(entitlement)")
147
-
148
- // Handle the entitlement in your app
149
- // For example, unlock premium features, apply discount, etc.
150
-
151
- let message = "Entitlement Granted!\nType: \(entitlement.type)"
152
- resultLabel.text = message
153
- resultLabel.textColor = .systemGreen
154
-
155
- // Show success alert
156
- let alert = UIAlertController(
157
- title: "Success!",
158
- message: "You've earned a \(entitlement.type) entitlement!",
159
- preferredStyle: .alert
160
- )
161
- alert.addAction(UIAlertAction(title: "OK", style: .default))
162
- present(alert, animated: true)
163
-
164
- // TODO: Implement your app's logic to apply the entitlement
165
- // Examples:
166
- // - Update user's subscription status
167
- // - Apply discount to checkout
168
- // - Grant access to premium content
169
- // - Update UI to reflect new entitlement
170
- }
171
-
172
- func encoreBridge(_ bridge: EncoreWebViewBridge, userDeclinedWithReason reason: EncoreDeclineReason) {
173
- print("ℹ️ User declined offer: \(reason.rawValue)")
174
-
175
- let message = "Offer declined: \(reason.rawValue)"
176
- resultLabel.text = message
177
- resultLabel.textColor = .systemOrange
178
- }
179
-
180
- func encoreBridge(_ bridge: EncoreWebViewBridge, didEncounterError error: EncoreError) {
181
- print("❌ Encore error: \(error.code) - \(error.message)")
182
-
183
- let message = "Error: \(error.message)"
184
- resultLabel.text = message
185
- resultLabel.textColor = .systemRed
186
-
187
- // Show error alert
188
- let alert = UIAlertController(
189
- title: "Error",
190
- message: error.message,
191
- preferredStyle: .alert
192
- )
193
- alert.addAction(UIAlertAction(title: "OK", style: .default))
194
- present(alert, animated: true)
195
- }
196
- }
197
-
198
- // MARK: - Advanced Example: Cancellation Flow Integration
199
-
200
- extension ExampleViewController {
201
-
202
- /// Example: Present Encore offer during subscription cancellation
203
- func handleSubscriptionCancellation() {
204
- // User is trying to cancel their subscription
205
-
206
- // First, present the Encore offer as a retention tool
207
- encoreBridge.presentOffer()
208
-
209
- // The delegate methods will be called:
210
- // - If user completes offer: didGrantEntitlement() → Keep subscription active
211
- // - If user declines: userDeclinedWithReason() → Proceed with cancellation
212
- }
213
-
214
- /// Example: Check if user has active entitlements on app launch
215
- func checkEntitlementsOnLaunch() {
216
- // Note: Entitlement validation should be done server-side
217
- // This is just for demonstration of the bridge API
218
-
219
- encoreBridge.getState()
220
- // Response will be logged in console
221
- }
222
-
223
- /// Example: Update user attributes dynamically
224
- func updateUserData() {
225
- let newAttributes = [
226
- "subscriptionTier": "premium",
227
- "lastActiveDate": ISO8601DateFormatter().string(from: Date())
228
- ]
229
-
230
- encoreBridge.setUserAttributes(newAttributes)
231
- }
232
- }
233
-