@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
package/embed/README.md
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
# Encore iOS WebView Embed
|
|
2
|
+
|
|
3
|
+
This directory contains the embeddable HTML page and JavaScript bridge that allows iOS apps to use Encore via WKWebView without installing the native Swift SDK.
|
|
4
|
+
|
|
5
|
+
## 🎨 Mobile-Native UI
|
|
6
|
+
|
|
7
|
+
The embed now features an **iOS-native bottom sheet UI** that provides a mobile-optimized experience matching the iOS SDK's visual design:
|
|
8
|
+
|
|
9
|
+
- ✅ **Bottom Sheet Design** - Slides up from bottom (not a centered popup)
|
|
10
|
+
- ✅ **Native Animations** - Smooth 300ms cubic-bezier animations
|
|
11
|
+
- ✅ **Swipe-to-Dismiss** - Pull down gesture to close
|
|
12
|
+
- ✅ **Auto-Present** - Automatically shows offers on load
|
|
13
|
+
- ✅ **iOS Styling** - SF Pro font, system blue, native shadows
|
|
14
|
+
- ✅ **Safe Area Support** - Respects notches and home indicators
|
|
15
|
+
- ✅ **Dark Mode** - Automatic dark mode support
|
|
16
|
+
|
|
17
|
+
📖 **[Full Mobile UI Guide](./MOBILE-UI-GUIDE.md)** | 🧪 **[Testing Guide](../../encore-swift-sdk/EncoreSwiftUIDemo/MOBILE-UI-TESTING.md)**
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
21
|
+
The embed solution consists of:
|
|
22
|
+
|
|
23
|
+
- **`index.html`** - The main HTML page to be loaded in WKWebView
|
|
24
|
+
- **`bridge.js`** - JavaScript-iOS communication bridge via `webkit.messageHandlers`
|
|
25
|
+
- **`app.js`** - Application logic connecting Encore Web SDK to the bridge
|
|
26
|
+
- **`styles.css`** - Base styling (transparent until offer shown)
|
|
27
|
+
- **`mobile-styles.css`** - iOS-native bottom sheet components
|
|
28
|
+
- **`mobile-overrides.css`** - Transforms Web SDK modal to mobile sheet
|
|
29
|
+
|
|
30
|
+
## Architecture
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
iOS App (WKWebView)
|
|
34
|
+
↕ (webkit.messageHandlers)
|
|
35
|
+
Bridge.js
|
|
36
|
+
↕
|
|
37
|
+
App.js
|
|
38
|
+
↕
|
|
39
|
+
Encore Web SDK
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### URL Flow (How Offers Are Opened)
|
|
43
|
+
|
|
44
|
+
When a user claims an offer, the URL flows through the system as follows:
|
|
45
|
+
|
|
46
|
+
1. **SDK builds final URL** - `Encore.ts` constructs the advertiser URL with tracking parameters (transaction ID, user ID, etc.)
|
|
47
|
+
2. **SDK calls window.open()** - The SDK opens the URL using the standard `window.open(finalUrl)` API
|
|
48
|
+
3. **Embed intercepts** - `app.js` overrides `window.open()` to capture the URL before it opens
|
|
49
|
+
4. **Embed sends to iOS** - The captured URL is sent via `bridge.sendToIOS('encore:openURL', { url })`
|
|
50
|
+
5. **iOS opens Safari** - The native app receives the message and opens the URL in `SFSafariViewController`
|
|
51
|
+
|
|
52
|
+
This design ensures:
|
|
53
|
+
- ✅ **No DOM coupling** - The SDK doesn't need to write URLs to DOM attributes
|
|
54
|
+
- ✅ **Clean separation** - The SDK is unaware of the iOS integration
|
|
55
|
+
- ✅ **Tracking preserved** - All tracking parameters flow through naturally
|
|
56
|
+
- ✅ **Framework agnostic** - Works regardless of how the SDK UI is implemented
|
|
57
|
+
|
|
58
|
+
## Message Protocol
|
|
59
|
+
|
|
60
|
+
### Web-to-iOS Messages
|
|
61
|
+
|
|
62
|
+
Messages sent from JavaScript to iOS via `webkit.messageHandlers.encore.postMessage()`:
|
|
63
|
+
|
|
64
|
+
| Message Type | Payload | Description |
|
|
65
|
+
|-------------|---------|-------------|
|
|
66
|
+
| `encore:ready` | `{ timestamp, hasURLConfig, isMobile }` | Embed app loaded and ready |
|
|
67
|
+
| `encore:initialized` | `{ success, userId, version }` | SDK initialized successfully |
|
|
68
|
+
| `encore:sheet:presented` | `{ timestamp }` | **[Mobile]** Bottom sheet shown |
|
|
69
|
+
| `encore:sheet:dismissed` | `{ reason, timestamp }` | **[Mobile]** Bottom sheet closed |
|
|
70
|
+
| `encore:openURL` | `{ url, timestamp }` | **[Mobile]** Request to open URL in Safari |
|
|
71
|
+
| `encore:entitlementGranted` | `{ entitlement, timestamp }` | User completed offer |
|
|
72
|
+
| `encore:userDeclined` | `{ reason, details, timestamp }` | User declined offer |
|
|
73
|
+
| `encore:error` | `{ code, message, context }` | Error occurred |
|
|
74
|
+
| `encore:userIdentified` | `{ success, userId }` | User identified |
|
|
75
|
+
| `encore:attributesSet` | `{ success }` | Attributes set |
|
|
76
|
+
| `encore:reset` | `{ success }` | SDK reset |
|
|
77
|
+
| `encore:state` | `{ isInitialized, userId, version, config }` | Current state |
|
|
78
|
+
|
|
79
|
+
### iOS-to-Web Messages
|
|
80
|
+
|
|
81
|
+
Messages sent from iOS to JavaScript via `webView.evaluateJavaScript()` or `postMessage()`:
|
|
82
|
+
|
|
83
|
+
| Message Type | Payload | Description |
|
|
84
|
+
|-------------|---------|-------------|
|
|
85
|
+
| `configure` | `{ apiKey, environment?, userId?, attributes?, logLevel?, uiConfiguration? }` | Initialize SDK |
|
|
86
|
+
| `presentOffer` | `{}` | Show offer modal |
|
|
87
|
+
| `identify` | `{ userId, attributes? }` | Identify user |
|
|
88
|
+
| `setUserAttributes` | `{ attributes }` | Update user attributes |
|
|
89
|
+
| `reset` | `{}` | Reset SDK state |
|
|
90
|
+
| `getState` | `{}` | Request current state |
|
|
91
|
+
|
|
92
|
+
## Integration Methods
|
|
93
|
+
|
|
94
|
+
### Method 1: URL Builder (Recommended for Mobile)
|
|
95
|
+
|
|
96
|
+
Use the `EncoreURL` builder to create a properly formatted URL with auto-present:
|
|
97
|
+
|
|
98
|
+
```swift
|
|
99
|
+
import SwiftUI
|
|
100
|
+
|
|
101
|
+
let url = EncoreURL.build(
|
|
102
|
+
apiKey: "your_api_key",
|
|
103
|
+
userId: "user123",
|
|
104
|
+
environment: "production",
|
|
105
|
+
autoPresentOffer: true, // Auto-show offers
|
|
106
|
+
debug: false
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
// In SwiftUI
|
|
110
|
+
.sheet(isPresented: $showEncore) {
|
|
111
|
+
EncoreWebView(
|
|
112
|
+
apiKey: "your_api_key",
|
|
113
|
+
userId: "user123",
|
|
114
|
+
autoPresentOffer: true
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Pros**: Dead simple, iOS-native UI, auto-presents, swipe gestures
|
|
120
|
+
**Cons**: API key visible in URL (use server-side API key rotation)
|
|
121
|
+
|
|
122
|
+
### Method 2: URL Parameters (Manual)
|
|
123
|
+
|
|
124
|
+
Load the embed page with configuration in URL:
|
|
125
|
+
|
|
126
|
+
```swift
|
|
127
|
+
let url = URL(string: "https://encorekit.com/embed/?apiKey=YOUR_KEY&userId=USER_123&environment=production&autoPresentOffer=true")!
|
|
128
|
+
webView.load(URLRequest(url: url))
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Method 3: postMessage (Advanced)
|
|
132
|
+
|
|
133
|
+
Load empty embed page, then configure via postMessage:
|
|
134
|
+
|
|
135
|
+
```swift
|
|
136
|
+
// 1. Load embed page
|
|
137
|
+
webView.load(URL(string: "https://encorekit.com/embed/")!)
|
|
138
|
+
|
|
139
|
+
// 2. Configure after load (in WKNavigationDelegate)
|
|
140
|
+
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
|
141
|
+
let config = """
|
|
142
|
+
window.postMessage({
|
|
143
|
+
type: 'configure',
|
|
144
|
+
payload: {
|
|
145
|
+
apiKey: '\(apiKey)',
|
|
146
|
+
userId: '\(userId)',
|
|
147
|
+
environment: 'production'
|
|
148
|
+
}
|
|
149
|
+
}, '*');
|
|
150
|
+
"""
|
|
151
|
+
webView.evaluateJavaScript(config)
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Pros**: More secure, flexible, dynamic configuration
|
|
156
|
+
**Cons**: Slightly more code
|
|
157
|
+
|
|
158
|
+
### Method 4: JavaScript with Callbacks (Most Control)
|
|
159
|
+
|
|
160
|
+
For scenarios where you need to control the flow after initialization:
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
// In your custom JavaScript or via evaluateJavaScript
|
|
164
|
+
if (window.EncoreBridge && window.Encore) {
|
|
165
|
+
window.EncoreBridge.on('encore:initialized', (payload) => {
|
|
166
|
+
console.log('SDK initialized:', payload);
|
|
167
|
+
// Now safe to present offer
|
|
168
|
+
window.postMessage({ type: 'presentOffer', payload: {} }, '*');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
window.EncoreBridge.on('encore:error', (payload) => {
|
|
172
|
+
console.error('Initialization error:', payload);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Trigger initialization
|
|
176
|
+
window.postMessage({
|
|
177
|
+
type: 'configure',
|
|
178
|
+
payload: {
|
|
179
|
+
apiKey: 'pk_live_...',
|
|
180
|
+
userId: 'user_123',
|
|
181
|
+
environment: 'production'
|
|
182
|
+
}
|
|
183
|
+
}, '*');
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Pros**: Full control, proper error handling, no race conditions
|
|
188
|
+
**Cons**: More code, requires understanding of message flow
|
|
189
|
+
|
|
190
|
+
## Usage Examples
|
|
191
|
+
|
|
192
|
+
### Present an Offer
|
|
193
|
+
|
|
194
|
+
```swift
|
|
195
|
+
// From iOS
|
|
196
|
+
let script = """
|
|
197
|
+
window.postMessage({
|
|
198
|
+
type: 'presentOffer',
|
|
199
|
+
payload: {}
|
|
200
|
+
}, '*');
|
|
201
|
+
"""
|
|
202
|
+
webView.evaluateJavaScript(script)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Handle Entitlement Granted
|
|
206
|
+
|
|
207
|
+
```swift
|
|
208
|
+
// In iOS message handler
|
|
209
|
+
func userContentController(_ userContentController: WKUserContentController,
|
|
210
|
+
didReceive message: WKScriptMessage) {
|
|
211
|
+
guard message.name == "encore" else { return }
|
|
212
|
+
|
|
213
|
+
guard let dict = message.body as? [String: Any],
|
|
214
|
+
let type = dict["type"] as? String else {
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
switch type {
|
|
219
|
+
case "encore:entitlementGranted":
|
|
220
|
+
if let payload = dict["payload"] as? [String: Any],
|
|
221
|
+
let entitlement = payload["entitlement"] {
|
|
222
|
+
handleEntitlementGranted(entitlement)
|
|
223
|
+
}
|
|
224
|
+
case "encore:userDeclined":
|
|
225
|
+
if let payload = dict["payload"] as? [String: Any],
|
|
226
|
+
let reason = payload["reason"] as? String {
|
|
227
|
+
handleUserDeclined(reason)
|
|
228
|
+
}
|
|
229
|
+
case "encore:error":
|
|
230
|
+
if let payload = dict["payload"] as? [String: Any] {
|
|
231
|
+
handleError(payload)
|
|
232
|
+
}
|
|
233
|
+
case "encore:openURL":
|
|
234
|
+
if let payload = dict["payload"] as? [String: Any],
|
|
235
|
+
let urlString = payload["url"] as? String,
|
|
236
|
+
let url = URL(string: urlString) {
|
|
237
|
+
openInSafari(url: url)
|
|
238
|
+
}
|
|
239
|
+
default:
|
|
240
|
+
break
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
func openInSafari(url: URL) {
|
|
245
|
+
let safari = SFSafariViewController(url: url)
|
|
246
|
+
safari.delegate = self
|
|
247
|
+
present(safari, animated: true)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// SFSafariViewControllerDelegate
|
|
251
|
+
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
|
|
252
|
+
// User closed Safari, notify webview
|
|
253
|
+
let script = """
|
|
254
|
+
window.postMessage({
|
|
255
|
+
type: 'safariClosed',
|
|
256
|
+
payload: { timestamp: \(Date().timeIntervalSince1970 * 1000) }
|
|
257
|
+
}, '*');
|
|
258
|
+
"""
|
|
259
|
+
webView.evaluateJavaScript(script)
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Bridge API
|
|
264
|
+
|
|
265
|
+
The `EncoreBridge` object provides methods for communication between JavaScript and iOS:
|
|
266
|
+
|
|
267
|
+
### Listening for Messages
|
|
268
|
+
|
|
269
|
+
```javascript
|
|
270
|
+
// Listen for messages (stays subscribed)
|
|
271
|
+
const unsubscribe = bridge.on('encore:initialized', (payload) => {
|
|
272
|
+
console.log('SDK initialized:', payload);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Stop listening
|
|
276
|
+
unsubscribe();
|
|
277
|
+
|
|
278
|
+
// Listen once (automatically unsubscribes after first call)
|
|
279
|
+
bridge.once('encore:initialized', (payload) => {
|
|
280
|
+
console.log('SDK initialized (one-time):', payload);
|
|
281
|
+
// Handler is automatically removed after this
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Sending Messages
|
|
286
|
+
|
|
287
|
+
```javascript
|
|
288
|
+
// Send message to iOS
|
|
289
|
+
bridge.sendToIOS('encore:customEvent', {
|
|
290
|
+
data: 'value',
|
|
291
|
+
timestamp: Date.now()
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Initialization Callbacks
|
|
296
|
+
|
|
297
|
+
The `initializeSDK` function supports optional success/error callbacks to avoid race conditions:
|
|
298
|
+
|
|
299
|
+
```javascript
|
|
300
|
+
// Proper way to auto-present after initialization
|
|
301
|
+
initializeSDK(
|
|
302
|
+
{ apiKey: 'pk_...', userId: 'user_123' },
|
|
303
|
+
() => {
|
|
304
|
+
// Success - SDK is ready
|
|
305
|
+
presentOffer();
|
|
306
|
+
},
|
|
307
|
+
(error) => {
|
|
308
|
+
// Error - handle failure
|
|
309
|
+
console.error('Init failed:', error);
|
|
310
|
+
}
|
|
311
|
+
);
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**⚠️ Important**: Never use `setTimeout()` to wait for initialization. Always use callbacks or listen for the `encore:initialized` message to ensure the SDK is truly ready.
|
|
315
|
+
|
|
316
|
+
## Debug Mode
|
|
317
|
+
|
|
318
|
+
Enable debug mode to see message logs:
|
|
319
|
+
|
|
320
|
+
1. **Via URL**: Add `?debug=true` to the embed URL
|
|
321
|
+
2. **Via Console**: Run `enableEncoreDebug()` in the web inspector
|
|
322
|
+
3. **Persistent**: Once enabled, it's saved to localStorage
|
|
323
|
+
|
|
324
|
+
Debug panel shows:
|
|
325
|
+
- All messages sent/received
|
|
326
|
+
- SDK initialization status
|
|
327
|
+
- Errors and warnings
|
|
328
|
+
- Timestamps
|
|
329
|
+
|
|
330
|
+
## File Structure
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
embed/
|
|
334
|
+
├── index.html # Main HTML page
|
|
335
|
+
├── bridge.js # iOS-JavaScript bridge (SOURCE FILE - vanilla JS)
|
|
336
|
+
├── app.js # Application logic (SOURCE FILE - vanilla JS)
|
|
337
|
+
├── styles.css # Base styles (transparent background)
|
|
338
|
+
├── mobile-styles.css # iOS-native bottom sheet UI
|
|
339
|
+
├── mobile-overrides.css # Web SDK → mobile sheet transforms
|
|
340
|
+
├── dist/
|
|
341
|
+
│ ├── bridge.min.js # Minified bridge (generated)
|
|
342
|
+
│ ├── bridge.min.js.map # Source map (generated)
|
|
343
|
+
│ ├── app.min.js # Minified app (generated)
|
|
344
|
+
│ └── app.min.js.map # Source map (generated)
|
|
345
|
+
├── README.md # This file
|
|
346
|
+
├── MOBILE-UI-GUIDE.md # Comprehensive mobile UI docs
|
|
347
|
+
└── NEXTJS-DEPLOYMENT.md # Deployment guide
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**Note**: `bridge.js` and `app.js` are vanilla JavaScript source files (not TypeScript). They are intentionally written as pure ES6+ to avoid transpilation overhead and are only minified for production.
|
|
351
|
+
|
|
352
|
+
## Building for Production
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
# Build the embed bundle (minifies bridge.js and app.js)
|
|
356
|
+
npm run build:embed
|
|
357
|
+
|
|
358
|
+
# Or build everything (SDK + embed)
|
|
359
|
+
npm run build
|
|
360
|
+
|
|
361
|
+
# Output will be in embed/dist/
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Security Considerations
|
|
365
|
+
|
|
366
|
+
1. **HTTPS Only**: Always use HTTPS for the embed URL
|
|
367
|
+
2. **API Key Security**: API keys are public but should have usage limits
|
|
368
|
+
3. **Message Validation**: Bridge validates all message structures
|
|
369
|
+
4. **CSP Headers**: Content Security Policy should be configured on server
|
|
370
|
+
5. **Origin Validation**: Messages should validate expected origins
|
|
371
|
+
|
|
372
|
+
## Browser Compatibility
|
|
373
|
+
|
|
374
|
+
- iOS 13+ (WKWebView)
|
|
375
|
+
- Modern JavaScript (ES6+)
|
|
376
|
+
- No external dependencies
|
|
377
|
+
|
|
378
|
+
## Troubleshooting
|
|
379
|
+
|
|
380
|
+
### Messages not being received by iOS
|
|
381
|
+
|
|
382
|
+
Check:
|
|
383
|
+
1. Message handler is registered: `userContentController.add(self, name: "encore")`
|
|
384
|
+
2. WKWebView configuration is correct
|
|
385
|
+
3. Debug mode is enabled to see logs
|
|
386
|
+
|
|
387
|
+
### SDK not initializing
|
|
388
|
+
|
|
389
|
+
Check:
|
|
390
|
+
1. API key is valid
|
|
391
|
+
2. Network connectivity
|
|
392
|
+
3. CORS settings (if self-hosting)
|
|
393
|
+
4. Check debug panel for error messages
|
|
394
|
+
|
|
395
|
+
### Offer modal not showing
|
|
396
|
+
|
|
397
|
+
Check:
|
|
398
|
+
1. SDK is initialized (`encore:initialized` received)
|
|
399
|
+
2. Offers are available for this user
|
|
400
|
+
3. CSS/styles are loaded properly
|
|
401
|
+
4. Check console for errors
|
|
402
|
+
|
|
403
|
+
## Support
|
|
404
|
+
|
|
405
|
+
For more information:
|
|
406
|
+
- [Full iOS WebView Integration Guide](../docs/ios-webview-integration.md)
|
|
407
|
+
- [Swift Example Code](../examples/ios-webview/)
|
|
408
|
+
- [Encore Documentation](https://docs.encorekit.com)
|
|
409
|
+
|
package/embed/index.html
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
6
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
7
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
8
|
+
<title>Encore Embed</title>
|
|
9
|
+
<link rel="stylesheet" href="embed/styles.css">
|
|
10
|
+
<!-- Mobile-native styles for iOS bottom sheet UI -->
|
|
11
|
+
<link rel="stylesheet" href="embed/mobile-styles.css">
|
|
12
|
+
<!-- Override Web SDK modal to work as bottom sheet -->
|
|
13
|
+
<link rel="stylesheet" href="embed/mobile-overrides.css">
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<!--
|
|
17
|
+
This page is designed to be embedded in iOS WKWebView.
|
|
18
|
+
It remains transparent until an offer is presented.
|
|
19
|
+
-->
|
|
20
|
+
|
|
21
|
+
<!-- Loading indicator (hidden by default) -->
|
|
22
|
+
<div id="encore-loading" class="encore-loading" style="display: none;">
|
|
23
|
+
<div class="encore-spinner"></div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<!-- Debug panel (only shown in development) -->
|
|
27
|
+
<div id="encore-debug" class="encore-debug" style="display: none;">
|
|
28
|
+
<div class="debug-header">
|
|
29
|
+
<span>Encore Debug</span>
|
|
30
|
+
<div class="debug-controls">
|
|
31
|
+
<button id="debug-minimize" title="Minimize">−</button>
|
|
32
|
+
<button id="debug-close" title="Close">×</button>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<div id="debug-log" class="debug-log"></div>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<!-- Credit Claimed Confirmation View -->
|
|
39
|
+
<div id="encore-claimed" class="encore-claimed-view" style="display: none;">
|
|
40
|
+
<div class="claimed-content">
|
|
41
|
+
<div class="claimed-checkmark">✓</div>
|
|
42
|
+
<h2 class="claimed-title">Credit Claimed!</h2>
|
|
43
|
+
<p class="claimed-message">Your reward has been unlocked</p>
|
|
44
|
+
<button id="claimed-done" class="encore-button encore-button-primary">Done</button>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<!-- Load Encore Web SDK (local build) -->
|
|
49
|
+
<script src="embed/dist/encore.min.js"></script>
|
|
50
|
+
|
|
51
|
+
<!-- Load iOS Bridge -->
|
|
52
|
+
<script src="embed/dist/bridge.min.js"></script>
|
|
53
|
+
|
|
54
|
+
<!-- Load Application Logic -->
|
|
55
|
+
<script src="embed/dist/app.min.js"></script>
|
|
56
|
+
</body>
|
|
57
|
+
</html>
|
package/embed/styles.css
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encore Embed Styles
|
|
3
|
+
* Minimal styles for the embed container
|
|
4
|
+
* The Web SDK provides its own modal styles
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
* {
|
|
8
|
+
margin: 0;
|
|
9
|
+
padding: 0;
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
html, body {
|
|
14
|
+
width: 100%;
|
|
15
|
+
height: 100%;
|
|
16
|
+
/* White background to fill the sheet */
|
|
17
|
+
background: white;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
/* No padding - SwiftUI sheet handles safe areas */
|
|
20
|
+
padding: 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Prevent text selection in embed */
|
|
24
|
+
body {
|
|
25
|
+
-webkit-user-select: none;
|
|
26
|
+
user-select: none;
|
|
27
|
+
-webkit-touch-callout: none;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Loading indicator */
|
|
31
|
+
.encore-loading {
|
|
32
|
+
position: fixed;
|
|
33
|
+
top: 50%;
|
|
34
|
+
left: 50%;
|
|
35
|
+
transform: translate(-50%, -50%);
|
|
36
|
+
z-index: 9997;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.encore-spinner {
|
|
40
|
+
width: 40px;
|
|
41
|
+
height: 40px;
|
|
42
|
+
border: 4px solid rgba(0, 0, 0, 0.1);
|
|
43
|
+
border-top-color: #000;
|
|
44
|
+
border-radius: 50%;
|
|
45
|
+
animation: encore-spin 1s linear infinite;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@keyframes encore-spin {
|
|
49
|
+
to { transform: rotate(360deg); }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Dark mode support */
|
|
53
|
+
@media (prefers-color-scheme: dark) {
|
|
54
|
+
.encore-spinner {
|
|
55
|
+
border-color: rgba(255, 255, 255, 0.1);
|
|
56
|
+
border-top-color: #fff;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Debug panel */
|
|
61
|
+
.encore-debug {
|
|
62
|
+
position: fixed;
|
|
63
|
+
bottom: 0;
|
|
64
|
+
left: 0;
|
|
65
|
+
right: 0;
|
|
66
|
+
max-height: 40vh;
|
|
67
|
+
background: rgba(0, 0, 0, 0.9);
|
|
68
|
+
color: #00ff00;
|
|
69
|
+
font-family: 'Courier New', monospace;
|
|
70
|
+
font-size: 12px;
|
|
71
|
+
z-index: 99999;
|
|
72
|
+
border-top: 2px solid #00ff00;
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-direction: column;
|
|
75
|
+
transition: max-height 0.3s ease;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.encore-debug.minimized {
|
|
79
|
+
max-height: 40px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.encore-debug.minimized .debug-log {
|
|
83
|
+
display: none;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.encore-debug.minimized .debug-header {
|
|
87
|
+
border-bottom: none;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.debug-header {
|
|
91
|
+
display: flex;
|
|
92
|
+
justify-content: space-between;
|
|
93
|
+
align-items: center;
|
|
94
|
+
padding: 8px 12px;
|
|
95
|
+
background: rgba(0, 0, 0, 0.95);
|
|
96
|
+
border-bottom: 1px solid #00ff00;
|
|
97
|
+
font-weight: bold;
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.debug-controls {
|
|
102
|
+
display: flex;
|
|
103
|
+
gap: 8px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#debug-minimize,
|
|
107
|
+
#debug-close {
|
|
108
|
+
background: transparent;
|
|
109
|
+
border: 1px solid #00ff00;
|
|
110
|
+
color: #00ff00;
|
|
111
|
+
width: 24px;
|
|
112
|
+
height: 24px;
|
|
113
|
+
border-radius: 50%;
|
|
114
|
+
cursor: pointer;
|
|
115
|
+
font-size: 16px;
|
|
116
|
+
line-height: 1;
|
|
117
|
+
padding: 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#debug-minimize:active,
|
|
121
|
+
#debug-close:active {
|
|
122
|
+
background: rgba(0, 255, 0, 0.2);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.debug-log {
|
|
126
|
+
flex: 1;
|
|
127
|
+
overflow-y: auto;
|
|
128
|
+
padding: 8px 12px;
|
|
129
|
+
-webkit-overflow-scrolling: touch;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.debug-entry {
|
|
133
|
+
margin-bottom: 4px;
|
|
134
|
+
word-break: break-word;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.debug-entry.error {
|
|
138
|
+
color: #ff0000;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.debug-entry.warning {
|
|
142
|
+
color: #ffaa00;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.debug-entry.info {
|
|
146
|
+
color: #00aaff;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Ensure Web SDK modal appears above everything */
|
|
150
|
+
:root {
|
|
151
|
+
--encore-modal-z-index: 9998;
|
|
152
|
+
--encore-backdrop-z-index: 9997;
|
|
153
|
+
}
|
|
154
|
+
|