Files
self/packages/native-shell-ios/Sources/SelfNativeShell/WebView/SelfWebViewHost.swift
Nesopie 2f2ec3abe6 Feat/didit webapp (#1882)
* feat: replace Sumsub with Didit JS SDK in webview-app

- Add @didit-protocol/sdk-web, remove @sumsub/websdk
- Create diditProvider.ts with session creation + SDK launch
- Update ProviderLaunchScreen to use Didit embedded mode
- Delete sumsubProvider.ts and sumsub-websdk.d.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add Socket.IO attestation flow to webview KYC

After Didit JS SDK completes, connect Socket.IO to the TEE,
subscribe by sessionId, and wait for signed KYC data (attestation).
Emit ack_success for session cleanup. Attach attestation to the
provider result before navigating to the result screen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: update TEE URL to kyc.self.xyz, update SDK test app README for Didit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: only route KYC (Other IDs) to Didit provider, others to Coming Soon

Passport, ID card, and Aadhaar require NFC/MRZ scanning which isn't
available in the WebView. Only "Other IDs" goes through the Didit
JS SDK flow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: Didit SDK full-width rendering and KYC routing

- Wire onNotListedPress to launch Didit for "View other supported IDs"
- Remove verificationId gate from ProviderLaunchScreen
- Switch to modal mode with CSS overrides for full-screen on mobile
- Force .shadow-card to 100% width/height in WebView context

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add camera permissions and file upload to Android WebView

Add WebChromeClient to AndroidWebViewHost:
- onPermissionRequest: auto-grants camera for Didit SDK
- onShowFileChooser: opens system file picker for document upload
- SelfVerificationActivity handles file chooser result callback

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: gitignore Gradle build artifacts for native-shell-android

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add runtime camera permission and CAMERA manifest declaration

- Add CAMERA permission to sdk-test-app AndroidManifest.xml
- Request runtime camera permission in onPermissionRequest before granting
- Handle permission result in SelfVerificationActivity
- Store pending PermissionRequest for async grant/deny after user response

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix ios camera

* fix: address CodeRabbit review findings

- Replace ngrok URL with kyc.self.xyz in Android and iOS test apps
- Fix file chooser hang when context is not an Activity
- Move NSCameraUsageDescription to project.yml (survives xcodegen regen)
- Delete manual Info.plist that would be overwritten

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: replace ngrok URL with kyc.self.xyz in diditProvider and diditAttestation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: explicitly disable Didit SDK debug logging

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: webview lint

* fix: validate origin and handle audio permission in WebView permission grants

- Deny permission requests from untrusted origins
- Deny instead of grant when context is not an Activity
- Handle RECORD_AUDIO alongside CAMERA for liveness checks
- Add RECORD_AUDIO to AndroidManifest.xml

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: seshanthS <seshanth@protonmail.com>
2026-03-30 15:53:14 +05:30

100 lines
3.1 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
config.allowsInlineMediaPlayback = true
config.mediaTypesRequiringUserActionForPlayback = []
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.module.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)
}
}