@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.
- package/README.md +94 -9
- 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 +409 -0
- package/embed/index.html +57 -0
- package/embed/styles.css +154 -0
- package/examples/README.md +149 -0
- package/examples/angular/README.md +210 -0
- package/examples/angular/angular.json +73 -0
- package/examples/angular/package.json +32 -0
- package/examples/angular/src/app/app.component.html +56 -0
- package/examples/angular/src/app/app.component.ts +114 -0
- package/examples/angular/src/app/encore.service.ts +83 -0
- package/examples/angular/src/index.html +13 -0
- package/examples/angular/src/main.ts +7 -0
- package/examples/angular/src/styles.css +225 -0
- package/examples/angular/tsconfig.json +33 -0
- package/examples/ios-webview/EncoreURLBuilder.swift +87 -0
- package/examples/ios-webview/EncoreWebViewBridge.swift +426 -0
- package/examples/ios-webview/ExampleViewController.swift +233 -0
- package/examples/ios-webview/README.md +416 -0
- package/examples/ios-webview/SimpleEncoreView.swift +94 -0
- package/examples/ios-webview/SimpleExample.swift +131 -0
- package/examples/react/README.md +186 -0
- package/examples/react/index.html +13 -0
- package/examples/react/package.json +24 -0
- package/examples/react/src/App.tsx +173 -0
- package/examples/react/src/index.css +227 -0
- package/examples/react/src/main.tsx +11 -0
- package/examples/react/src/vite-env.d.ts +2 -0
- package/examples/react/tsconfig.json +25 -0
- package/examples/react/vite.config.ts +8 -0
- package/examples/svelte/README.md +233 -0
- package/examples/svelte/index.html +13 -0
- package/examples/svelte/package.json +25 -0
- package/examples/svelte/src/App.svelte +164 -0
- package/examples/svelte/src/app.css +224 -0
- package/examples/svelte/src/main.ts +9 -0
- package/examples/svelte/src/vite-env.d.ts +3 -0
- package/examples/svelte/svelte.config.js +8 -0
- package/examples/svelte/tsconfig.json +16 -0
- package/examples/svelte/tsconfig.node.json +11 -0
- package/examples/svelte/vite.config.ts +8 -0
- package/examples/vanilla-js/README.md +271 -0
- package/examples/vanilla-js/index.html +421 -0
- package/examples/vue/README.md +212 -0
- package/examples/vue/index.html +13 -0
- package/examples/vue/package.json +22 -0
- package/examples/vue/src/App.vue +170 -0
- package/examples/vue/src/main.ts +6 -0
- package/examples/vue/src/style.css +224 -0
- package/examples/vue/src/vite-env.d.ts +2 -0
- package/examples/vue/tsconfig.json +25 -0
- package/examples/vue/vite.config.ts +8 -0
- package/package.json +22 -3
- package/types/analytics/AnalyticsClient.d.ts +14 -0
- package/types/analytics/AnalyticsClient.d.ts.map +1 -0
- package/types/analytics/events.d.ts +63 -0
- package/types/analytics/events.d.ts.map +1 -0
- package/types/analytics/models.d.ts +17 -0
- package/types/analytics/models.d.ts.map +1 -0
- package/types/api/APIClient.d.ts +44 -8
- package/types/api/APIClient.d.ts.map +1 -1
- package/types/api/endpoints.d.ts +11 -7
- package/types/api/endpoints.d.ts.map +1 -1
- package/types/api/models.d.ts +134 -68
- package/types/api/models.d.ts.map +1 -1
- package/types/core/Configuration.d.ts +4 -0
- package/types/core/Configuration.d.ts.map +1 -1
- package/types/core/Encore.d.ts +16 -12
- package/types/core/Encore.d.ts.map +1 -1
- package/types/core/EntitlementManager.d.ts +9 -0
- package/types/core/EntitlementManager.d.ts.map +1 -1
- package/types/core/OfferManager.d.ts +27 -7
- package/types/core/OfferManager.d.ts.map +1 -1
- package/types/types.d.ts +1 -1
- package/types/types.d.ts.map +1 -1
- package/types/ui/OfferCard.d.ts.map +1 -1
- package/types/ui/OfferCarousel.d.ts.map +1 -1
- package/types/ui/Tooltip.d.ts +22 -0
- package/types/ui/Tooltip.d.ts.map +1 -0
- package/types/ui/styles.d.ts.map +1 -1
- package/dist/cjs/index.js +0 -2
- package/dist/cjs/index.js.map +0 -1
- package/types/src/api/APIClient.d.ts +0 -63
- package/types/src/api/APIClient.d.ts.map +0 -1
- package/types/src/api/endpoints.d.ts +0 -35
- package/types/src/api/endpoints.d.ts.map +0 -1
- package/types/src/api/models.d.ts +0 -156
- package/types/src/api/models.d.ts.map +0 -1
- package/types/src/core/Configuration.d.ts +0 -42
- package/types/src/core/Configuration.d.ts.map +0 -1
- package/types/src/core/Encore.d.ts +0 -81
- package/types/src/core/Encore.d.ts.map +0 -1
- package/types/src/core/EntitlementManager.d.ts +0 -65
- package/types/src/core/EntitlementManager.d.ts.map +0 -1
- package/types/src/core/OfferManager.d.ts +0 -35
- package/types/src/core/OfferManager.d.ts.map +0 -1
- package/types/src/core/PlacementBuilder.d.ts +0 -27
- package/types/src/core/PlacementBuilder.d.ts.map +0 -1
- package/types/src/core/SignalManager.d.ts +0 -51
- package/types/src/core/SignalManager.d.ts.map +0 -1
- package/types/src/core/StorageManager.d.ts +0 -34
- package/types/src/core/StorageManager.d.ts.map +0 -1
- package/types/src/core/VerificationPoller.d.ts +0 -27
- package/types/src/core/VerificationPoller.d.ts.map +0 -1
- package/types/src/index.d.ts +0 -7
- package/types/src/index.d.ts.map +0 -1
- package/types/src/types.d.ts +0 -156
- package/types/src/types.d.ts.map +0 -1
- package/types/src/ui/OfferCard.d.ts +0 -29
- package/types/src/ui/OfferCard.d.ts.map +0 -1
- package/types/src/ui/OfferCarousel.d.ts +0 -55
- package/types/src/ui/OfferCarousel.d.ts.map +0 -1
- package/types/src/ui/OfferModal.d.ts +0 -41
- package/types/src/ui/OfferModal.d.ts.map +0 -1
- package/types/src/ui/SuccessScreen.d.ts +0 -33
- package/types/src/ui/SuccessScreen.d.ts.map +0 -1
- package/types/src/ui/styles.d.ts +0 -44
- package/types/src/ui/styles.d.ts.map +0 -1
- package/types/src/utils/eventEmitter.d.ts +0 -50
- package/types/src/utils/eventEmitter.d.ts.map +0 -1
- package/types/src/utils/focusDetection.d.ts +0 -21
- package/types/src/utils/focusDetection.d.ts.map +0 -1
- package/types/src/utils/logger.d.ts +0 -21
- package/types/src/utils/logger.d.ts.map +0 -1
- package/types/src/utils/network.d.ts +0 -57
- package/types/src/utils/network.d.ts.map +0 -1
- package/types/src/utils/uuid.d.ts +0 -10
- 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
|
+
|