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