@encorekit/web-sdk 2.0.3 → 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.
- package/README.md +112 -40
- 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 +82 -367
- package/examples/ios-webview/EncoreURLBuilder.swift +179 -61
- package/examples/ios-webview/README.md +75 -354
- package/examples/ios-webview/SimpleEncoreView.swift +49 -36
- package/examples/ios-webview/SimpleExample.swift +30 -86
- package/package.json +3 -7
- package/types/analytics/DiagnosticsOutbox.d.ts +22 -0
- package/types/analytics/DiagnosticsOutbox.d.ts.map +1 -0
- package/types/analytics/ReliableOutbox.d.ts +40 -0
- package/types/analytics/ReliableOutbox.d.ts.map +1 -0
- package/types/analytics/events.d.ts +100 -0
- package/types/analytics/events.d.ts.map +1 -1
- package/types/analytics/placementResolved.d.ts +150 -0
- package/types/analytics/placementResolved.d.ts.map +1 -0
- package/types/api/APIClient.d.ts +10 -2
- package/types/api/APIClient.d.ts.map +1 -1
- package/types/api/errorReportLimits.d.ts +50 -0
- package/types/api/errorReportLimits.d.ts.map +1 -0
- package/types/api/generated/types.d.ts +54 -7
- package/types/api/generated/types.d.ts.map +1 -1
- package/types/core/Configuration.d.ts +5 -0
- package/types/core/Configuration.d.ts.map +1 -1
- package/types/core/Encore.d.ts +61 -2
- package/types/core/Encore.d.ts.map +1 -1
- package/types/core/OfferManager.d.ts +29 -3
- package/types/core/OfferManager.d.ts.map +1 -1
- package/types/core/PlacementBuilder.d.ts +26 -2
- package/types/core/PlacementBuilder.d.ts.map +1 -1
- package/types/core/StrictUnlock.d.ts +20 -0
- package/types/core/StrictUnlock.d.ts.map +1 -0
- package/types/core/StrictUnlockReconciler.d.ts +94 -0
- package/types/core/StrictUnlockReconciler.d.ts.map +1 -0
- package/types/core/TransactionStore.d.ts +57 -13
- package/types/core/TransactionStore.d.ts.map +1 -1
- package/types/core/VerificationPoller.d.ts.map +1 -1
- package/types/core/claimBehavior.d.ts +28 -0
- package/types/core/claimBehavior.d.ts.map +1 -0
- package/types/core/errorContext.d.ts +18 -0
- package/types/core/errorContext.d.ts.map +1 -0
- package/types/core/errorType.d.ts +59 -0
- package/types/core/errorType.d.ts.map +1 -0
- package/types/core/hostDisplay.d.ts +50 -0
- package/types/core/hostDisplay.d.ts.map +1 -0
- package/types/core/useCase.d.ts +21 -0
- package/types/core/useCase.d.ts.map +1 -1
- package/types/core/version.d.ts +1 -1
- package/types/experiments/ExposureOutbox.d.ts +4 -28
- package/types/experiments/ExposureOutbox.d.ts.map +1 -1
- package/types/types.d.ts +188 -3
- package/types/types.d.ts.map +1 -1
- package/types/ui/OfferCard.d.ts +3 -1
- package/types/ui/OfferCard.d.ts.map +1 -1
- package/types/ui/OfferCarousel.d.ts +5 -0
- package/types/ui/OfferCarousel.d.ts.map +1 -1
- package/types/ui/OfferModal.d.ts +20 -0
- package/types/ui/OfferModal.d.ts.map +1 -1
- package/types/ui/PrimerScreen.d.ts +18 -11
- package/types/ui/PrimerScreen.d.ts.map +1 -1
- package/types/ui/RedemptionScreen.d.ts +15 -19
- package/types/ui/RedemptionScreen.d.ts.map +1 -1
- package/types/ui/RewardActions.d.ts +35 -0
- package/types/ui/RewardActions.d.ts.map +1 -1
- package/types/ui/appearance.d.ts +14 -0
- package/types/ui/appearance.d.ts.map +1 -1
- package/types/ui/brandComponents.d.ts.map +1 -1
- package/types/ui/styles.d.ts.map +1 -1
- package/types/utils/logger.d.ts +11 -1
- package/types/utils/logger.d.ts.map +1 -1
- package/embed/index.html +0 -57
- package/embed/styles.css +0 -154
- package/examples/ios-webview/EncoreWebViewBridge.swift +0 -419
- package/examples/ios-webview/ExampleViewController.swift +0 -233
package/embed/README.md
CHANGED
|
@@ -1,409 +1,124 @@
|
|
|
1
|
-
# Encore
|
|
1
|
+
# Encore Hosted Offer Page
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A publisher who cannot install an SDK opens one URL in a plain webview. Encore
|
|
4
|
+
hosts the page, and the entire configuration surface is the query string.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
There is no native bridge on any platform. Nothing is posted back to the host
|
|
7
|
+
app, and no message handler needs registering. If you are looking for
|
|
8
|
+
`webkit.messageHandlers`, a `WKScriptMessageHandler`, or an `encore:*` message
|
|
9
|
+
protocol, they were removed: the bridge is what made the old embed iOS only,
|
|
10
|
+
and dropping it is what made this page work everywhere.
|
|
6
11
|
|
|
7
|
-
The
|
|
12
|
+
## The URL
|
|
8
13
|
|
|
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? }` | 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
14
|
```
|
|
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))
|
|
15
|
+
https://encorekit.com/offers?apiKey=pk_live_...&userId=user_123
|
|
129
16
|
```
|
|
130
17
|
|
|
131
|
-
|
|
18
|
+
`/embed` and `/payrange` serve the same page, so URLs already shipped against
|
|
19
|
+
them keep working. Prefer `/offers` for anything new.
|
|
132
20
|
|
|
133
|
-
|
|
21
|
+
Open it in whatever webview the platform gives you and load the request. That
|
|
22
|
+
is the whole integration.
|
|
134
23
|
|
|
135
24
|
```swift
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
type: 'configure',
|
|
144
|
-
payload: {
|
|
145
|
-
apiKey: '\(apiKey)',
|
|
146
|
-
userId: '\(userId)',
|
|
147
|
-
environment: 'production'
|
|
148
|
-
}
|
|
149
|
-
}, '*');
|
|
150
|
-
"""
|
|
151
|
-
webView.evaluateJavaScript(config)
|
|
25
|
+
guard var components = URLComponents(string: "https://encorekit.com/offers") else { return }
|
|
26
|
+
components.queryItems = [
|
|
27
|
+
URLQueryItem(name: "apiKey", value: apiKey),
|
|
28
|
+
URLQueryItem(name: "userId", value: userId),
|
|
29
|
+
]
|
|
30
|
+
if let url = components.url {
|
|
31
|
+
webView.load(URLRequest(url: url))
|
|
152
32
|
}
|
|
153
33
|
```
|
|
154
34
|
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
```
|
|
35
|
+
Build the query string with `URLComponents` rather than interpolating into a
|
|
36
|
+
string, so values are percent encoded for you.
|
|
204
37
|
|
|
205
|
-
|
|
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
|
-
```
|
|
38
|
+
## Parameters
|
|
262
39
|
|
|
263
|
-
|
|
40
|
+
`apiKey` and `userId` are both required. Without either, the page shows an
|
|
41
|
+
error state naming what is missing rather than a blank screen.
|
|
264
42
|
|
|
265
|
-
|
|
43
|
+
| Parameter | Notes |
|
|
44
|
+
|-----------|-------|
|
|
45
|
+
| `apiKey` | Your publishable key |
|
|
46
|
+
| `userId` | Your identifier for the end user |
|
|
266
47
|
|
|
267
|
-
|
|
48
|
+
Optional.
|
|
268
49
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
50
|
+
| Parameter | Default | Notes |
|
|
51
|
+
|-----------|---------|-------|
|
|
52
|
+
| `environment` | `production` | One of `production`, `development`, `staging`, `localhost` |
|
|
53
|
+
| `useCase` | `rewardUsers` | Or `reduceChurn` for the churn carousel |
|
|
54
|
+
| `placement` | `hosted_reward` or `hosted_offer` | Follows `useCase` so the two stay separable in reporting |
|
|
55
|
+
| `autoPresentOffer` | `true` | Set `false` to suppress the automatic presentation |
|
|
56
|
+
| `headline` | | Overrides the default headline |
|
|
57
|
+
| `subheadline` | | Overrides the default subheadline |
|
|
274
58
|
|
|
275
|
-
|
|
276
|
-
|
|
59
|
+
The default `useCase` here is `rewardUsers`, which deliberately differs from the
|
|
60
|
+
Web SDK's own `reduceChurn`. The SDK default suits a save attempt fired as
|
|
61
|
+
someone leaves. This page is opened deliberately by your app, and that moment is
|
|
62
|
+
a reward far more often than it is a cancellation.
|
|
277
63
|
|
|
278
|
-
|
|
279
|
-
bridge.once('encore:initialized', (payload) => {
|
|
280
|
-
console.log('SDK initialized (one-time):', payload);
|
|
281
|
-
// Handler is automatically removed after this
|
|
282
|
-
});
|
|
283
|
-
```
|
|
64
|
+
## Targeting attributes
|
|
284
65
|
|
|
285
|
-
|
|
66
|
+
Attributes are flat query parameters, one per key. An earlier revision of these
|
|
67
|
+
examples packed them into a single JSON `attributes=` parameter; that form is
|
|
68
|
+
ignored.
|
|
286
69
|
|
|
287
|
-
```javascript
|
|
288
|
-
// Send message to iOS
|
|
289
|
-
bridge.sendToIOS('encore:customEvent', {
|
|
290
|
-
data: 'value',
|
|
291
|
-
timestamp: Date.now()
|
|
292
|
-
});
|
|
293
70
|
```
|
|
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
|
-
);
|
|
71
|
+
&countryCode=US&subscriptionTier=premium&custom.plan=gold
|
|
312
72
|
```
|
|
313
73
|
|
|
314
|
-
|
|
74
|
+
Accepted: `countryCode`, `postalCode`, `city`, `state`, `language`,
|
|
75
|
+
`subscriptionTier`, `monthsSubscribed`, `billingCycle`, `lastPaymentAmount`,
|
|
76
|
+
`lastActiveDate`, `totalSessions`, and any `custom.` prefixed key.
|
|
315
77
|
|
|
316
|
-
|
|
78
|
+
Custom keys are capped at 20 per URL and every value is truncated at 256
|
|
79
|
+
characters. An unknown parameter is ignored rather than forwarded.
|
|
317
80
|
|
|
318
|
-
|
|
81
|
+
### Refused on purpose
|
|
319
82
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
83
|
+
`email`, `firstName`, `lastName`, `phoneNumber`, `dateOfBirth`, `gender`,
|
|
84
|
+
`latitude` and `longitude` are dropped, and the page reports that it dropped
|
|
85
|
+
them. A query string lands in webview history and in hosting access logs, so
|
|
86
|
+
direct identifiers and precise location do not belong in one. If you genuinely
|
|
87
|
+
need one, ask for a short lived signed token instead of sending the raw value.
|
|
323
88
|
|
|
324
|
-
|
|
325
|
-
- All messages sent/received
|
|
326
|
-
- SDK initialization status
|
|
327
|
-
- Errors and warnings
|
|
328
|
-
- Timestamps
|
|
89
|
+
## Learning about the entitlement
|
|
329
90
|
|
|
330
|
-
|
|
91
|
+
The page does not call your app back. Your backend asks Encore instead:
|
|
331
92
|
|
|
332
93
|
```
|
|
333
|
-
|
|
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
|
|
94
|
+
GET https://api.encorekit.com/encore/publisher/sdk/v1/entitlements/server?userId=<userId>
|
|
348
95
|
```
|
|
349
96
|
|
|
350
|
-
|
|
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
|
-
```
|
|
97
|
+
authenticated with your API key and HMAC signature. Poll it when the webview
|
|
98
|
+
closes, or on your own schedule.
|
|
363
99
|
|
|
364
|
-
##
|
|
100
|
+
## One thing to know about webviews
|
|
365
101
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
102
|
+
When the user claims an offer, the page navigates the whole webview to the
|
|
103
|
+
advertiser rather than opening a second window. `window.open` returns null in a
|
|
104
|
+
bare `WebView` or `WKWebView` unless the host app implements the multiple window
|
|
105
|
+
callbacks, so a top level navigation is the only handoff that works without any
|
|
106
|
+
cooperation from you. Expect the webview to leave the offer page at that point.
|
|
371
107
|
|
|
372
|
-
##
|
|
108
|
+
## What used to be here
|
|
373
109
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
110
|
+
This directory once held a static embed page (`index.html`, `bridge.js`,
|
|
111
|
+
`app.js` and its CSS) that spoke a `webkit.messageHandlers` bridge to a native
|
|
112
|
+
iOS host. Those files are deleted. Nothing served them: their build output was
|
|
113
|
+
gitignored and never published, the page loaded a bundle no build produced, and
|
|
114
|
+
the copy that briefly hosted them was removed from the website separately.
|
|
377
115
|
|
|
378
|
-
|
|
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
|
|
116
|
+
This README is all that remains, because the URL contract above is the
|
|
117
|
+
integration that replaced them.
|
|
402
118
|
|
|
403
119
|
## Support
|
|
404
120
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
- [Swift
|
|
408
|
-
- [Encore
|
|
409
|
-
|
|
121
|
+
- **[Hosted Offer Page guide](https://docs.encorekit.com/publishers/web/guides/hosted-offer-page)** for the full integration walkthrough,
|
|
122
|
+
including the backend entitlement check
|
|
123
|
+
- [Swift examples](../examples/ios-webview/) for a URL builder you can copy
|
|
124
|
+
- [Encore documentation](https://docs.encorekit.com)
|