capacitor-didit-plugin 0.1.0

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.
@@ -0,0 +1,20 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'CapacitorDiditPlugin'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.license = package['license']
10
+ s.homepage = package['repository']['url']
11
+ s.author = package['author']
12
+ s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13
+ s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
14
+ s.ios.deployment_target = '15.0'
15
+ s.dependency 'Capacitor'
16
+ # DiditSDK is not published to the CocoaPods trunk — the consuming app's
17
+ # Podfile must declare it with its podspec URL (see README installation).
18
+ s.dependency 'DiditSDK/All'
19
+ s.swift_version = '5.1'
20
+ end
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aurora Systems
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/Package.swift ADDED
@@ -0,0 +1,30 @@
1
+ // swift-tools-version: 5.9
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "CapacitorDiditPlugin",
6
+ platforms: [.iOS(.v15)],
7
+ products: [
8
+ .library(
9
+ name: "CapacitorDiditPlugin",
10
+ targets: ["DiditVerificationPlugin"])
11
+ ],
12
+ dependencies: [
13
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"),
14
+ .package(url: "https://github.com/didit-protocol/sdk-ios.git", from: "4.1.0")
15
+ ],
16
+ targets: [
17
+ .target(
18
+ name: "DiditVerificationPlugin",
19
+ dependencies: [
20
+ .product(name: "Capacitor", package: "capacitor-swift-pm"),
21
+ .product(name: "Cordova", package: "capacitor-swift-pm"),
22
+ .product(name: "DiditSDK", package: "sdk-ios")
23
+ ],
24
+ path: "ios/Sources/DiditVerificationPlugin"),
25
+ .testTarget(
26
+ name: "DiditVerificationPluginTests",
27
+ dependencies: ["DiditVerificationPlugin"],
28
+ path: "ios/Tests/DiditVerificationPluginTests")
29
+ ]
30
+ )
package/README.md ADDED
@@ -0,0 +1,162 @@
1
+ # capacitor-didit-plugin
2
+
3
+ Capacitor plugin that wraps the native [Didit](https://didit.me) identity-verification (KYC) SDKs for iOS and Android, so verification (document capture, liveness, NFC passport reading) runs fully in-app — no browser window.
4
+
5
+ - iOS: [didit-protocol/sdk-ios](https://github.com/didit-protocol/sdk-ios) (`DiditSDK` 4.x)
6
+ - Android: [didit-protocol/sdk-android](https://github.com/didit-protocol/sdk-android) (`me.didit:didit-sdk` 4.x)
7
+
8
+ Requires **Capacitor 8**, iOS 15+, Android minSdk 24 (Capacitor 8 defaults).
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install capacitor-didit-plugin
14
+ npx cap sync
15
+ ```
16
+
17
+ The plugin is auto-registered by Capacitor on both platforms — do **not** add `registerPlugin` calls in `MainActivity`, custom `CAPBridgeViewController` subclasses, or `packageClassList` patch scripts.
18
+
19
+ ### iOS setup
20
+
21
+ **Swift Package Manager apps (the default for new Capacitor 8 projects): no dependency steps.** The plugin ships a `Package.swift` that pulls `DiditSDK` automatically — `npx cap sync ios` is enough. Skip to step 2 (Info.plist).
22
+
23
+ **CocoaPods apps (projects migrated from Capacitor ≤ 7):**
24
+
25
+ **1. Add the Didit SDK pod to your app's Podfile.** The Didit SDK is not published to the CocoaPods trunk; it is resolved from a podspec URL. In `ios/App/Podfile`, add this line inside the `target 'App' do` block (below `capacitor_pods` — Capacitor rewrites the `capacitor_pods` section on every sync, but lines you add directly in the target block persist):
26
+
27
+ ```ruby
28
+ target 'App' do
29
+ capacitor_pods
30
+ # Add your Pods here
31
+ pod 'DiditSDK/All', :podspec => 'https://raw.githubusercontent.com/didit-protocol/sdk-ios/main/DiditSDK.podspec'
32
+ end
33
+ ```
34
+
35
+ Use the `All` subspec — the plugin's podspec depends on `DiditSDK/All`.
36
+
37
+ **2. Add usage descriptions to `ios/App/App/Info.plist`:**
38
+
39
+ ```xml
40
+ <key>NSCameraUsageDescription</key>
41
+ <string>Camera access is required to scan your identity document and verify liveness.</string>
42
+ <key>NSMicrophoneUsageDescription</key>
43
+ <string>Microphone access is required for liveness video verification.</string>
44
+ <key>NSPhotoLibraryUsageDescription</key>
45
+ <string>Photo library access lets you upload document images.</string>
46
+ ```
47
+
48
+ If you use NFC passport reading, also follow [Didit's NFC setup](https://github.com/didit-protocol/sdk-ios#nfc) (NFC capability, `NFCReaderUsageDescription`, ISO7816 select identifiers).
49
+
50
+ **3. Run `npx cap sync ios`** (which runs `pod install`).
51
+
52
+ #### iOS troubleshooting
53
+
54
+ - **`pod install` fails with "unknown project version" / cannot open `project.pbxproj`** — your project was saved in a new Xcode format (objectVersion 70, Xcode 16.3+/26) that older CocoaPods tooling can't parse. Update the `xcodeproj` gem to ≥ 1.28: `gem install xcodeproj` (or `sudo gem update xcodeproj`), then re-run `pod install`.
55
+ - **"Unable to find a specification for `DiditSDK/All`"** — the `pod 'DiditSDK/All', :podspec => …` line is missing from your Podfile (step 1).
56
+
57
+ ### Android setup
58
+
59
+ The plugin's `build.gradle` declares the Didit Maven repository (`raw.githubusercontent.com/didit-protocol/sdk-android`) and JitPack (needed for some of the SDK's transitive dependencies) and injects them into the consuming build, applies Kotlin itself, and pins the Kotlin/Java toolchain to JDK 21 — your app needs **no** repository or Kotlin configuration in the default Capacitor project layout. A plain Capacitor 8 app builds with no extra steps.
60
+
61
+ **(Only if your `settings.gradle` centralizes repositories.)** If you use `dependencyResolutionManagement` with `RepositoriesMode.FAIL_ON_PROJECT_REPOS` or `PREFER_SETTINGS`, project-level repositories (including the ones this plugin injects) are ignored — add them centrally instead:
62
+
63
+ ```groovy
64
+ dependencyResolutionManagement {
65
+ repositories {
66
+ google()
67
+ mavenCentral()
68
+ maven { url "https://raw.githubusercontent.com/didit-protocol/sdk-android/main/repository" }
69
+ maven { url "https://jitpack.io" }
70
+ }
71
+ }
72
+ ```
73
+
74
+ The default Capacitor app template does not use `dependencyResolutionManagement`, so most apps can skip this.
75
+
76
+ #### Android troubleshooting
77
+
78
+ - **`Unsupported class file major version 69` (or similar) when Gradle starts** — your default JDK is newer than Capacitor 8's Gradle supports. Build with JDK 21, e.g. `export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"` (Android Studio's bundled JDK) before running Gradle, or install Temurin 21.
79
+ - **`Could not find me.didit:didit-sdk`** — your build ignores project-level repositories; see the `dependencyResolutionManagement` note above.
80
+ - **`2 files found with path 'META-INF/versions/9/OSGI-INF/MANIFEST.MF'`** — happens when another dependency in your app ships the same multi-release-jar manifest as one of the Didit SDK's transitive dependencies (seen e.g. alongside OneSignal). Add the exclusion to `android/app/build.gradle` inside the `android { }` block:
81
+
82
+ ```groovy
83
+ packagingOptions {
84
+ resources {
85
+ excludes += 'META-INF/versions/9/OSGI-INF/MANIFEST.MF'
86
+ }
87
+ }
88
+ ```
89
+ - **Out-of-memory / metaspace errors during build** — the Didit SDK pulls in Jetpack Compose; raise the Gradle heap in `android/gradle.properties`: `org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=512m`.
90
+ - **Kotlin `jvmTarget` mismatch errors from *other* plugins** — some third-party Kotlin plugins don't pin a jvmTarget and default to your running JDK. Pin every Kotlin module in your app's root `android/build.gradle`:
91
+
92
+ ```groovy
93
+ subprojects {
94
+ plugins.withId('org.jetbrains.kotlin.android') {
95
+ project.extensions.getByName('kotlin').jvmToolchain(21)
96
+ }
97
+ }
98
+ ```
99
+
100
+ (This plugin already pins its own toolchain; the snippet is only for others that don't.)
101
+ - **R8 full mode issues in release builds** — the SDK ships consumer ProGuard rules; if you still hit R8 full-mode problems, set `android.enableR8.fullMode=false` in `gradle.properties`.
102
+
103
+ ## Usage
104
+
105
+ Create a verification session **from your backend** (Didit `POST /v2/session/`), pass the session token to the app, then:
106
+
107
+ ```typescript
108
+ import { DiditVerification } from 'capacitor-didit-plugin';
109
+
110
+ async function verifyIdentity(sessionToken: string) {
111
+ try {
112
+ const result = await DiditVerification.startVerification({ sessionToken });
113
+ // result.status: 'Approved' | 'Pending' | 'Declined'
114
+ // result.sessionId: reconcile with your backend / webhooks
115
+ return result;
116
+ } catch (err: any) {
117
+ if (err.code === 'CANCELLED') {
118
+ // user closed the flow
119
+ }
120
+ throw err;
121
+ }
122
+ }
123
+ ```
124
+
125
+ `Pending` means the session completed but the decision isn't final (processing or manual review) — treat the Didit webhook / API status on your backend as the source of truth.
126
+
127
+ ## API
128
+
129
+ ### `startVerification(options: { sessionToken: string }): Promise<DiditVerificationResult>`
130
+
131
+ Launches the native Didit verification flow and resolves when the user finishes.
132
+
133
+ ```typescript
134
+ interface DiditVerificationResult {
135
+ status: 'Approved' | 'Pending' | 'Declined';
136
+ sessionId: string;
137
+ }
138
+ ```
139
+
140
+ On web the call rejects as unavailable — gate it with `Capacitor.isNativePlatform()` and fall back to Didit's hosted web verification URL.
141
+
142
+ ### Error codes
143
+
144
+ | `err.code` | Meaning |
145
+ | --------------- | ----------------------------------------------------------- |
146
+ | `MISSING_TOKEN` | `sessionToken` was absent or empty. |
147
+ | `BUSY` | A verification flow is already running. |
148
+ | `UNAVAILABLE` | No activity / view controller to present from, or platform is web. |
149
+ | `CANCELLED` | The user exited the flow before completing it. |
150
+ | `FAILED` | The SDK reported an error — see the error message. |
151
+
152
+ ## Migrating from an in-app (local) plugin
153
+
154
+ If you previously wired the Didit SDK into your app project directly, remove the old wiring after installing this package:
155
+
156
+ - **iOS:** delete the local `DiditVerificationPlugin.swift` / bridge files, the custom `ViewController` subclass with `registerPluginInstance`, any `capacitor.config.json` `packageClassList` patch script, and restore `Main.storyboard`'s controller class if you changed it. Keep the `pod 'DiditSDK/All'` Podfile line.
157
+ - **Android:** delete the local `DiditVerificationPlugin.kt`, remove `registerPlugin(DiditVerificationPlugin.class)` from `MainActivity`, and remove `me.didit:didit-sdk` plus the Didit Maven repo from your **app** module (the plugin brings both). Your app also no longer needs `apply plugin: 'kotlin-android'` or the Kotlin classpath if nothing else uses Kotlin.
158
+ - **JS:** replace imports of the local `src/plugins/DiditVerification` module with `import { DiditVerification } from 'capacitor-didit-plugin'`. The API is identical; cancellations now reject with `code: 'CANCELLED'`.
159
+
160
+ ## License
161
+
162
+ MIT
@@ -0,0 +1,82 @@
1
+ ext {
2
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
4
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
5
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
6
+ diditSdkVersion = project.hasProperty('diditSdkVersion') ? rootProject.ext.diditSdkVersion : '4.1.0'
7
+ }
8
+
9
+ buildscript {
10
+ ext.kotlin_version = project.hasProperty("kotlin_version") ? rootProject.ext.kotlin_version : '2.2.20'
11
+ repositories {
12
+ google()
13
+ mavenCentral()
14
+ }
15
+ dependencies {
16
+ classpath 'com.android.tools.build:gradle:8.13.0'
17
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
18
+ }
19
+ }
20
+
21
+ apply plugin: 'com.android.library'
22
+ apply plugin: 'kotlin-android'
23
+
24
+ android {
25
+ namespace = "zw.co.aurorasystems.plugins.didit"
26
+ compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
27
+ defaultConfig {
28
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
29
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
30
+ versionCode 1
31
+ versionName "1.0"
32
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
33
+ }
34
+ buildTypes {
35
+ release {
36
+ minifyEnabled false
37
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
38
+ }
39
+ }
40
+ lintOptions {
41
+ abortOnError = false
42
+ }
43
+ compileOptions {
44
+ sourceCompatibility JavaVersion.VERSION_21
45
+ targetCompatibility JavaVersion.VERSION_21
46
+ }
47
+ }
48
+
49
+ kotlin {
50
+ jvmToolchain(21)
51
+ }
52
+
53
+ repositories {
54
+ google()
55
+ mavenCentral()
56
+ // The Didit SDK is published to a GitHub-hosted Maven repository, and
57
+ // some of its transitive dependencies resolve from JitPack.
58
+ maven { url "https://raw.githubusercontent.com/didit-protocol/sdk-android/main/repository" }
59
+ maven { url "https://jitpack.io" }
60
+ }
61
+
62
+ // The consuming app resolves this module's didit-sdk dependency on its own
63
+ // runtime classpath, using the app's repositories — not this module's. Inject
64
+ // the Didit repositories into every project so consumers need no manual repo
65
+ // setup. (Apps that centralize repositories in settings.gradle with
66
+ // FAIL_ON_PROJECT_REPOS must add them there instead — see README.)
67
+ rootProject.allprojects {
68
+ repositories {
69
+ maven { url "https://raw.githubusercontent.com/didit-protocol/sdk-android/main/repository" }
70
+ maven { url "https://jitpack.io" }
71
+ }
72
+ }
73
+
74
+ dependencies {
75
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
76
+ implementation project(':capacitor-android')
77
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
78
+ implementation "me.didit:didit-sdk:$diditSdkVersion"
79
+ testImplementation "junit:junit:$junitVersion"
80
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
81
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
82
+ }
@@ -0,0 +1,2 @@
1
+ # The Didit SDK ships its own consumer ProGuard rules; nothing extra is
2
+ # required for this plugin.
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" />
@@ -0,0 +1,86 @@
1
+ package zw.co.aurorasystems.plugins.didit
2
+
3
+ import android.app.Application
4
+ import com.getcapacitor.JSObject
5
+ import com.getcapacitor.Plugin
6
+ import com.getcapacitor.PluginCall
7
+ import com.getcapacitor.PluginMethod
8
+ import com.getcapacitor.annotation.CapacitorPlugin
9
+ import me.didit.sdk.DiditSdk
10
+ import me.didit.sdk.VerificationResult
11
+
12
+ @CapacitorPlugin(name = "DiditVerification")
13
+ class DiditVerificationPlugin : Plugin() {
14
+
15
+ @Volatile
16
+ private var activeCallbackId: String? = null
17
+
18
+ override fun load() {
19
+ DiditSdk.initialize(context.applicationContext as Application)
20
+ }
21
+
22
+ override fun handleOnDestroy() {
23
+ activeCallbackId = null
24
+ }
25
+
26
+ @PluginMethod
27
+ fun startVerification(call: PluginCall) {
28
+ val sessionToken = call.getString("sessionToken")
29
+ if (sessionToken.isNullOrEmpty()) {
30
+ call.reject("sessionToken is required", "MISSING_TOKEN")
31
+ return
32
+ }
33
+
34
+ val currentActivity = activity
35
+ if (currentActivity == null) {
36
+ call.reject("Activity is not available", "UNAVAILABLE")
37
+ return
38
+ }
39
+
40
+ if (activeCallbackId != null) {
41
+ call.reject("A verification is already in progress", "BUSY")
42
+ return
43
+ }
44
+
45
+ // Save the call so it survives configuration changes while the
46
+ // SDK's activity is in the foreground.
47
+ bridge.saveCall(call)
48
+ activeCallbackId = call.callbackId
49
+
50
+ currentActivity.runOnUiThread {
51
+ DiditSdk.startVerification(token = sessionToken) { result ->
52
+ // Clear the busy flag before the null-guard: a WebView reload
53
+ // resets the bridge and drops saved calls, and the guard must
54
+ // not leave the plugin permanently locked in that case.
55
+ activeCallbackId = null
56
+
57
+ // getSavedCall returning null means the call was already
58
+ // settled or dropped by a bridge reset — guards against the
59
+ // SDK firing the callback twice.
60
+ val savedCall = bridge.getSavedCall(call.callbackId) ?: return@startVerification
61
+ bridge.releaseCall(savedCall)
62
+
63
+ when (result) {
64
+ is VerificationResult.Completed -> {
65
+ val ret = JSObject()
66
+ ret.put("status", mapStatus(result.session.status.name))
67
+ ret.put("sessionId", result.session.sessionId)
68
+ savedCall.resolve(ret)
69
+ }
70
+ is VerificationResult.Cancelled -> {
71
+ savedCall.reject("Verification cancelled", "CANCELLED")
72
+ }
73
+ is VerificationResult.Failed -> {
74
+ savedCall.reject(result.error.message ?: "Verification failed", "FAILED")
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ private fun mapStatus(rawStatus: String): String = when (rawStatus.uppercase()) {
82
+ "APPROVED" -> "Approved"
83
+ "DECLINED" -> "Declined"
84
+ else -> "Pending"
85
+ }
86
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The final status of a completed Didit verification session.
3
+ */
4
+ export type DiditVerificationStatus = 'Approved' | 'Pending' | 'Declined';
5
+ export interface DiditVerificationResult {
6
+ /**
7
+ * The session status reported by the Didit SDK when the flow completed.
8
+ *
9
+ * `Pending` means the session finished but is still being processed or
10
+ * needs manual review — poll the Didit API from your backend for the
11
+ * final decision.
12
+ */
13
+ status: DiditVerificationStatus;
14
+ /**
15
+ * The Didit verification session id, for reconciliation with your backend.
16
+ */
17
+ sessionId: string;
18
+ }
19
+ export interface StartVerificationOptions {
20
+ /**
21
+ * The session token created for this user via the Didit API
22
+ * (`POST /v2/session/` from your backend).
23
+ */
24
+ sessionToken: string;
25
+ }
26
+ export interface DiditVerificationPlugin {
27
+ /**
28
+ * Launch the native Didit verification flow (document capture, liveness,
29
+ * NFC where available) and resolve when the user finishes.
30
+ *
31
+ * Rejects with `code`:
32
+ * - `MISSING_TOKEN` — `sessionToken` was absent or empty.
33
+ * - `BUSY` — a verification flow is already running.
34
+ * - `UNAVAILABLE` — no activity / view controller to present from,
35
+ * or the platform is web.
36
+ * - `CANCELLED` — the user exited the flow before completing it.
37
+ * - `FAILED` — the SDK reported an error (message has details).
38
+ */
39
+ startVerification(options: StartVerificationOptions): Promise<DiditVerificationResult>;
40
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=definitions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ import type { DiditVerificationPlugin } from './definitions';
2
+ declare const DiditVerification: DiditVerificationPlugin;
3
+ export * from './definitions';
4
+ export { DiditVerification };
@@ -0,0 +1,7 @@
1
+ import { registerPlugin } from '@capacitor/core';
2
+ const DiditVerification = registerPlugin('DiditVerification', {
3
+ web: () => import('./web').then((m) => new m.DiditVerificationWeb()),
4
+ });
5
+ export * from './definitions';
6
+ export { DiditVerification };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,iBAAiB,GAAG,cAAc,CAA0B,mBAAmB,EAAE;IACrF,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,oBAAoB,EAAE,CAAC;CACrE,CAAC,CAAC;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { WebPlugin } from '@capacitor/core';
2
+ import type { DiditVerificationPlugin, DiditVerificationResult } from './definitions';
3
+ export declare class DiditVerificationWeb extends WebPlugin implements DiditVerificationPlugin {
4
+ startVerification(): Promise<DiditVerificationResult>;
5
+ }
@@ -0,0 +1,7 @@
1
+ import { WebPlugin } from '@capacitor/core';
2
+ export class DiditVerificationWeb extends WebPlugin {
3
+ async startVerification() {
4
+ throw this.unavailable('The Didit native SDK is not available on web. Use the Didit web verification URL instead.');
5
+ }
6
+ }
7
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C,MAAM,OAAO,oBAAqB,SAAQ,SAAS;IACjD,KAAK,CAAC,iBAAiB;QACrB,MAAM,IAAI,CAAC,WAAW,CACpB,2FAA2F,CAC5F,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ var core = require('@capacitor/core');
4
+
5
+ const DiditVerification = core.registerPlugin('DiditVerification', {
6
+ web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.DiditVerificationWeb()),
7
+ });
8
+
9
+ class DiditVerificationWeb extends core.WebPlugin {
10
+ async startVerification() {
11
+ throw this.unavailable('The Didit native SDK is not available on web. Use the Didit web verification URL instead.');
12
+ }
13
+ }
14
+
15
+ var web = /*#__PURE__*/Object.freeze({
16
+ __proto__: null,
17
+ DiditVerificationWeb: DiditVerificationWeb
18
+ });
19
+
20
+ exports.DiditVerification = DiditVerification;
21
+ //# sourceMappingURL=plugin.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst DiditVerification = registerPlugin('DiditVerification', {\n web: () => import('./web').then((m) => new m.DiditVerificationWeb()),\n});\nexport * from './definitions';\nexport { DiditVerification };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class DiditVerificationWeb extends WebPlugin {\n async startVerification() {\n throw this.unavailable('The Didit native SDK is not available on web. Use the Didit web verification URL instead.');\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;AACK,MAAC,iBAAiB,GAAGA,mBAAc,CAAC,mBAAmB,EAAE;AAC9D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,oBAAoB,EAAE,CAAC;AACxE,CAAC;;ACFM,MAAM,oBAAoB,SAASC,cAAS,CAAC;AACpD,IAAI,MAAM,iBAAiB,GAAG;AAC9B,QAAQ,MAAM,IAAI,CAAC,WAAW,CAAC,2FAA2F,CAAC;AAC3H,IAAI;AACJ;;;;;;;;;"}
package/dist/plugin.js ADDED
@@ -0,0 +1,24 @@
1
+ var capacitorDiditVerification = (function (exports, core) {
2
+ 'use strict';
3
+
4
+ const DiditVerification = core.registerPlugin('DiditVerification', {
5
+ web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.DiditVerificationWeb()),
6
+ });
7
+
8
+ class DiditVerificationWeb extends core.WebPlugin {
9
+ async startVerification() {
10
+ throw this.unavailable('The Didit native SDK is not available on web. Use the Didit web verification URL instead.');
11
+ }
12
+ }
13
+
14
+ var web = /*#__PURE__*/Object.freeze({
15
+ __proto__: null,
16
+ DiditVerificationWeb: DiditVerificationWeb
17
+ });
18
+
19
+ exports.DiditVerification = DiditVerification;
20
+
21
+ return exports;
22
+
23
+ })({}, capacitorExports);
24
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst DiditVerification = registerPlugin('DiditVerification', {\n web: () => import('./web').then((m) => new m.DiditVerificationWeb()),\n});\nexport * from './definitions';\nexport { DiditVerification };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class DiditVerificationWeb extends WebPlugin {\n async startVerification() {\n throw this.unavailable('The Didit native SDK is not available on web. Use the Didit web verification URL instead.');\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,iBAAiB,GAAGA,mBAAc,CAAC,mBAAmB,EAAE;IAC9D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,oBAAoB,EAAE,CAAC;IACxE,CAAC;;ICFM,MAAM,oBAAoB,SAASC,cAAS,CAAC;IACpD,IAAI,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,MAAM,IAAI,CAAC,WAAW,CAAC,2FAA2F,CAAC;IAC3H,IAAI;IACJ;;;;;;;;;;;;;;;"}
@@ -0,0 +1,112 @@
1
+ import SwiftUI
2
+ import UIKit
3
+ import DiditSDK
4
+
5
+ // MARK: - Result types passed back to the Capacitor plugin
6
+
7
+ struct DiditNativeResult {
8
+ let status: String // "Approved" | "Pending" | "Declined"
9
+ let sessionId: String
10
+ }
11
+
12
+ enum DiditNativeError: LocalizedError {
13
+ case cancelled
14
+ case sdkError(String)
15
+
16
+ var code: String {
17
+ switch self {
18
+ case .cancelled: return "CANCELLED"
19
+ case .sdkError: return "FAILED"
20
+ }
21
+ }
22
+
23
+ var errorDescription: String? {
24
+ switch self {
25
+ case .cancelled: return "Verification cancelled"
26
+ case .sdkError(let msg): return msg
27
+ }
28
+ }
29
+ }
30
+
31
+ // MARK: - Transparent SwiftUI view that registers the DiditSDK callback
32
+
33
+ private struct DiditBridgeView: View {
34
+ let onResult: (Result<DiditNativeResult, Error>) -> Void
35
+
36
+ var body: some View {
37
+ Color.clear
38
+ .ignoresSafeArea()
39
+ .diditVerification { sdkResult in
40
+ switch sdkResult {
41
+ case .completed(let session):
42
+ let status: String
43
+ switch session.status {
44
+ case .approved: status = "Approved"
45
+ case .declined: status = "Declined"
46
+ default: status = "Pending"
47
+ }
48
+ onResult(.success(DiditNativeResult(
49
+ status: status,
50
+ sessionId: session.sessionId
51
+ )))
52
+
53
+ case .cancelled:
54
+ onResult(.failure(DiditNativeError.cancelled))
55
+
56
+ case .failed(let error, _):
57
+ onResult(.failure(DiditNativeError.sdkError(error.localizedDescription)))
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ // MARK: - Bridge that presents the SwiftUI host and launches the SDK
64
+
65
+ final class DiditVerificationBridge {
66
+
67
+ static func present(
68
+ from presentingVC: UIViewController,
69
+ sessionToken: String,
70
+ completion: @escaping (Result<DiditNativeResult, Error>) -> Void
71
+ ) {
72
+ // Present from the top-most controller — presenting from a controller
73
+ // that already has a modal up fails silently in UIKit.
74
+ var topVC = presentingVC
75
+ while let presented = topVC.presentedViewController {
76
+ topVC = presented
77
+ }
78
+
79
+ var hosting: UIHostingController<DiditBridgeView>?
80
+
81
+ // Guard against the SDK firing the callback more than once (e.g. a
82
+ // completed event followed by a cleanup event). The second invocation
83
+ // would call resolve/reject on an already-settled CAPPluginCall, which
84
+ // crashes the bridge on iOS 17+.
85
+ var didComplete = false
86
+ let onceCompletion: (Result<DiditNativeResult, Error>) -> Void = { result in
87
+ guard !didComplete else { return }
88
+ didComplete = true
89
+ DispatchQueue.main.async {
90
+ hosting?.dismiss(animated: false)
91
+ hosting = nil
92
+ completion(result)
93
+ }
94
+ }
95
+
96
+ let bridgeView = DiditBridgeView(onResult: onceCompletion)
97
+
98
+ let hc = UIHostingController(rootView: bridgeView)
99
+ hc.view.backgroundColor = .clear
100
+ // overFullScreen keeps the underlying UI visible while DiditSDK
101
+ // presents its own screens on top.
102
+ hc.modalPresentationStyle = .overFullScreen
103
+ hosting = hc
104
+
105
+ topVC.present(hc, animated: false) {
106
+ // The SwiftUI environment with .diditVerification is now active;
107
+ // the SDK finds the top view controller and the registered
108
+ // callback through the environment.
109
+ DiditSdk.shared.startVerification(token: sessionToken)
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,62 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ @objc(DiditVerificationPlugin)
5
+ public class DiditVerificationPlugin: CAPPlugin, CAPBridgedPlugin {
6
+ public let identifier = "DiditVerificationPlugin"
7
+ public let jsName = "DiditVerification"
8
+ public let pluginMethods: [CAPPluginMethod] = [
9
+ CAPPluginMethod(name: "startVerification", returnType: CAPPluginReturnPromise)
10
+ ]
11
+
12
+ private var isVerificationActive = false
13
+
14
+ @objc func startVerification(_ call: CAPPluginCall) {
15
+ guard let sessionToken = call.getString("sessionToken"), !sessionToken.isEmpty else {
16
+ call.reject("sessionToken is required", "MISSING_TOKEN")
17
+ return
18
+ }
19
+
20
+ // Keep the call alive across the async gap so the bridge doesn't
21
+ // release it before the SDK callback fires.
22
+ call.keepAlive = true
23
+
24
+ DispatchQueue.main.async { [weak self] in
25
+ guard let self = self else { return }
26
+
27
+ let finish: (CAPPluginCall) -> Void = { [weak self] call in
28
+ self?.bridge?.releaseCall(call)
29
+ }
30
+
31
+ guard !self.isVerificationActive else {
32
+ call.reject("A verification is already in progress", "BUSY")
33
+ finish(call)
34
+ return
35
+ }
36
+
37
+ guard let rootVC = self.bridge?.viewController else {
38
+ call.reject("No view controller available", "UNAVAILABLE")
39
+ finish(call)
40
+ return
41
+ }
42
+
43
+ self.isVerificationActive = true
44
+
45
+ DiditVerificationBridge.present(from: rootVC, sessionToken: sessionToken) { [weak self] result in
46
+ self?.isVerificationActive = false
47
+
48
+ switch result {
49
+ case .success(let verification):
50
+ call.resolve([
51
+ "status": verification.status,
52
+ "sessionId": verification.sessionId
53
+ ])
54
+ case .failure(let error):
55
+ let code = (error as? DiditNativeError)?.code ?? "FAILED"
56
+ call.reject(error.localizedDescription, code)
57
+ }
58
+ finish(call)
59
+ }
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,10 @@
1
+ import XCTest
2
+ @testable import DiditVerificationPlugin
3
+
4
+ class DiditVerificationPluginTests: XCTestCase {
5
+ func testPluginIdentity() {
6
+ let plugin = DiditVerificationPlugin()
7
+ XCTAssertEqual(plugin.jsName, "DiditVerification")
8
+ XCTAssertEqual(plugin.pluginMethods.count, 1)
9
+ }
10
+ }
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "capacitor-didit-plugin",
3
+ "version": "0.1.0",
4
+ "description": "Capacitor plugin wrapping the native Didit identity verification (KYC) SDKs for iOS and Android",
5
+ "main": "dist/plugin.cjs.js",
6
+ "module": "dist/esm/index.js",
7
+ "types": "dist/esm/index.d.ts",
8
+ "unpkg": "dist/plugin.js",
9
+ "files": [
10
+ "android/src/main/",
11
+ "android/build.gradle",
12
+ "android/proguard-rules.pro",
13
+ "dist/",
14
+ "ios/Sources",
15
+ "ios/Tests",
16
+ "Package.swift",
17
+ "CapacitorDiditPlugin.podspec"
18
+ ],
19
+ "author": "Aurora Systems <zkomichi@aurorasystems.co.zw>",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/aurora-systems/capacitor-didit-plugin.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/aurora-systems/capacitor-didit-plugin/issues"
27
+ },
28
+ "keywords": [
29
+ "capacitor",
30
+ "plugin",
31
+ "native",
32
+ "didit",
33
+ "kyc",
34
+ "identity-verification",
35
+ "liveness"
36
+ ],
37
+ "scripts": {
38
+ "verify": "npm run verify:web",
39
+ "verify:web": "npm run build",
40
+ "build": "npm run clean && tsc && rollup -c rollup.config.mjs",
41
+ "clean": "rimraf ./dist",
42
+ "watch": "tsc --watch",
43
+ "prepublishOnly": "npm run build"
44
+ },
45
+ "devDependencies": {
46
+ "@capacitor/android": "^8.0.0",
47
+ "@capacitor/core": "^8.0.0",
48
+ "@capacitor/ios": "^8.0.0",
49
+ "rimraf": "^6.0.1",
50
+ "rollup": "^4.30.1",
51
+ "typescript": "^5.9.0"
52
+ },
53
+ "peerDependencies": {
54
+ "@capacitor/core": ">=8.0.0"
55
+ },
56
+ "capacitor": {
57
+ "ios": {
58
+ "src": "ios"
59
+ },
60
+ "android": {
61
+ "src": "android"
62
+ }
63
+ }
64
+ }