mirror of
https://github.com/selfxyz/self.git
synced 2026-04-27 03:01:15 -04:00
* feat: add iOS native shell package (NSL-02) Plain Swift implementation of the WebView host with bridge handlers for secure storage (Keychain), crypto (EC P-256), and lifecycle. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add Android native shell package (NSL-01) Plain Kotlin implementation of the WebView host with bridge handlers for secure storage (EncryptedSharedPreferences), crypto (Android Keystore EC P-256), and lifecycle. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: integrate Sumsub Web SDK into ProviderLaunchScreen (WV-05) Rewrites ProviderLaunchScreen to launch Sumsub Web SDK, adds KYC provider types, result normalization, and a ProviderResultScreen for displaying verification outcomes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: update spec status for NSL-01, NSL-02, WV-05 to in-progress All three items are code-complete but need integration testing before marking done. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add build-pipeline workstream specs, update NSL-03 and BP-01 status Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add WebView bundle build pipeline (BP-01) Build script copies webview-app dist into both native shell asset directories. Gradle preBuild validation fails fast when bundle is missing. Root package.json gets build:sdk-* scripts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add SDK test apps for Android and iOS (NSL-03) Minimal test apps to exercise native shells end-to-end: - Android: Jetpack Compose app using SelfSdk.launch() via composite build - iOS: SwiftUI app using SelfSdk.createViewController() via local SPM dep Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * update lockfile * fix: address CodeRabbit PR review findings for native shells - Fix iOS double callback: add hasEmittedResult flag to LifecycleHandler so dismiss() won't fire onCancelled after onResult already emitted - Fix Android error result codes: use RESULT_FIRST_USER for failed verifications instead of always RESULT_OK; add dedicated handler in SelfSdk.handleResult - Fix iOS production query params: append params to file URL via URLComponents so WebView receives teeUrl/verificationId/userId - Fix build:sdk-ios false-green: chain swift build after bundle script - Add expectedRequestCode param to handleResult for flexibility - Upgrade security-crypto 1.1.0-alpha06 → 1.1.0 stable - Improve callback type safety: onSuccess takes raw JSON string, onFailure takes SelfSdkException instead of generic Exception - Add requireBiometric intent comments to both SecureStorageHandlers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address remaining CodeRabbit PR review findings (round 2) - iOS BridgeResponse: add requestId/success fields, rename result→data to match JS bridge contract - iOS test app: fix callback deallocation with Coordinator pattern - ProviderLaunchScreen: fail closed on missing verificationId, fix retry via retryCount state - ProviderResultScreen: guard unknown status with fallback to error config - build-webview-bundle.sh: validate index.html before deleting targets - Package.swift: fix SPM resource path with target path/sources Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
98 lines
3.0 KiB
Swift
98 lines
3.0 KiB
Swift
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
import Foundation
|
|
import UIKit
|
|
import WebKit
|
|
|
|
final class SelfWebViewHost: NSObject {
|
|
private var webView: WKWebView?
|
|
private let router: MessageRouter
|
|
private let isDebugMode: Bool
|
|
|
|
init(router: MessageRouter, isDebugMode: Bool = false) {
|
|
self.router = router
|
|
self.isDebugMode = isDebugMode
|
|
super.init()
|
|
}
|
|
|
|
func createWebView() -> WKWebView {
|
|
let config = WKWebViewConfiguration()
|
|
let contentController = WKUserContentController()
|
|
contentController.add(WeakScriptMessageProxy(handler: self), name: "SelfNativeIOS")
|
|
config.userContentController = contentController
|
|
config.preferences.javaScriptCanOpenWindowsAutomatically = false
|
|
|
|
let webView = WKWebView(frame: .zero, configuration: config)
|
|
webView.scrollView.bounces = false
|
|
webView.isOpaque = false
|
|
webView.backgroundColor = .clear
|
|
|
|
if #available(iOS 16.4, *) {
|
|
webView.isInspectable = isDebugMode
|
|
}
|
|
|
|
self.webView = webView
|
|
return webView
|
|
}
|
|
|
|
func loadContent(queryParams: String) {
|
|
guard let webView = webView else { return }
|
|
|
|
if isDebugMode {
|
|
let urlString = "http://localhost:5173?\(queryParams)"
|
|
if let url = URL(string: urlString) {
|
|
webView.load(URLRequest(url: url))
|
|
}
|
|
} else {
|
|
guard let bundlePath = Bundle.main.path(forResource: "self-sdk-web", ofType: nil) else {
|
|
return
|
|
}
|
|
let fileURL = URL(fileURLWithPath: "\(bundlePath)/index.html")
|
|
let bundleURL = URL(fileURLWithPath: bundlePath)
|
|
|
|
var components = URLComponents(url: fileURL, resolvingAgainstBaseURL: false)
|
|
if !queryParams.isEmpty {
|
|
components?.query = queryParams
|
|
}
|
|
let targetURL = components?.url ?? fileURL
|
|
|
|
webView.loadFileURL(targetURL, allowingReadAccessTo: bundleURL)
|
|
}
|
|
}
|
|
|
|
func evaluateJs(_ js: String) {
|
|
DispatchQueue.main.async { [weak self] in
|
|
self?.webView?.evaluateJavaScript(js, completionHandler: nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension SelfWebViewHost: WKScriptMessageHandler {
|
|
func userContentController(
|
|
_ userContentController: WKUserContentController,
|
|
didReceive message: WKScriptMessage
|
|
) {
|
|
guard message.name == "SelfNativeIOS",
|
|
let body = message.body as? String else {
|
|
return
|
|
}
|
|
router.onMessageReceived(rawJson: body)
|
|
}
|
|
}
|
|
|
|
// Prevents WKWebView retain cycle with WKScriptMessageHandler
|
|
private final class WeakScriptMessageProxy: NSObject, WKScriptMessageHandler {
|
|
private weak var handler: WKScriptMessageHandler?
|
|
|
|
init(handler: WKScriptMessageHandler) {
|
|
self.handler = handler
|
|
}
|
|
|
|
func userContentController(
|
|
_ userContentController: WKUserContentController,
|
|
didReceive message: WKScriptMessage
|
|
) {
|
|
handler?.userContentController(userContentController, didReceive: message)
|
|
}
|
|
}
|