@sekyro/react-native 0.0.0-stage → 0.1.0-rc.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @sekyro/react-native
2
+
3
+ ## 0.1.0-rc.1
4
+
5
+ ### Minor Changes
6
+
7
+ - e9dd971: Flow model 2026-10-23 (SDK spec §3.7): the native Android and iOS drop-ins render the three-section flow (contact → identity → live photo) with consent as one line on the first screen, the phone-or-email choice, the identity summary before the live photo, the document chip as its own method, and one camera session for the live photo with the ICAO portrait. Step events report the new `SekyroStep` values `'contact_choice'`, `'identity_summary'` (never the details shown) and `'portrait'` (with the server's `icao_*` reason as `retryReason`); `'consent'` is no longer emitted (consent is one line on the first screen). **Migration (TypeScript):** code that switches exhaustively over `SekyroStep` must add the three new branches.
8
+ - 088fa55: National eID (SDK spec §3.2; Houwiyeti in Mauritania): the native Android and iOS drop-ins now run the eID screens, and step events report them as the new `SekyroStep` value `'eid'` (with the server's `eid_*` reason as `retryReason`). **Migration (TypeScript):** code that switches exhaustively over `SekyroStep` must add an `'eid'` branch.
9
+ - f52408e: First release candidate of the React Native SDK: `startSekyro()` and `<SekyroFlow>` over the native Android (Maven Central) and iOS (`sekyro-ios-dist`) SDKs 0.1.0-rc.2, step events, theming and string overrides, Expo config plugin. New Architecture only (React Native ≥ 0.76).
10
+
11
+ ### Patch Changes
12
+
13
+ - 45f639f: Licence: the SDKs are now under the Sekyro AI SDK License of Ibtikar Technologies, Inc. (https://sekyro.ai/en/legal/sdk-license, also in French and Arabic). `package.json#license` is `SEE LICENSE IN LICENSE`, and each package's `LICENSE` summarises the licence and links to it. `@sekyro/web` now also ships `THIRD-PARTY-NOTICES.txt` with the MIT notice of the bundled scanic document detector and the MIT/NCSA notice of the Emscripten glue in its OCR worker.
package/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ Sekyro AI SDK License (proprietary)
2
+
3
+ Copyright (c) 2026 Ibtikar Technologies, Inc. All rights reserved.
4
+
5
+ This SDK is proprietary software of Ibtikar Technologies, Inc., licensed
6
+ only under the Sekyro AI SDK License:
7
+ https://sekyro.ai/en/legal/sdk-license
8
+ (in French: https://sekyro.ai/fr/legal/sdk-license,
9
+ in Arabic: https://sekyro.ai/ar/legal/sdk-license).
10
+
11
+ In short, and subject to the full license:
12
+
13
+ - You may use the SDK only with a Sekyro AI account, a customer agreement
14
+ with Ibtikar Technologies, Inc. and valid API keys, to integrate the
15
+ Sekyro AI services into your own applications. You may distribute it to
16
+ your end users only as embedded in those applications.
17
+ - You may not use it with another identity verification provider, modify it
18
+ beyond the configuration and theming it exposes, reverse engineer,
19
+ decompile or disassemble it (except where the law expressly allows it),
20
+ circumvent its security, attestation, license checks or usage metering,
21
+ or distribute or sublicense it on its own.
22
+ - Open-source components keep their own licenses. See the third-party
23
+ notices shipped with this SDK.
24
+ - The SDK is provided "as is", without warranty, except as your customer
25
+ agreement provides. The license is governed by the laws of the State of
26
+ Delaware, USA. If this license and your customer agreement conflict,
27
+ the customer agreement prevails.
28
+
29
+ Questions: contact@sekyro.ai
package/README.md CHANGED
@@ -1,3 +1,77 @@
1
- # Temporary Holding Version
1
+ # @sekyro/react-native
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Sekyro AI eKYC for React Native: a thin TurboModule over the binary Sekyro Android and iOS SDKs.
4
+ The native drop-in runs the whole flow (consent, document, NFC chip, liveness) full screen and
5
+ returns a display-only result. Read the decision on your server (webhooks or `sessions.get`).
6
+
7
+ Requirements: React Native 0.76 or later with the New Architecture, Android minSdk 24, iOS 15.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ npm install @sekyro/react-native
13
+ ```
14
+
15
+ ### iOS (Podfile)
16
+
17
+ The pod depends on `SekyroKit`, which is not on CocoaPods trunk. Declare its source once in the
18
+ app target, after `use_native_modules!`:
19
+
20
+ ```ruby
21
+ require Pod::Executable.execute_command('node', ['-p',
22
+ 'require.resolve("@sekyro/react-native/package.json", {paths: [process.argv[1]]})', __dir__]
23
+ ).strip.sub(/package\.json$/, 'scripts/sekyro_pods.rb')
24
+
25
+ target 'App' do
26
+ config = use_native_modules!
27
+ sekyro_kit_pod!
28
+ # ...
29
+ end
30
+ ```
31
+
32
+ Add `NSCameraUsageDescription` and, for chip reading, `NFCReaderUsageDescription`, the ISO 7816
33
+ identifiers `A0000002471001` and `A0000002472001`, and the NFC `TAG` entitlement.
34
+
35
+ ### Android (Gradle)
36
+
37
+ The native core comes from Maven Central (`ai.sekyro:sekyro-bom` + `ai.sekyro:sekyro-ui`), so
38
+ `mavenCentral()` must be in your repositories. Set `minSdkVersion` to 24 or more. The core's
39
+ manifest merges the camera and NFC permissions and `SekyroActivity`; nothing else to add.
40
+
41
+ ### Expo
42
+
43
+ ```json
44
+ { "plugins": [["@sekyro/react-native", { "nfc": true, "appAttest": "production" }]] }
45
+ ```
46
+
47
+ Then `npx expo prebuild`. Options: `nfc` (default `true`), `appAttest`
48
+ (`'production'` | `'development'` | `false`), `cameraPermission`, `nfcPermission`.
49
+
50
+ ## Use
51
+
52
+ ```tsx
53
+ import { startSekyro } from '@sekyro/react-native';
54
+
55
+ const result = await startSekyro({
56
+ clientToken, // from your backend: sekyro.sessions.create()
57
+ locale: 'fr',
58
+ theme: { colors: { brand: '#0F68B1' }, radius: 12 },
59
+ onEvent: (e) => console.log(e.type), // step telemetry, no personal data
60
+ });
61
+ // { status: 'finished', sessionId, displayStatus } | { status: 'cancelled' } | { status: 'failed', code }
62
+ ```
63
+
64
+ Or declaratively: `<SekyroFlow clientToken={token} onResult={setResult} />` (renders nothing).
65
+
66
+ Never log the `clientToken`; it is a one-time bearer credential.
67
+
68
+ While a flow runs, the wrapper owns the native SDK's process-wide event listener
69
+ (`Sekyro.setEventListener` on Android, the drop-in's `onEvent` on iOS) and clears it when the
70
+ flow ends, so a listener your app set natively is replaced for that time. A flow interrupted
71
+ because the React instance was torn down resolves `{ status: 'failed', code: 'flow_interrupted' }`.
72
+
73
+ Full guide: [React Native guide](https://docs.sekyro.ai/en/mobile/react-native).
74
+
75
+ ## License
76
+
77
+ Proprietary: the [Sekyro AI SDK License](https://sekyro.ai/en/legal/sdk-license). See [LICENSE](./LICENSE).
@@ -0,0 +1,22 @@
1
+ # @sekyro/react-native — iOS glue (TurboModule over the binary SekyroKit.xcframework).
2
+ require 'json'
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'SekyroReactNative'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.homepage = 'https://sekyro.ai'
10
+ s.license = { :type => 'Proprietary', :file => 'LICENSE' }
11
+ s.authors = 'Ibtikar Technologies, Inc.'
12
+ s.platforms = { :ios => '15.1' }
13
+ s.source = { :http => "https://registry.npmjs.org/@sekyro/react-native/-/react-native-#{package['version']}.tgz" }
14
+ s.source_files = 'ios/**/*.{h,mm,swift}'
15
+ # The TurboModule header imports the C++ codegen header: keep it out of the module umbrella that Swift imports.
16
+ s.private_header_files = 'ios/RCTSekyroReactNative.h'
17
+ s.swift_version = '5.9'
18
+ # The binary core, pinned by package.json#sekyro.ios. WHERE it comes from is declared in the app's Podfile
19
+ # by `sekyro_kit_pod!` (scripts/sekyro_pods.rb): the public sekyro-ios-dist podspec, or a local dist tree.
20
+ s.dependency 'SekyroKit', package['sekyro']['ios']
21
+ install_modules_dependencies(s)
22
+ end
@@ -0,0 +1,38 @@
1
+ // @sekyro/react-native — Android glue (TurboModule over the binary ai.sekyro:sekyro-ui drop-in).
2
+ // The Sekyro SDK itself is a Maven dependency pinned by package.json#sekyro.android: no AAR ships in npm.
3
+ import groovy.json.JsonSlurper
4
+
5
+ def pkg = new JsonSlurper().parse(file("../package.json"))
6
+ def safeExtGet = { prop, fallback -> rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback }
7
+
8
+ // Development only: resolve ai.sekyro from ~/.m2 after `./gradlew publishToMavenLocal` in sekyro-mobile/android.
9
+ // The app resolves its runtime classpath with ITS repositories, so the content-filtered repo is added to every project.
10
+ // `-Psekyro.version=<v>` overrides the pin (a local publish is versioned VERSION_NAME, e.g. 0.1.0-SNAPSHOT).
11
+ def sekyroLocal = rootProject.findProperty("sekyro.mavenLocal") == "true"
12
+ def sekyroVersion = rootProject.findProperty("sekyro.version") ?: pkg.sekyro.android
13
+ if (sekyroLocal) {
14
+ rootProject.allprojects { repositories { mavenLocal { content { includeGroup "ai.sekyro" } } } }
15
+ }
16
+
17
+ apply plugin: "com.android.library"
18
+ apply plugin: "org.jetbrains.kotlin.android"
19
+ apply plugin: "com.facebook.react"
20
+
21
+ android {
22
+ namespace "ai.sekyro.reactnative"
23
+ compileSdk safeExtGet("compileSdkVersion", 36)
24
+ defaultConfig { minSdk Math.max(24, safeExtGet("minSdkVersion", 24) as int) }
25
+ compileOptions { sourceCompatibility JavaVersion.VERSION_17; targetCompatibility JavaVersion.VERSION_17 }
26
+ testOptions { unitTests.includeAndroidResources = true }
27
+ }
28
+
29
+ kotlin { jvmToolchain(17) }
30
+
31
+ dependencies {
32
+ implementation "com.facebook.react:react-android"
33
+ implementation platform("ai.sekyro:sekyro-bom:${sekyroVersion}")
34
+ implementation "ai.sekyro:sekyro-ui"
35
+ testImplementation "junit:junit:4.13.2"
36
+ testImplementation "org.robolectric:robolectric:4.15.1"
37
+ testImplementation "androidx.test:core:1.6.1"
38
+ }
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Permissions (camera, NFC), optional features and SekyroActivity come from the Sekyro AARs' merged manifests. -->
3
+ <manifest />
@@ -0,0 +1,73 @@
1
+ package ai.sekyro.reactnative
2
+
3
+ import ai.sekyro.SekyroConfig
4
+ import ai.sekyro.SekyroLocale
5
+ import ai.sekyro.SekyroResult
6
+ import ai.sekyro.SekyroTheme
7
+ import ai.sekyro.core.DisplayStatus
8
+ import com.facebook.react.bridge.Arguments
9
+ import com.facebook.react.bridge.ReadableMap
10
+ import com.facebook.react.bridge.ReadableType
11
+ import com.facebook.react.bridge.WritableMap
12
+
13
+ /** JS wire ↔ Sekyro types. The JS layer validated the config; this re-checks the shape and never logs it. */
14
+ internal object ConfigMapper {
15
+ private val HEX = Regex("^#[0-9a-fA-F]{6}$")
16
+
17
+ private fun ReadableMap.str(key: String): String? =
18
+ if (hasKey(key) && !isNull(key) && getType(key) == ReadableType.String) getString(key) else null
19
+
20
+ private fun color(map: ReadableMap?, key: String): Int? {
21
+ val hex = map?.str(key) ?: return null
22
+ require(HEX.matches(hex)) { "invalid_config" }
23
+ return (0xFF000000 or hex.substring(1).toLong(16)).toInt()
24
+ }
25
+
26
+ fun toConfig(m: ReadableMap): SekyroConfig {
27
+ val token = m.str("clientToken").orEmpty()
28
+ require(token.isNotEmpty()) { "invalid_config" }
29
+ val colors = if (m.hasKey("colors") && !m.isNull("colors")) m.getMap("colors") else null
30
+ val radius = if (m.hasKey("radius") && !m.isNull("radius")) m.getDouble("radius") else null
31
+ require(radius == null || radius in 0.0..32.0) { "invalid_config" }
32
+ val darkMode = m.str("darkMode")
33
+ val hasTheme = colors != null || radius != null || darkMode != null
34
+ val theme = if (!hasTheme) null else SekyroTheme(
35
+ brand = color(colors, "brand"), brandFg = color(colors, "brandFg"),
36
+ radiusDp = radius?.toInt(),
37
+ darkMode = when (darkMode) {
38
+ "light" -> SekyroTheme.DarkMode.LIGHT; "dark" -> SekyroTheme.DarkMode.DARK; else -> SekyroTheme.DarkMode.SYSTEM
39
+ },
40
+ bg = color(colors, "bg"), surface = color(colors, "surface"), fg = color(colors, "fg"),
41
+ fgMuted = color(colors, "fgMuted"), danger = color(colors, "danger"),
42
+ success = color(colors, "success"), warning = color(colors, "warning"),
43
+ ).also { color(colors, "border") } // `border` (validated) and `fontFamily`: not themable on Android in 0.1 (README).
44
+ val strings = if (m.hasKey("strings") && !m.isNull("strings")) {
45
+ m.getMap("strings")?.toHashMap().orEmpty().mapNotNull { (k, v) -> (v as? String)?.let { k to it } }.toMap()
46
+ } else emptyMap()
47
+ return SekyroConfig(
48
+ clientToken = token,
49
+ apiOrigin = m.str("apiOrigin"),
50
+ locale = when (m.str("locale")) {
51
+ "ar" -> SekyroLocale.AR; "fr" -> SekyroLocale.FR; "en" -> SekyroLocale.EN; else -> null
52
+ },
53
+ theme = theme,
54
+ strings = strings,
55
+ )
56
+ }
57
+
58
+ /** [into] is injectable so JVM tests can use a JavaOnlyMap (Arguments.createMap() needs the RN native libs). */
59
+ fun toWire(r: SekyroResult, into: WritableMap = Arguments.createMap()): WritableMap = into.apply {
60
+ when (r) {
61
+ is SekyroResult.Finished -> {
62
+ putString("status", "finished"); putString("sessionId", r.sessionId)
63
+ // EXPIRED stays on the wire; the JS layer maps it to { status: 'failed', code: 'session_expired' } (iOS parity).
64
+ putString("displayStatus", when (r.displayStatus) {
65
+ DisplayStatus.APPROVED -> "approved"; DisplayStatus.REVIEW_PENDING -> "review_pending"
66
+ DisplayStatus.REJECTED -> "rejected"; DisplayStatus.EXPIRED -> "expired"
67
+ })
68
+ }
69
+ SekyroResult.Cancelled -> putString("status", "cancelled")
70
+ is SekyroResult.Failed -> { putString("status", "failed"); putString("code", r.code) }
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,107 @@
1
+ package ai.sekyro.reactnative
2
+
3
+ import ai.sekyro.Sekyro
4
+ import ai.sekyro.SekyroContract
5
+ import ai.sekyro.SekyroEvent
6
+ import ai.sekyro.SekyroResult
7
+ import ai.sekyro.core.SekyroVersion
8
+ import android.app.Activity
9
+ import android.content.Intent
10
+ import com.facebook.react.bridge.Arguments
11
+ import com.facebook.react.bridge.BaseActivityEventListener
12
+ import com.facebook.react.bridge.Promise
13
+ import com.facebook.react.bridge.ReactApplicationContext
14
+ import com.facebook.react.bridge.ReadableMap
15
+ import com.facebook.react.bridge.UiThreadUtil
16
+ import com.facebook.react.bridge.WritableMap
17
+ import java.util.concurrent.atomic.AtomicBoolean
18
+ import java.util.concurrent.atomic.AtomicInteger
19
+
20
+ /**
21
+ * `SekyroReactNative` TurboModule: presents the native drop-in (SekyroActivity) and forwards its step events.
22
+ * Never logs the config (the client token is a bearer credential).
23
+ *
24
+ * The result comes back through React Native's activity-event routing (`startActivityForResult` +
25
+ * [BaseActivityEventListener]), not an activity-result registry callback: when the RN host activity is
26
+ * recreated while SekyroActivity is on top (configuration change, "Don't keep activities"), the recreated
27
+ * `ReactActivity` still forwards `onActivityResult` to this module, so the promise always settles and the
28
+ * module never stays locked. If the React instance itself is torn down mid-flow, [invalidate] settles it as
29
+ * `failed / flow_interrupted`.
30
+ */
31
+ class SekyroReactNativeModule(context: ReactApplicationContext) : NativeSekyroReactNativeSpec(context) {
32
+ private val running = AtomicBoolean(false)
33
+ private val seq = AtomicInteger(0)
34
+ @Volatile private var pending: Promise? = null
35
+
36
+ private val activityListener = object : BaseActivityEventListener() {
37
+ override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
38
+ if (requestCode != REQUEST_CODE) return
39
+ settle(SekyroContract().parseResult(resultCode, data))
40
+ }
41
+ }
42
+
43
+ init {
44
+ context.addActivityEventListener(activityListener)
45
+ }
46
+
47
+ override fun getName(): String = NAME
48
+
49
+ override fun nativeVersion(): String = "android ${SekyroVersion.name}"
50
+
51
+ override fun start(config: ReadableMap, promise: Promise) {
52
+ val parsed = try { ConfigMapper.toConfig(config) } catch (_: IllegalArgumentException) {
53
+ promise.reject("invalid_config", "invalid config"); return
54
+ }
55
+ val activity = reactApplicationContext.currentActivity
56
+ ?: run { promise.reject("no_activity", "no foreground activity"); return }
57
+ if (!running.compareAndSet(false, true)) { promise.reject("flow_in_progress", "a Sekyro flow is already running"); return }
58
+ UiThreadUtil.runOnUiThread {
59
+ try {
60
+ if (activity.isFinishing || activity.isDestroyed) throw IllegalStateException("activity is finishing")
61
+ pending = promise
62
+ Sekyro.setEventListener { event -> emitOnEvent(toWire(event)) }
63
+ activity.startActivityForResult(SekyroContract().createIntent(activity, parsed), REQUEST_CODE)
64
+ } catch (_: Exception) {
65
+ // e.g. the activity is finishing, or the drop-in activity cannot be resolved.
66
+ pending = null
67
+ Sekyro.setEventListener(null)
68
+ running.set(false)
69
+ promise.reject("no_activity", "the foreground activity cannot launch the flow")
70
+ }
71
+ }
72
+ }
73
+
74
+ /** Settle the running flow once (a duplicate or late result is ignored). */
75
+ private fun settle(result: SekyroResult) {
76
+ val p = pending ?: return
77
+ pending = null
78
+ Sekyro.setEventListener(null)
79
+ running.set(false)
80
+ p.resolve(ConfigMapper.toWire(result))
81
+ }
82
+
83
+ private fun toWire(e: SekyroEvent): WritableMap = Arguments.createMap().apply {
84
+ putInt("seq", seq.incrementAndGet())
85
+ when (e) {
86
+ is SekyroEvent.Step -> {
87
+ putString("type", "step"); putString("step", e.step.wire)
88
+ e.retryReason?.let { putString("retryReason", it) }
89
+ }
90
+ SekyroEvent.WaitingForNetwork -> putString("type", "waiting_for_network")
91
+ }
92
+ }
93
+
94
+ override fun invalidate() {
95
+ reactApplicationContext.removeActivityEventListener(activityListener)
96
+ // The React instance is going away with a flow still open: settle it rather than leak it.
97
+ settle(SekyroResult.Failed("flow_interrupted"))
98
+ Sekyro.setEventListener(null)
99
+ super.invalidate()
100
+ }
101
+
102
+ companion object {
103
+ const val NAME = "SekyroReactNative"
104
+ /** Activity request code of the drop-in (any 16-bit value unique to this module). */
105
+ private const val REQUEST_CODE = 0x5E4B
106
+ }
107
+ }
@@ -0,0 +1,26 @@
1
+ package ai.sekyro.reactnative
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+
9
+ /** Autolinked by the React Native CLI (New Architecture: a TurboModule). */
10
+ class SekyroReactNativePackage : BaseReactPackage() {
11
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? =
12
+ if (name == SekyroReactNativeModule.NAME) SekyroReactNativeModule(reactContext) else null
13
+
14
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider {
15
+ mapOf(
16
+ SekyroReactNativeModule.NAME to ReactModuleInfo(
17
+ SekyroReactNativeModule.NAME,
18
+ SekyroReactNativeModule::class.java.name,
19
+ false, // canOverrideExistingModule
20
+ false, // needsEagerInit
21
+ false, // isCxxModule
22
+ true, // isTurboModule
23
+ ),
24
+ )
25
+ }
26
+ }
package/app.plugin.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./plugin');
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";var u=Object.defineProperty;var N=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var h=Object.prototype.hasOwnProperty;var C=(e,r)=>{for(var t in r)u(e,t,{get:r[t],enumerable:!0})},O=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let i of w(r))!h.call(e,i)&&i!==t&&u(e,i,{get:()=>r[i],enumerable:!(n=N(r,i))||n.enumerable});return e};var x=e=>O(u({},"__esModule",{value:!0}),e);var A={};C(A,{SDK_VERSION:()=>R,SekyroError:()=>l,SekyroFlow:()=>E,nativeVersion:()=>_,startSekyro:()=>f});module.exports=x(A);var S=require("react-native"),d=S.TurboModuleRegistry.get("SekyroReactNative");var l=class extends Error{code;constructor(r,t=r){super(t),this.name="SekyroError",this.code=r}};var b=500,T=new Set(["ar","fr","en"]),F=["brand","brandFg","bg","surface","fg","fgMuted","border","danger","success","warning"],I=new Set(["clientToken","apiOrigin","locale","theme","strings","onEvent"]),D=/^#[0-9a-fA-F]{6}$/,V=/^http:\/\/(localhost|127\.0\.0\.1|10\.0\.2\.2)(:\d+)?$/,a=e=>{throw new l("invalid_config",`invalid config: ${e}`)};function k(e){(typeof e!="object"||e===null)&&a("config");for(let n of Object.keys(e))I.has(n)||a(`unknown key "${n.slice(0,32)}"`);(typeof e.clientToken!="string"||e.clientToken.length===0)&&a("clientToken");let r={clientToken:e.clientToken};e.apiOrigin!==void 0&&(typeof e.apiOrigin=="string"&&(/^https:\/\/[a-z0-9.-]+(:\d+)?$/i.test(e.apiOrigin)||__DEV__&&V.test(e.apiOrigin))||a("apiOrigin"),r.apiOrigin=e.apiOrigin),e.locale!==void 0&&(T.has(e.locale)||a("locale"),r.locale=e.locale);let t=e.theme;if(t){if(t.colors){let n={};for(let[i,s]of Object.entries(t.colors))s!==void 0&&((!F.includes(i)||typeof s!="string"||!D.test(s))&&a(`theme.colors.${i.slice(0,32)}`),n[i]=s);r.colors=n}t.radius!==void 0&&((typeof t.radius!="number"||!Number.isFinite(t.radius)||t.radius<0||t.radius>32)&&a("theme.radius"),r.radius=t.radius),t.fontFamily!==void 0&&((typeof t.fontFamily!="string"||!/^[\w .-]{1,64}$/.test(t.fontFamily))&&a("theme.fontFamily"),r.fontFamily=t.fontFamily),t.darkMode!==void 0&&(["system","light","dark"].includes(t.darkMode)||a("theme.darkMode"),r.darkMode=t.darkMode)}if(e.strings){e.locale||a("strings need locale");let n=e.strings[e.locale]??{},i={},s=Object.entries(n).filter(([,o])=>o!==void 0);s.length>b&&a("strings (too many overrides)");for(let[o,y]of s)/^[a-zA-Z0-9_.]{1,96}$/.test(o)||a("strings key"),(typeof y!="string"||y.length>2e3)&&a(`strings.${o}`),i[o]=y;r.strings=i}return r}var M=new Set(["method","phone_otp","email_otp","registry","document","nfc","liveness","processing","result","update_required","eid","contact_choice","identity_summary","portrait"]);function g(e){if(e.type==="waiting_for_network")return{type:"waiting_for_network"};if(e.type!=="step"||!M.has(e.step))return null;let r=e.step,t=typeof e.retryReason=="string"&&/^[a-z0-9_]{1,64}$/.test(e.retryReason)?e.retryReason:void 0;return t?{type:"step",step:r,retryReason:t}:{type:"step",step:r}}var $=new Set(["approved","review_pending","rejected"]);function m(e){if(e.status==="cancelled")return{status:"cancelled"};if(e.status==="failed")return{status:"failed",code:e.code||"unknown_error"};if(e.status==="finished"&&e.sessionId){if(e.displayStatus==="expired")return{status:"failed",code:"session_expired"};if($.has(e.displayStatus))return{status:"finished",sessionId:e.sessionId,displayStatus:e.displayStatus}}return{status:"failed",code:"unknown_error"}}function v(){if(!d)throw new l("unsupported_platform","the SekyroReactNative native module is not linked (New Architecture app, pod install / Gradle sync)");return d}var c=!1,j=new Set(["no_activity","unsupported_platform","flow_in_progress","invalid_config"]);async function f(e){let r=k(e),t=v();if(c)throw new l("flow_in_progress");c=!0;let n=e.onEvent,i=n?t.onEvent(s=>{let o=g(s);o&&n(o)}):void 0;try{return m(await t.start(r))}catch(s){let o=s?.code;if(typeof o=="string"&&j.has(o))throw new l(o);return{status:"failed",code:"unknown_error"}}finally{i?.remove(),c=!1}}function _(){return v().nativeVersion()}var p=require("react");function E(e){let r=(0,p.useRef)(null),t=(0,p.useRef)(e);return t.current=e,(0,p.useEffect)(()=>{if(r.current===e.clientToken)return;r.current=e.clientToken;let{onResult:n,onEvent:i,...s}=t.current;f({...s,onEvent:o=>t.current.onEvent?.(o)}).then(o=>t.current.onResult(o),o=>t.current.onResult({status:"failed",code:o instanceof l?o.code:"unknown_error"}))},[e.clientToken]),null}var R="0.1.0-rc.1";0&&(module.exports={SDK_VERSION,SekyroError,SekyroFlow,nativeVersion,startSekyro});
@@ -0,0 +1,99 @@
1
+ /** UI language of the native flow. @public */
2
+ type SekyroLocale = 'ar' | 'fr' | 'en';
3
+ /** Themeable colour roles, shared by the Android and iOS cores. @public */
4
+ type SekyroColorRole = 'brand' | 'brandFg' | 'bg' | 'surface' | 'fg' | 'fgMuted' | 'border' | 'danger' | 'success' | 'warning';
5
+ /** Runtime theme applied over the native defaults. @public */
6
+ interface SekyroTheme {
7
+ /** `'#RRGGBB'` per role. */
8
+ colors?: Partial<Record<SekyroColorRole, string>>;
9
+ /** Corner radius in dp / pt, 0–32. */
10
+ radius?: number;
11
+ /** iOS only in 0.1 (Android ignores it). */
12
+ fontFamily?: string;
13
+ /** Colour scheme; `'system'` follows the device. */
14
+ darkMode?: 'system' | 'light' | 'dark';
15
+ }
16
+ /**
17
+ * Screen of the native flow, as reported by step events (`eid`: the national eID sign-in, SDK spec §3.2).
18
+ * Flow model 2026-10-23: `contact_choice` (phone or email), `identity_summary` (check the details read — the event never
19
+ * carries them), `portrait` (the ICAO portrait on its own). `consent` is never emitted (consent is one line on the first
20
+ * screen); it stays in the union only because the public API may only grow. @public
21
+ */
22
+ type SekyroStep = 'consent' | 'method' | 'phone_otp' | 'email_otp' | 'registry' | 'document' | 'nfc' | 'liveness' | 'processing' | 'result' | 'update_required' | 'eid' | 'contact_choice' | 'identity_summary' | 'portrait';
23
+ /** Telemetry only; carries no personal data. @public */
24
+ type SekyroEvent = {
25
+ type: 'step';
26
+ step: SekyroStep;
27
+ retryReason?: string;
28
+ } | {
29
+ type: 'waiting_for_network';
30
+ };
31
+ /** Options for {@link startSekyro}. @public */
32
+ interface SekyroConfig {
33
+ /** One-time session token from your backend (`sessions.create`). Never logged. */
34
+ clientToken: string;
35
+ /** e.g. `'https://api-staging.sekyro.ai'`; defaults to production. */
36
+ apiOrigin?: string;
37
+ /** UI language of the flow; required when `strings` is set. */
38
+ locale?: SekyroLocale;
39
+ /** Colours, radius, font and colour scheme. */
40
+ theme?: SekyroTheme;
41
+ /** Catalog-key overrides per locale; only `strings[locale]` is used, so `locale` is required. */
42
+ strings?: Partial<Record<SekyroLocale, Record<string, string>>>;
43
+ /** Step telemetry of this flow only (no personal data). */
44
+ onEvent?: (event: SekyroEvent) => void;
45
+ }
46
+ /** What the end user was shown; not the decision. @public */
47
+ type SekyroDisplayStatus = 'approved' | 'review_pending' | 'rejected';
48
+ /** Display-only outcome; read the decision server-side (webhooks / `sessions.get`). @public */
49
+ type SekyroResult = {
50
+ status: 'finished';
51
+ sessionId: string;
52
+ displayStatus: SekyroDisplayStatus;
53
+ } | {
54
+ status: 'cancelled';
55
+ } | {
56
+ status: 'failed';
57
+ code: string;
58
+ };
59
+
60
+ /**
61
+ * Present the Sekyro drop-in full screen. Resolves a display-only result; read the decision
62
+ * server-side. Rejects only with a {@link SekyroError} (programmer errors).
63
+ * @public
64
+ */
65
+ declare function startSekyro(config: SekyroConfig): Promise<SekyroResult>;
66
+ /**
67
+ * Version of the bundled native core: `'android 0.1.0-rc.2'` / `'ios 0.1.0-rc.2'`.
68
+ * @public
69
+ */
70
+ declare function nativeVersion(): string;
71
+
72
+ /** Props of {@link SekyroFlow}: the config plus result and event callbacks. @public */
73
+ type SekyroFlowProps = Omit<SekyroConfig, 'onEvent'> & {
74
+ onResult: (result: SekyroResult) => void;
75
+ onEvent?: (event: SekyroEvent) => void;
76
+ };
77
+ /**
78
+ * Declarative drop-in: presents the native flow full screen on mount and renders nothing.
79
+ * Mount it once per session; to restart, remount with a new `clientToken`.
80
+ * @public
81
+ */
82
+ declare function SekyroFlow(props: SekyroFlowProps): null;
83
+
84
+ /** Programmer errors only. Flow outcomes resolve as a {@link SekyroResult}. @public */
85
+ type SekyroErrorCode = 'invalid_config' | 'flow_in_progress' | 'no_activity' | 'unsupported_platform';
86
+ /** Thrown (rejected) by {@link startSekyro} on a programmer error. @public */
87
+ declare class SekyroError extends Error {
88
+ /** Machine-readable cause. */
89
+ readonly code: SekyroErrorCode;
90
+ constructor(code: SekyroErrorCode, message?: string);
91
+ }
92
+
93
+ /**
94
+ * Version of this npm package.
95
+ * @public
96
+ */
97
+ declare const SDK_VERSION: string;
98
+
99
+ export { SDK_VERSION, type SekyroColorRole, type SekyroConfig, type SekyroDisplayStatus, SekyroError, type SekyroErrorCode, type SekyroEvent, SekyroFlow, type SekyroFlowProps, type SekyroLocale, type SekyroResult, type SekyroStep, type SekyroTheme, nativeVersion, startSekyro };
@@ -0,0 +1,99 @@
1
+ /** UI language of the native flow. @public */
2
+ type SekyroLocale = 'ar' | 'fr' | 'en';
3
+ /** Themeable colour roles, shared by the Android and iOS cores. @public */
4
+ type SekyroColorRole = 'brand' | 'brandFg' | 'bg' | 'surface' | 'fg' | 'fgMuted' | 'border' | 'danger' | 'success' | 'warning';
5
+ /** Runtime theme applied over the native defaults. @public */
6
+ interface SekyroTheme {
7
+ /** `'#RRGGBB'` per role. */
8
+ colors?: Partial<Record<SekyroColorRole, string>>;
9
+ /** Corner radius in dp / pt, 0–32. */
10
+ radius?: number;
11
+ /** iOS only in 0.1 (Android ignores it). */
12
+ fontFamily?: string;
13
+ /** Colour scheme; `'system'` follows the device. */
14
+ darkMode?: 'system' | 'light' | 'dark';
15
+ }
16
+ /**
17
+ * Screen of the native flow, as reported by step events (`eid`: the national eID sign-in, SDK spec §3.2).
18
+ * Flow model 2026-10-23: `contact_choice` (phone or email), `identity_summary` (check the details read — the event never
19
+ * carries them), `portrait` (the ICAO portrait on its own). `consent` is never emitted (consent is one line on the first
20
+ * screen); it stays in the union only because the public API may only grow. @public
21
+ */
22
+ type SekyroStep = 'consent' | 'method' | 'phone_otp' | 'email_otp' | 'registry' | 'document' | 'nfc' | 'liveness' | 'processing' | 'result' | 'update_required' | 'eid' | 'contact_choice' | 'identity_summary' | 'portrait';
23
+ /** Telemetry only; carries no personal data. @public */
24
+ type SekyroEvent = {
25
+ type: 'step';
26
+ step: SekyroStep;
27
+ retryReason?: string;
28
+ } | {
29
+ type: 'waiting_for_network';
30
+ };
31
+ /** Options for {@link startSekyro}. @public */
32
+ interface SekyroConfig {
33
+ /** One-time session token from your backend (`sessions.create`). Never logged. */
34
+ clientToken: string;
35
+ /** e.g. `'https://api-staging.sekyro.ai'`; defaults to production. */
36
+ apiOrigin?: string;
37
+ /** UI language of the flow; required when `strings` is set. */
38
+ locale?: SekyroLocale;
39
+ /** Colours, radius, font and colour scheme. */
40
+ theme?: SekyroTheme;
41
+ /** Catalog-key overrides per locale; only `strings[locale]` is used, so `locale` is required. */
42
+ strings?: Partial<Record<SekyroLocale, Record<string, string>>>;
43
+ /** Step telemetry of this flow only (no personal data). */
44
+ onEvent?: (event: SekyroEvent) => void;
45
+ }
46
+ /** What the end user was shown; not the decision. @public */
47
+ type SekyroDisplayStatus = 'approved' | 'review_pending' | 'rejected';
48
+ /** Display-only outcome; read the decision server-side (webhooks / `sessions.get`). @public */
49
+ type SekyroResult = {
50
+ status: 'finished';
51
+ sessionId: string;
52
+ displayStatus: SekyroDisplayStatus;
53
+ } | {
54
+ status: 'cancelled';
55
+ } | {
56
+ status: 'failed';
57
+ code: string;
58
+ };
59
+
60
+ /**
61
+ * Present the Sekyro drop-in full screen. Resolves a display-only result; read the decision
62
+ * server-side. Rejects only with a {@link SekyroError} (programmer errors).
63
+ * @public
64
+ */
65
+ declare function startSekyro(config: SekyroConfig): Promise<SekyroResult>;
66
+ /**
67
+ * Version of the bundled native core: `'android 0.1.0-rc.2'` / `'ios 0.1.0-rc.2'`.
68
+ * @public
69
+ */
70
+ declare function nativeVersion(): string;
71
+
72
+ /** Props of {@link SekyroFlow}: the config plus result and event callbacks. @public */
73
+ type SekyroFlowProps = Omit<SekyroConfig, 'onEvent'> & {
74
+ onResult: (result: SekyroResult) => void;
75
+ onEvent?: (event: SekyroEvent) => void;
76
+ };
77
+ /**
78
+ * Declarative drop-in: presents the native flow full screen on mount and renders nothing.
79
+ * Mount it once per session; to restart, remount with a new `clientToken`.
80
+ * @public
81
+ */
82
+ declare function SekyroFlow(props: SekyroFlowProps): null;
83
+
84
+ /** Programmer errors only. Flow outcomes resolve as a {@link SekyroResult}. @public */
85
+ type SekyroErrorCode = 'invalid_config' | 'flow_in_progress' | 'no_activity' | 'unsupported_platform';
86
+ /** Thrown (rejected) by {@link startSekyro} on a programmer error. @public */
87
+ declare class SekyroError extends Error {
88
+ /** Machine-readable cause. */
89
+ readonly code: SekyroErrorCode;
90
+ constructor(code: SekyroErrorCode, message?: string);
91
+ }
92
+
93
+ /**
94
+ * Version of this npm package.
95
+ * @public
96
+ */
97
+ declare const SDK_VERSION: string;
98
+
99
+ export { SDK_VERSION, type SekyroColorRole, type SekyroConfig, type SekyroDisplayStatus, SekyroError, type SekyroErrorCode, type SekyroEvent, SekyroFlow, type SekyroFlowProps, type SekyroLocale, type SekyroResult, type SekyroStep, type SekyroTheme, nativeVersion, startSekyro };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{TurboModuleRegistry as m}from"react-native";var f=m.get("SekyroReactNative");var a=class extends Error{code;constructor(r,t=r){super(t),this.name="SekyroError",this.code=r}};var v=500,_=new Set(["ar","fr","en"]),E=["brand","brandFg","bg","surface","fg","fgMuted","border","danger","success","warning"],R=new Set(["clientToken","apiOrigin","locale","theme","strings","onEvent"]),N=/^#[0-9a-fA-F]{6}$/,w=/^http:\/\/(localhost|127\.0\.0\.1|10\.0\.2\.2)(:\d+)?$/,i=e=>{throw new a("invalid_config",`invalid config: ${e}`)};function d(e){(typeof e!="object"||e===null)&&i("config");for(let s of Object.keys(e))R.has(s)||i(`unknown key "${s.slice(0,32)}"`);(typeof e.clientToken!="string"||e.clientToken.length===0)&&i("clientToken");let r={clientToken:e.clientToken};e.apiOrigin!==void 0&&(typeof e.apiOrigin=="string"&&(/^https:\/\/[a-z0-9.-]+(:\d+)?$/i.test(e.apiOrigin)||__DEV__&&w.test(e.apiOrigin))||i("apiOrigin"),r.apiOrigin=e.apiOrigin),e.locale!==void 0&&(_.has(e.locale)||i("locale"),r.locale=e.locale);let t=e.theme;if(t){if(t.colors){let s={};for(let[l,n]of Object.entries(t.colors))n!==void 0&&((!E.includes(l)||typeof n!="string"||!N.test(n))&&i(`theme.colors.${l.slice(0,32)}`),s[l]=n);r.colors=s}t.radius!==void 0&&((typeof t.radius!="number"||!Number.isFinite(t.radius)||t.radius<0||t.radius>32)&&i("theme.radius"),r.radius=t.radius),t.fontFamily!==void 0&&((typeof t.fontFamily!="string"||!/^[\w .-]{1,64}$/.test(t.fontFamily))&&i("theme.fontFamily"),r.fontFamily=t.fontFamily),t.darkMode!==void 0&&(["system","light","dark"].includes(t.darkMode)||i("theme.darkMode"),r.darkMode=t.darkMode)}if(e.strings){e.locale||i("strings need locale");let s=e.strings[e.locale]??{},l={},n=Object.entries(s).filter(([,o])=>o!==void 0);n.length>v&&i("strings (too many overrides)");for(let[o,p]of n)/^[a-zA-Z0-9_.]{1,96}$/.test(o)||i("strings key"),(typeof p!="string"||p.length>2e3)&&i(`strings.${o}`),l[o]=p;r.strings=l}return r}var h=new Set(["method","phone_otp","email_otp","registry","document","nfc","liveness","processing","result","update_required","eid","contact_choice","identity_summary","portrait"]);function c(e){if(e.type==="waiting_for_network")return{type:"waiting_for_network"};if(e.type!=="step"||!h.has(e.step))return null;let r=e.step,t=typeof e.retryReason=="string"&&/^[a-z0-9_]{1,64}$/.test(e.retryReason)?e.retryReason:void 0;return t?{type:"step",step:r,retryReason:t}:{type:"step",step:r}}var C=new Set(["approved","review_pending","rejected"]);function S(e){if(e.status==="cancelled")return{status:"cancelled"};if(e.status==="failed")return{status:"failed",code:e.code||"unknown_error"};if(e.status==="finished"&&e.sessionId){if(e.displayStatus==="expired")return{status:"failed",code:"session_expired"};if(C.has(e.displayStatus))return{status:"finished",sessionId:e.sessionId,displayStatus:e.displayStatus}}return{status:"failed",code:"unknown_error"}}function k(){if(!f)throw new a("unsupported_platform","the SekyroReactNative native module is not linked (New Architecture app, pod install / Gradle sync)");return f}var y=!1,O=new Set(["no_activity","unsupported_platform","flow_in_progress","invalid_config"]);async function u(e){let r=d(e),t=k();if(y)throw new a("flow_in_progress");y=!0;let s=e.onEvent,l=s?t.onEvent(n=>{let o=c(n);o&&s(o)}):void 0;try{return S(await t.start(r))}catch(n){let o=n?.code;if(typeof o=="string"&&O.has(o))throw new a(o);return{status:"failed",code:"unknown_error"}}finally{l?.remove(),y=!1}}function x(){return k().nativeVersion()}import{useEffect as b,useRef as g}from"react";function T(e){let r=g(null),t=g(e);return t.current=e,b(()=>{if(r.current===e.clientToken)return;r.current=e.clientToken;let{onResult:s,onEvent:l,...n}=t.current;u({...n,onEvent:o=>t.current.onEvent?.(o)}).then(o=>t.current.onResult(o),o=>t.current.onResult({status:"failed",code:o instanceof a?o.code:"unknown_error"}))},[e.clientToken]),null}var F="0.1.0-rc.1";export{F as SDK_VERSION,a as SekyroError,T as SekyroFlow,x as nativeVersion,u as startSekyro};
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,5 @@
1
+ // @sekyro/react-native — iOS TurboModule (New Architecture only).
2
+ #import <RNSekyroReactNativeSpec/RNSekyroReactNativeSpec.h>
3
+
4
+ @interface RCTSekyroReactNative : NativeSekyroReactNativeSpecBase <NativeSekyroReactNativeSpec>
5
+ @end