@encorekit/web-sdk 0.1.1 → 0.1.7

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 (134) hide show
  1. package/README.md +94 -9
  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 +409 -0
  9. package/embed/index.html +57 -0
  10. package/embed/styles.css +154 -0
  11. package/examples/README.md +149 -0
  12. package/examples/angular/README.md +210 -0
  13. package/examples/angular/angular.json +73 -0
  14. package/examples/angular/package.json +32 -0
  15. package/examples/angular/src/app/app.component.html +56 -0
  16. package/examples/angular/src/app/app.component.ts +114 -0
  17. package/examples/angular/src/app/encore.service.ts +83 -0
  18. package/examples/angular/src/index.html +13 -0
  19. package/examples/angular/src/main.ts +7 -0
  20. package/examples/angular/src/styles.css +225 -0
  21. package/examples/angular/tsconfig.json +33 -0
  22. package/examples/ios-webview/EncoreURLBuilder.swift +87 -0
  23. package/examples/ios-webview/EncoreWebViewBridge.swift +426 -0
  24. package/examples/ios-webview/ExampleViewController.swift +233 -0
  25. package/examples/ios-webview/README.md +416 -0
  26. package/examples/ios-webview/SimpleEncoreView.swift +94 -0
  27. package/examples/ios-webview/SimpleExample.swift +131 -0
  28. package/examples/react/README.md +186 -0
  29. package/examples/react/index.html +13 -0
  30. package/examples/react/package.json +24 -0
  31. package/examples/react/src/App.tsx +173 -0
  32. package/examples/react/src/index.css +227 -0
  33. package/examples/react/src/main.tsx +11 -0
  34. package/examples/react/src/vite-env.d.ts +2 -0
  35. package/examples/react/tsconfig.json +25 -0
  36. package/examples/react/vite.config.ts +8 -0
  37. package/examples/svelte/README.md +233 -0
  38. package/examples/svelte/index.html +13 -0
  39. package/examples/svelte/package.json +25 -0
  40. package/examples/svelte/src/App.svelte +164 -0
  41. package/examples/svelte/src/app.css +224 -0
  42. package/examples/svelte/src/main.ts +9 -0
  43. package/examples/svelte/src/vite-env.d.ts +3 -0
  44. package/examples/svelte/svelte.config.js +8 -0
  45. package/examples/svelte/tsconfig.json +16 -0
  46. package/examples/svelte/tsconfig.node.json +11 -0
  47. package/examples/svelte/vite.config.ts +8 -0
  48. package/examples/vanilla-js/README.md +271 -0
  49. package/examples/vanilla-js/index.html +421 -0
  50. package/examples/vue/README.md +212 -0
  51. package/examples/vue/index.html +13 -0
  52. package/examples/vue/package.json +22 -0
  53. package/examples/vue/src/App.vue +170 -0
  54. package/examples/vue/src/main.ts +6 -0
  55. package/examples/vue/src/style.css +224 -0
  56. package/examples/vue/src/vite-env.d.ts +2 -0
  57. package/examples/vue/tsconfig.json +25 -0
  58. package/examples/vue/vite.config.ts +8 -0
  59. package/package.json +22 -3
  60. package/types/analytics/AnalyticsClient.d.ts +14 -0
  61. package/types/analytics/AnalyticsClient.d.ts.map +1 -0
  62. package/types/analytics/events.d.ts +63 -0
  63. package/types/analytics/events.d.ts.map +1 -0
  64. package/types/analytics/models.d.ts +17 -0
  65. package/types/analytics/models.d.ts.map +1 -0
  66. package/types/api/APIClient.d.ts +44 -8
  67. package/types/api/APIClient.d.ts.map +1 -1
  68. package/types/api/endpoints.d.ts +11 -7
  69. package/types/api/endpoints.d.ts.map +1 -1
  70. package/types/api/models.d.ts +134 -68
  71. package/types/api/models.d.ts.map +1 -1
  72. package/types/core/Configuration.d.ts +4 -0
  73. package/types/core/Configuration.d.ts.map +1 -1
  74. package/types/core/Encore.d.ts +16 -12
  75. package/types/core/Encore.d.ts.map +1 -1
  76. package/types/core/EntitlementManager.d.ts +9 -0
  77. package/types/core/EntitlementManager.d.ts.map +1 -1
  78. package/types/core/OfferManager.d.ts +27 -7
  79. package/types/core/OfferManager.d.ts.map +1 -1
  80. package/types/types.d.ts +1 -1
  81. package/types/types.d.ts.map +1 -1
  82. package/types/ui/OfferCard.d.ts.map +1 -1
  83. package/types/ui/OfferCarousel.d.ts.map +1 -1
  84. package/types/ui/Tooltip.d.ts +22 -0
  85. package/types/ui/Tooltip.d.ts.map +1 -0
  86. package/types/ui/styles.d.ts.map +1 -1
  87. package/dist/cjs/index.js +0 -2
  88. package/dist/cjs/index.js.map +0 -1
  89. package/types/src/api/APIClient.d.ts +0 -63
  90. package/types/src/api/APIClient.d.ts.map +0 -1
  91. package/types/src/api/endpoints.d.ts +0 -35
  92. package/types/src/api/endpoints.d.ts.map +0 -1
  93. package/types/src/api/models.d.ts +0 -156
  94. package/types/src/api/models.d.ts.map +0 -1
  95. package/types/src/core/Configuration.d.ts +0 -42
  96. package/types/src/core/Configuration.d.ts.map +0 -1
  97. package/types/src/core/Encore.d.ts +0 -81
  98. package/types/src/core/Encore.d.ts.map +0 -1
  99. package/types/src/core/EntitlementManager.d.ts +0 -65
  100. package/types/src/core/EntitlementManager.d.ts.map +0 -1
  101. package/types/src/core/OfferManager.d.ts +0 -35
  102. package/types/src/core/OfferManager.d.ts.map +0 -1
  103. package/types/src/core/PlacementBuilder.d.ts +0 -27
  104. package/types/src/core/PlacementBuilder.d.ts.map +0 -1
  105. package/types/src/core/SignalManager.d.ts +0 -51
  106. package/types/src/core/SignalManager.d.ts.map +0 -1
  107. package/types/src/core/StorageManager.d.ts +0 -34
  108. package/types/src/core/StorageManager.d.ts.map +0 -1
  109. package/types/src/core/VerificationPoller.d.ts +0 -27
  110. package/types/src/core/VerificationPoller.d.ts.map +0 -1
  111. package/types/src/index.d.ts +0 -7
  112. package/types/src/index.d.ts.map +0 -1
  113. package/types/src/types.d.ts +0 -156
  114. package/types/src/types.d.ts.map +0 -1
  115. package/types/src/ui/OfferCard.d.ts +0 -29
  116. package/types/src/ui/OfferCard.d.ts.map +0 -1
  117. package/types/src/ui/OfferCarousel.d.ts +0 -55
  118. package/types/src/ui/OfferCarousel.d.ts.map +0 -1
  119. package/types/src/ui/OfferModal.d.ts +0 -41
  120. package/types/src/ui/OfferModal.d.ts.map +0 -1
  121. package/types/src/ui/SuccessScreen.d.ts +0 -33
  122. package/types/src/ui/SuccessScreen.d.ts.map +0 -1
  123. package/types/src/ui/styles.d.ts +0 -44
  124. package/types/src/ui/styles.d.ts.map +0 -1
  125. package/types/src/utils/eventEmitter.d.ts +0 -50
  126. package/types/src/utils/eventEmitter.d.ts.map +0 -1
  127. package/types/src/utils/focusDetection.d.ts +0 -21
  128. package/types/src/utils/focusDetection.d.ts.map +0 -1
  129. package/types/src/utils/logger.d.ts +0 -21
  130. package/types/src/utils/logger.d.ts.map +0 -1
  131. package/types/src/utils/network.d.ts +0 -57
  132. package/types/src/utils/network.d.ts.map +0 -1
  133. package/types/src/utils/uuid.d.ts +0 -10
  134. package/types/src/utils/uuid.d.ts.map +0 -1
@@ -0,0 +1,416 @@
1
+ # Encore iOS WebView Integration Example
2
+
3
+ This directory contains example Swift code for integrating Encore into your iOS app using WKWebView instead of the native Swift SDK.
4
+
5
+ ## Overview
6
+
7
+ This approach is ideal for:
8
+ - Apps that can't or don't want to add new SDK dependencies
9
+ - Quick integration without modifying build configuration
10
+ - Cross-platform teams familiar with web technologies
11
+ - Apps already using web views for other features
12
+
13
+ ## Files
14
+
15
+ - **`EncoreWebViewBridge.swift`** - Complete bridge implementation (copy this to your project)
16
+ - **`ExampleViewController.swift`** - Example usage in a view controller
17
+ - **`README.md`** - This file
18
+
19
+ ## Quick Start
20
+
21
+ ### 1. Add the Bridge to Your Project
22
+
23
+ Copy `EncoreWebViewBridge.swift` to your Xcode project. No additional dependencies required!
24
+
25
+ ### 2. Basic Integration
26
+
27
+ ```swift
28
+ import UIKit
29
+ import WebKit
30
+
31
+ class YourViewController: UIViewController {
32
+ private var encoreBridge: EncoreWebViewBridge!
33
+ private var webView: WKWebView!
34
+
35
+ override func viewDidLoad() {
36
+ super.viewDidLoad()
37
+
38
+ // 1. Create configuration
39
+ let config = EncoreConfiguration(
40
+ apiKey: "your_api_key_here",
41
+ environment: "production",
42
+ userId: "user_123"
43
+ )
44
+
45
+ // 2. Initialize bridge
46
+ encoreBridge = EncoreWebViewBridge(configuration: config)
47
+ encoreBridge.delegate = self
48
+
49
+ // 3. Setup webView
50
+ webView = encoreBridge.setupWebView()
51
+ view.addSubview(webView)
52
+
53
+ // 4. Configure constraints (webView should fill screen)
54
+ webView.translatesAutoresizingMaskIntoConstraints = false
55
+ NSLayoutConstraint.activate([
56
+ webView.topAnchor.constraint(equalTo: view.topAnchor),
57
+ webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
58
+ webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
59
+ webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
60
+ ])
61
+
62
+ // 5. Load embed
63
+ encoreBridge.loadEmbed()
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### 3. Implement Delegate Methods
69
+
70
+ ```swift
71
+ extension YourViewController: EncoreWebViewBridgeDelegate {
72
+ func encoreBridgeDidBecomeReady(_ bridge: EncoreWebViewBridge) {
73
+ print("Encore is ready")
74
+ }
75
+
76
+ func encoreBridge(_ bridge: EncoreWebViewBridge,
77
+ didInitializeWithUserId userId: String) {
78
+ print("Initialized with user: \(userId)")
79
+ // Now you can present offers
80
+ }
81
+
82
+ func encoreBridge(_ bridge: EncoreWebViewBridge,
83
+ didGrantEntitlement entitlement: EncoreEntitlement) {
84
+ // User completed an offer!
85
+ print("Granted: \(entitlement.type)")
86
+
87
+ // TODO: Apply the entitlement in your app
88
+ // - Unlock premium features
89
+ // - Apply discount
90
+ // - Update subscription status
91
+ }
92
+
93
+ func encoreBridge(_ bridge: EncoreWebViewBridge,
94
+ userDeclinedWithReason reason: EncoreDeclineReason) {
95
+ print("User declined: \(reason)")
96
+ // Handle decline (optional)
97
+ }
98
+
99
+ func encoreBridge(_ bridge: EncoreWebViewBridge,
100
+ didEncounterError error: EncoreError) {
101
+ print("Error: \(error.message)")
102
+ // Handle error (optional)
103
+ }
104
+ }
105
+ ```
106
+
107
+ ### 4. Present an Offer
108
+
109
+ ```swift
110
+ // When you want to show an offer (e.g., during cancellation flow)
111
+ encoreBridge.presentOffer()
112
+ ```
113
+
114
+ ## Use Cases
115
+
116
+ ### Subscription Cancellation Flow
117
+
118
+ ```swift
119
+ func handleCancelSubscription() {
120
+ // Present retention offer before cancelling
121
+ encoreBridge.presentOffer()
122
+ }
123
+
124
+ // In delegate:
125
+ func encoreBridge(_ bridge: EncoreWebViewBridge,
126
+ didGrantEntitlement entitlement: EncoreEntitlement) {
127
+ // User completed offer - keep subscription active!
128
+ keepSubscriptionActive()
129
+ showSuccessMessage()
130
+ }
131
+
132
+ func encoreBridge(_ bridge: EncoreWebViewBridge,
133
+ userDeclinedWithReason reason: EncoreDeclineReason) {
134
+ // User still wants to cancel - proceed
135
+ proceedWithCancellation()
136
+ }
137
+ ```
138
+
139
+ ### Feature Paywall
140
+
141
+ ```swift
142
+ func showPremiumFeature() {
143
+ guard userHasPremiumAccess() else {
144
+ // Show Encore offer before paywall
145
+ encoreBridge.presentOffer()
146
+ return
147
+ }
148
+
149
+ // Show premium feature
150
+ }
151
+ ```
152
+
153
+ ### Dynamic User Attributes
154
+
155
+ ```swift
156
+ func updateUserContext() {
157
+ let attributes = [
158
+ "subscriptionTier": user.tier,
159
+ "monthsSubscribed": "\(user.subscriptionMonths)",
160
+ "lastActiveDate": ISO8601DateFormatter().string(from: Date())
161
+ ]
162
+
163
+ encoreBridge.setUserAttributes(attributes)
164
+ }
165
+ ```
166
+
167
+ ## Configuration Options
168
+
169
+ ### EncoreConfiguration
170
+
171
+ ```swift
172
+ public struct EncoreConfiguration {
173
+ public let apiKey: String // Required: Your Encore API key
174
+ public let environment: String // "production" or "localhost"
175
+ public let userId: String? // Optional: User identifier
176
+ public let attributes: [String: String]? // Optional: User attributes
177
+ public let logLevel: String? // Optional: "debug" or "none"
178
+ public let uiConfiguration: [String: String]? // Optional: UI text customization
179
+ }
180
+ ```
181
+
182
+ ### User Attributes
183
+
184
+ You can pass user attributes for better targeting:
185
+
186
+ ```swift
187
+ let attributes = [
188
+ // Identity
189
+ "email": "user@example.com",
190
+ "firstName": "Jane",
191
+ "lastName": "Doe",
192
+
193
+ // Subscription
194
+ "subscriptionTier": "free",
195
+ "monthsSubscribed": "6",
196
+ "billingCycle": "monthly",
197
+
198
+ // Demographics
199
+ "postalCode": "10001",
200
+ "city": "New York",
201
+ "state": "NY",
202
+ "countryCode": "US",
203
+
204
+ // Engagement
205
+ "lastActiveDate": "2024-01-15",
206
+ "totalSessions": "150"
207
+ ]
208
+ ```
209
+
210
+ ### UI Configuration
211
+
212
+ Customize the offer modal text:
213
+
214
+ ```swift
215
+ let uiConfig = [
216
+ "titleText": "Special Offer for You!",
217
+ "subtitleText": "Complete offers to unlock premium features",
218
+ "offerDescriptionText": "Complete this offer:",
219
+ "applyCreditsButtonText": "Claim My Offer"
220
+ ]
221
+
222
+ let config = EncoreConfiguration(
223
+ apiKey: "your_key",
224
+ environment: "production",
225
+ uiConfiguration: uiConfig
226
+ )
227
+ ```
228
+
229
+ ## API Reference
230
+
231
+ ### EncoreWebViewBridge
232
+
233
+ #### Methods
234
+
235
+ ```swift
236
+ // Setup and load
237
+ func setupWebView() -> WKWebView
238
+ func loadEmbed()
239
+
240
+ // Configuration
241
+ func configure(_ configuration: EncoreConfiguration)
242
+
243
+ // Offers
244
+ func presentOffer()
245
+
246
+ // User management
247
+ func identify(userId: String, attributes: [String: String]? = nil)
248
+ func setUserAttributes(_ attributes: [String: String])
249
+ func reset()
250
+
251
+ // State
252
+ func getState()
253
+ ```
254
+
255
+ #### Properties
256
+
257
+ ```swift
258
+ weak var delegate: EncoreWebViewBridgeDelegate?
259
+ var isReady: Bool { get }
260
+ var isInitialized: Bool { get }
261
+ var currentUserId: String? { get }
262
+ ```
263
+
264
+ ### Delegate Protocol
265
+
266
+ ```swift
267
+ protocol EncoreWebViewBridgeDelegate: AnyObject {
268
+ func encoreBridgeDidBecomeReady(_ bridge: EncoreWebViewBridge)
269
+ func encoreBridge(_ bridge: EncoreWebViewBridge, didInitializeWithUserId userId: String)
270
+ func encoreBridge(_ bridge: EncoreWebViewBridge, didGrantEntitlement entitlement: EncoreEntitlement)
271
+ func encoreBridge(_ bridge: EncoreWebViewBridge, userDeclinedWithReason reason: EncoreDeclineReason)
272
+ func encoreBridge(_ bridge: EncoreWebViewBridge, didEncounterError error: EncoreError)
273
+ }
274
+ ```
275
+
276
+ ## Important Notes
277
+
278
+ ### WebView Positioning
279
+
280
+ The WebView should fill the entire screen (or your desired container) even though it's transparent. This ensures the offer modal can display properly when shown.
281
+
282
+ ```swift
283
+ // Correct: Full screen
284
+ webView.frame = view.bounds
285
+
286
+ // Or with constraints
287
+ NSLayoutConstraint.activate([
288
+ webView.topAnchor.constraint(equalTo: view.topAnchor),
289
+ webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
290
+ webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
291
+ webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
292
+ ])
293
+ ```
294
+
295
+ ### Entitlement Validation
296
+
297
+ **Important:** Always validate entitlements on your server, not in the iOS app. The client-side entitlement data is for UX purposes only.
298
+
299
+ ```swift
300
+ // ❌ Don't do this
301
+ func unlockPremium() {
302
+ if entitlement.type == "freeTrial" {
303
+ // Never trust client-side validation
304
+ }
305
+ }
306
+
307
+ // ✅ Do this instead
308
+ func unlockPremium() {
309
+ // Call your server to validate
310
+ api.validateEntitlement(userId) { result in
311
+ if result.isValid {
312
+ // Server confirmed - safe to unlock
313
+ }
314
+ }
315
+ }
316
+ ```
317
+
318
+ ### Thread Safety
319
+
320
+ All delegate methods are called on the main thread, so you can safely update UI directly.
321
+
322
+ ### Memory Management
323
+
324
+ The bridge holds a weak reference to its delegate to prevent retain cycles. Make sure your view controller maintains a strong reference to the bridge.
325
+
326
+ ```swift
327
+ // ✅ Correct
328
+ class MyVC: UIViewController {
329
+ private var encoreBridge: EncoreWebViewBridge! // Strong reference
330
+ }
331
+
332
+ // ❌ Wrong - bridge will be deallocated
333
+ func setupBridge() {
334
+ let bridge = EncoreWebViewBridge() // Local variable
335
+ bridge.delegate = self
336
+ // bridge is deallocated when function returns
337
+ }
338
+ ```
339
+
340
+ ### State Management & Page Reloads
341
+
342
+ The bridge automatically handles page reloads and navigation events:
343
+
344
+ - **On navigation start** (`didStartProvisionalNavigation`): State is reset (`isReady = false`, `isInitialized = false`)
345
+ - **On navigation finish** (`didFinish`): Configuration is automatically re-applied
346
+ - **Message queue**: Cleared on navigation to prevent stale messages
347
+
348
+ This means:
349
+ - ✅ **Page refreshes work correctly** - SDK is automatically re-initialized
350
+ - ✅ **No stale state** - Each page load starts fresh
351
+ - ✅ **No race conditions** - Configuration waits for JS to be ready
352
+
353
+ ```swift
354
+ // You don't need to do anything special for page reloads
355
+ // The bridge handles it automatically:
356
+
357
+ // Initial load
358
+ encoreBridge.loadEmbed() // → SDK initializes
359
+
360
+ // User refreshes page (swipe down, etc.)
361
+ // → Bridge detects navigation start
362
+ // → State is reset
363
+ // → Page finishes loading
364
+ // → Configuration is automatically re-applied
365
+ // → SDK initializes again
366
+ ```
367
+
368
+ **Important:** Don't rely on `isInitialized` staying `true` across navigations. If you need to track state across page loads, maintain it separately in your view controller.
369
+
370
+ ## Troubleshooting
371
+
372
+ ### Offers not showing
373
+
374
+ 1. Check that `didInitializeWithUserId` was called
375
+ 2. Verify your API key is correct
376
+ 3. Check network connectivity
377
+ 4. Look for error messages in delegate methods
378
+ 5. Try enabling debug mode: add `?debug=true` to embed URL
379
+
380
+ ### WebView blank/not loading
381
+
382
+ 1. Ensure Info.plist has network permissions
383
+ 2. Check the embed URL is accessible
384
+ 3. Verify WKWebView configuration allows JavaScript
385
+ 4. Check console for JavaScript errors
386
+
387
+ ### Messages not received
388
+
389
+ 1. Verify message handler name is "encore"
390
+ 2. Check that delegate is set
391
+ 3. Ensure bridge has strong reference
392
+ 4. Try adding print statements in delegate methods
393
+
394
+ ### Build errors
395
+
396
+ 1. Make sure you're targeting iOS 13.0 or later
397
+ 2. Import WebKit framework
398
+ 3. Check that EncoreWebViewBridge.swift is added to target
399
+
400
+ ## Requirements
401
+
402
+ - iOS 13.0+
403
+ - Xcode 12.0+
404
+ - Swift 5.0+
405
+ - Network connectivity
406
+
407
+ ## Support
408
+
409
+ For issues or questions:
410
+ - Email: admin@encorekit.com
411
+ - Documentation: https://docs.encorekit.com
412
+
413
+ ## License
414
+
415
+ This example code is provided as-is for integration purposes.
416
+
@@ -0,0 +1,94 @@
1
+ //
2
+ // SimpleEncoreView.swift
3
+ // Encore iOS Simple Integration
4
+ //
5
+ // Dead simple SwiftUI wrapper for Encore HTML tag
6
+ //
7
+
8
+ import SwiftUI
9
+ import WebKit
10
+
11
+ /// Simple SwiftUI view that displays Encore embed
12
+ ///
13
+ /// Usage:
14
+ /// ```swift
15
+ /// EncoreWebView(
16
+ /// apiKey: "your_api_key",
17
+ /// userId: "user_123",
18
+ /// attributes: ["email": "user@example.com"]
19
+ /// )
20
+ /// ```
21
+ public struct EncoreWebView: View {
22
+ let apiKey: String
23
+ let userId: String
24
+ let environment: String
25
+ let attributes: [String: String]
26
+ let debug: Bool
27
+
28
+ public init(
29
+ apiKey: String,
30
+ userId: String,
31
+ environment: String = "production",
32
+ attributes: [String: String] = [:],
33
+ debug: Bool = false
34
+ ) {
35
+ self.apiKey = apiKey
36
+ self.userId = userId
37
+ self.environment = environment
38
+ self.attributes = attributes
39
+ self.debug = debug
40
+ }
41
+
42
+ public var body: some View {
43
+ WebViewRepresentable(url: buildURL())
44
+ .ignoresSafeArea()
45
+ }
46
+
47
+ private func buildURL() -> URL {
48
+ EncoreURL.build(
49
+ apiKey: apiKey,
50
+ userId: userId,
51
+ environment: environment,
52
+ attributes: attributes,
53
+ debug: debug
54
+ )!
55
+ }
56
+ }
57
+
58
+ // MARK: - Private WebView Wrapper
59
+
60
+ private struct WebViewRepresentable: UIViewRepresentable {
61
+ let url: URL
62
+
63
+ func makeUIView(context: Context) -> WKWebView {
64
+ let webView = WKWebView()
65
+ webView.backgroundColor = .systemBackground
66
+ webView.isOpaque = true
67
+ webView.load(URLRequest(url: url))
68
+ return webView
69
+ }
70
+
71
+ func updateUIView(_ webView: WKWebView, context: Context) {
72
+ // No updates needed
73
+ }
74
+ }
75
+
76
+ // MARK: - Preview
77
+
78
+ #if DEBUG
79
+ struct EncoreWebView_Previews: PreviewProvider {
80
+ static var previews: some View {
81
+ EncoreWebView(
82
+ apiKey: "test_api_key",
83
+ userId: "preview_user",
84
+ environment: "localhost",
85
+ attributes: [
86
+ "email": "preview@example.com",
87
+ "subscriptionTier": "free"
88
+ ],
89
+ debug: true
90
+ )
91
+ }
92
+ }
93
+ #endif
94
+
@@ -0,0 +1,131 @@
1
+ //
2
+ // SimpleExample.swift
3
+ // Encore iOS Simple Integration
4
+ //
5
+ // The simplest possible integration - just open a URL in WKWebView
6
+ //
7
+
8
+ import UIKit
9
+ import WebKit
10
+
11
+ class SimpleEncoreViewController: UIViewController {
12
+
13
+ private var webView: WKWebView!
14
+
15
+ override func viewDidLoad() {
16
+ super.viewDidLoad()
17
+
18
+ // 1. Create WKWebView
19
+ webView = WKWebView(frame: view.bounds)
20
+ webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
21
+ webView.backgroundColor = .clear
22
+ webView.isOpaque = false
23
+ view.addSubview(webView)
24
+
25
+ // 2. Build Encore URL
26
+ let url = EncoreURL.build(
27
+ apiKey: "your_api_key_here",
28
+ userId: "user_123",
29
+ environment: "production",
30
+ attributes: [
31
+ "email": "user@example.com",
32
+ "subscriptionTier": "free"
33
+ ]
34
+ )!
35
+
36
+ // 3. Load it!
37
+ webView.load(URLRequest(url: url))
38
+
39
+ // That's it! The embed will auto-initialize and show offers.
40
+ }
41
+ }
42
+
43
+ // MARK: - SwiftUI Version
44
+
45
+ import SwiftUI
46
+
47
+ struct SimpleEncoreView: View {
48
+ var body: some View {
49
+ EncoreWebView(
50
+ apiKey: "your_api_key_here",
51
+ userId: "user_123",
52
+ attributes: [
53
+ "email": "user@example.com",
54
+ "subscriptionTier": "free"
55
+ ]
56
+ )
57
+ }
58
+ }
59
+
60
+ struct EncoreWebView: UIViewRepresentable {
61
+ let apiKey: String
62
+ let userId: String?
63
+ let environment: String
64
+ let attributes: [String: String]?
65
+
66
+ init(
67
+ apiKey: String,
68
+ userId: String? = nil,
69
+ environment: String = "production",
70
+ attributes: [String: String]? = nil
71
+ ) {
72
+ self.apiKey = apiKey
73
+ self.userId = userId
74
+ self.environment = environment
75
+ self.attributes = attributes
76
+ }
77
+
78
+ func makeUIView(context: Context) -> WKWebView {
79
+ let webView = WKWebView()
80
+ webView.backgroundColor = .clear
81
+ webView.isOpaque = false
82
+
83
+ if let url = EncoreURL.build(
84
+ apiKey: apiKey,
85
+ userId: userId,
86
+ environment: environment,
87
+ attributes: attributes
88
+ ) {
89
+ webView.load(URLRequest(url: url))
90
+ }
91
+
92
+ return webView
93
+ }
94
+
95
+ func updateUIView(_ webView: WKWebView, context: Context) {
96
+ // No updates needed
97
+ }
98
+ }
99
+
100
+ // MARK: - Usage Examples
101
+
102
+ extension SimpleEncoreViewController {
103
+
104
+ /// Example: Show in cancellation flow
105
+ static func showInCancellationFlow(from viewController: UIViewController) {
106
+ let encoreVC = SimpleEncoreViewController()
107
+ encoreVC.modalPresentationStyle = .fullScreen
108
+ viewController.present(encoreVC, animated: true)
109
+ }
110
+
111
+ /// Example: Show as modal sheet
112
+ static func showAsSheet(from viewController: UIViewController) {
113
+ let encoreVC = SimpleEncoreViewController()
114
+ if let sheet = encoreVC.sheetPresentationController {
115
+ sheet.detents = [.large()]
116
+ sheet.prefersGrabberVisible = true
117
+ }
118
+ viewController.present(encoreVC, animated: true)
119
+ }
120
+
121
+ /// Example: Embed in existing view
122
+ static func embedInView(_ containerView: UIView, parentVC: UIViewController) {
123
+ let encoreVC = SimpleEncoreViewController()
124
+ parentVC.addChild(encoreVC)
125
+ containerView.addSubview(encoreVC.view)
126
+ encoreVC.view.frame = containerView.bounds
127
+ encoreVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
128
+ encoreVC.didMove(toParent: parentVC)
129
+ }
130
+ }
131
+