Files
self/packages/native-shell-ios/Sources/SelfNativeShell/API/SelfSdk.swift
Seshanth.S 60de8d6c1e Feat/webview sdk (#1856)
* 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>
2026-03-24 02:26:40 +05:30

78 lines
2.4 KiB
Swift

// SPDX-License-Identifier: BUSL-1.1
import Foundation
import UIKit
public final class SelfSdk {
public static func createViewController(
config: SelfSdkConfig,
callback: SelfSdkCallback
) -> UIViewController {
let viewController = SelfSdkViewController(config: config, callback: callback)
viewController.modalPresentationStyle = .fullScreen
return viewController
}
}
final class SelfSdkViewController: UIViewController {
private let config: SelfSdkConfig
private weak var callback: SelfSdkCallback?
private var webViewHost: SelfWebViewHost?
init(config: SelfSdkConfig, callback: SelfSdkCallback) {
self.config = config
self.callback = callback
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) is not supported")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
setupWebView()
}
private func setupWebView() {
let lifecycleHandler = LifecycleHandler(
viewController: self,
onResult: { [weak self] result in
if let dict = result as? [String: Any] {
self?.callback?.onSuccess(result: dict)
} else {
self?.callback?.onSuccess(result: [:])
}
},
onDismiss: { [weak self] in
self?.callback?.onCancelled()
}
)
let router = MessageRouter { [weak self] js in
self?.webViewHost?.evaluateJs(js)
}
router.register(handler: SecureStorageHandler())
router.register(handler: CryptoHandler())
router.register(handler: lifecycleHandler)
let host = SelfWebViewHost(router: router, isDebugMode: config.isDebugMode)
self.webViewHost = host
let webView = host.createWebView()
webView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(webView)
NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo: view.topAnchor),
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
host.loadContent(queryParams: config.toQueryParams())
}
}