@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 +13 -0
- package/LICENSE +29 -0
- package/README.md +76 -2
- package/SekyroReactNative.podspec +22 -0
- package/android/build.gradle +38 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/ai/sekyro/reactnative/ConfigMapper.kt +73 -0
- package/android/src/main/java/ai/sekyro/reactnative/SekyroReactNativeModule.kt +107 -0
- package/android/src/main/java/ai/sekyro/reactnative/SekyroReactNativePackage.kt +26 -0
- package/app.plugin.js +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +99 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.js +1 -0
- package/dist/package.json +1 -0
- package/ios/RCTSekyroReactNative.h +5 -0
- package/ios/RCTSekyroReactNative.mm +57 -0
- package/ios/SekyroRNBridge.swift +108 -0
- package/package.json +103 -4
- package/plugin/android.js +24 -0
- package/plugin/index.js +76 -0
- package/plugin/ios.js +61 -0
- package/sbom.cdx.json +153 -0
- package/scripts/sekyro_pods.rb +27 -0
- package/src/specs/NativeSekyroReactNative.ts +46 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// @sekyro/react-native — iOS TurboModule: rebuilds a plain dictionary from the codegen struct and hands it to
|
|
2
|
+
// SekyroRNBridge (Swift). Never logs the config: the client token is a bearer credential.
|
|
3
|
+
#import "RCTSekyroReactNative.h"
|
|
4
|
+
#if __has_include(<SekyroReactNative/SekyroReactNative-Swift.h>)
|
|
5
|
+
#import <SekyroReactNative/SekyroReactNative-Swift.h>
|
|
6
|
+
#else
|
|
7
|
+
#import "SekyroReactNative-Swift.h"
|
|
8
|
+
#endif
|
|
9
|
+
|
|
10
|
+
@implementation RCTSekyroReactNative
|
|
11
|
+
|
|
12
|
+
+ (NSString *)moduleName { return @"SekyroReactNative"; }
|
|
13
|
+
|
|
14
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
|
|
15
|
+
return std::make_shared<facebook::react::NativeSekyroReactNativeSpecJSI>(params);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
- (NSString *)nativeVersion { return [SekyroRNBridge nativeVersion]; }
|
|
19
|
+
|
|
20
|
+
- (void)start:(JS::NativeSekyroReactNative::NativeConfig &)config
|
|
21
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
22
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
23
|
+
// The struct is only valid during this call: copy everything out first.
|
|
24
|
+
NSMutableDictionary *d = [NSMutableDictionary new];
|
|
25
|
+
if (config.clientToken()) d[@"clientToken"] = config.clientToken();
|
|
26
|
+
if (config.apiOrigin()) d[@"apiOrigin"] = config.apiOrigin();
|
|
27
|
+
if (config.locale()) d[@"locale"] = config.locale();
|
|
28
|
+
if (config.fontFamily()) d[@"fontFamily"] = config.fontFamily();
|
|
29
|
+
if (config.darkMode()) d[@"darkMode"] = config.darkMode();
|
|
30
|
+
if (config.radius().has_value()) d[@"radius"] = @(config.radius().value());
|
|
31
|
+
id strings = config.strings();
|
|
32
|
+
if ([strings isKindOfClass:[NSDictionary class]]) d[@"strings"] = strings;
|
|
33
|
+
if (config.colors().has_value()) {
|
|
34
|
+
auto c = config.colors().value();
|
|
35
|
+
NSMutableDictionary *colors = [NSMutableDictionary new];
|
|
36
|
+
if (c.brand()) colors[@"brand"] = c.brand();
|
|
37
|
+
if (c.brandFg()) colors[@"brandFg"] = c.brandFg();
|
|
38
|
+
if (c.bg()) colors[@"bg"] = c.bg();
|
|
39
|
+
if (c.surface()) colors[@"surface"] = c.surface();
|
|
40
|
+
if (c.fg()) colors[@"fg"] = c.fg();
|
|
41
|
+
if (c.fgMuted()) colors[@"fgMuted"] = c.fgMuted();
|
|
42
|
+
if (c.border()) colors[@"border"] = c.border();
|
|
43
|
+
if (c.danger()) colors[@"danger"] = c.danger();
|
|
44
|
+
if (c.success()) colors[@"success"] = c.success();
|
|
45
|
+
if (c.warning()) colors[@"warning"] = c.warning();
|
|
46
|
+
d[@"colors"] = colors;
|
|
47
|
+
}
|
|
48
|
+
__weak RCTSekyroReactNative *weakSelf = self;
|
|
49
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
50
|
+
NSString *error = [SekyroRNBridge start:d
|
|
51
|
+
onEvent:^(NSDictionary *event) { [weakSelf emitOnEvent:event]; }
|
|
52
|
+
completion:^(NSDictionary *result) { resolve(result); }];
|
|
53
|
+
if (error) reject(error, error, nil);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// @sekyro/react-native — Swift side of the iOS TurboModule (RCTSekyroReactNative.mm calls it).
|
|
2
|
+
import Foundation
|
|
3
|
+
import SekyroKit
|
|
4
|
+
import UIKit
|
|
5
|
+
|
|
6
|
+
@objc(SekyroRNBridge)
|
|
7
|
+
public final class SekyroRNBridge: NSObject {
|
|
8
|
+
@MainActor private static var running = false
|
|
9
|
+
@MainActor private static var seq: Int32 = 0
|
|
10
|
+
|
|
11
|
+
@objc public static func nativeVersion() -> String { "ios \(Sekyro.sdkVersion)" }
|
|
12
|
+
|
|
13
|
+
struct InvalidConfig: Error {}
|
|
14
|
+
|
|
15
|
+
/// The JS layer validated the config; this re-checks the shape. Never logs it (the token is a bearer credential).
|
|
16
|
+
static func parse(_ d: NSDictionary) throws -> SekyroConfig {
|
|
17
|
+
guard let token = d["clientToken"] as? String, !token.isEmpty else { throw InvalidConfig() }
|
|
18
|
+
var origin: URL?
|
|
19
|
+
if let raw = d["apiOrigin"] as? String {
|
|
20
|
+
guard let url = URL(string: raw), url.scheme == "https" || url.host == "localhost" || url.host == "127.0.0.1" else { throw InvalidConfig() }
|
|
21
|
+
origin = url
|
|
22
|
+
}
|
|
23
|
+
let preferred = Locale.preferredLanguages.lazy.map { String($0.prefix(2)) }.first { ["ar", "fr", "en"].contains($0) }
|
|
24
|
+
let locale = SekyroLocale(rawValue: (d["locale"] as? String) ?? preferred ?? "fr") ?? .fr
|
|
25
|
+
let colors = d["colors"] as? [String: Any] ?? [:]
|
|
26
|
+
func color(_ key: String) throws -> SekyroColor? {
|
|
27
|
+
guard let value = colors[key] else { return nil }
|
|
28
|
+
guard let hex = value as? String, let c = SekyroColor(hex: hex) else { throw InvalidConfig() }
|
|
29
|
+
return c
|
|
30
|
+
}
|
|
31
|
+
let mode: SekyroDarkMode
|
|
32
|
+
switch d["darkMode"] as? String {
|
|
33
|
+
case "light": mode = .light
|
|
34
|
+
case "dark": mode = .dark
|
|
35
|
+
default: mode = .system
|
|
36
|
+
}
|
|
37
|
+
var radius: CGFloat?
|
|
38
|
+
if let r = d["radius"] as? NSNumber {
|
|
39
|
+
guard (0...32).contains(r.doubleValue) else { throw InvalidConfig() }
|
|
40
|
+
radius = CGFloat(truncating: r)
|
|
41
|
+
}
|
|
42
|
+
let strings = (d["strings"] as? [String: Any] ?? [:]).compactMapValues { $0 as? String }
|
|
43
|
+
let theme = SekyroTheme(
|
|
44
|
+
brand: try color("brand"), brandFg: try color("brandFg"), background: try color("bg"), surface: try color("surface"),
|
|
45
|
+
foreground: try color("fg"), foregroundMuted: try color("fgMuted"), border: try color("border"),
|
|
46
|
+
danger: try color("danger"), success: try color("success"), warning: try color("warning"),
|
|
47
|
+
cornerRadius: radius, fontFamily: d["fontFamily"] as? String, darkMode: mode, strings: strings)
|
|
48
|
+
return SekyroConfig(clientToken: token, apiOrigin: origin, locale: locale, theme: theme)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static func wire(_ result: SekyroResult) -> NSDictionary {
|
|
52
|
+
switch result {
|
|
53
|
+
case .finished(let sessionId, let status):
|
|
54
|
+
return ["status": "finished", "sessionId": sessionId, "displayStatus": status.rawValue]
|
|
55
|
+
case .cancelled:
|
|
56
|
+
return ["status": "cancelled"]
|
|
57
|
+
case .failed(let code):
|
|
58
|
+
return ["status": "failed", "code": code]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Presents the drop-in. Returns nil when started, else an error code (`invalid_config`, `no_activity`, `flow_in_progress`).
|
|
63
|
+
@MainActor @objc public static func start(_ config: NSDictionary,
|
|
64
|
+
onEvent: @escaping (NSDictionary) -> Void,
|
|
65
|
+
completion: @escaping (NSDictionary) -> Void) -> String? {
|
|
66
|
+
guard let parsed = try? parse(config) else { return "invalid_config" }
|
|
67
|
+
guard !running else { return "flow_in_progress" }
|
|
68
|
+
// UIKit only logs (and never calls back) when the presenter is off-window, mid-transition or
|
|
69
|
+
// already presenting — refuse up front so the promise can never stay pending.
|
|
70
|
+
guard let presenter = topViewController(), canPresent(from: presenter) else { return "no_activity" }
|
|
71
|
+
running = true
|
|
72
|
+
Sekyro.start(from: presenter, config: parsed, onEvent: { event in
|
|
73
|
+
seq &+= 1
|
|
74
|
+
switch event {
|
|
75
|
+
case .step(let step, let reason):
|
|
76
|
+
var e: [String: Any] = ["type": "step", "step": step.rawValue, "seq": NSNumber(value: seq)]
|
|
77
|
+
if let reason { e["retryReason"] = reason }
|
|
78
|
+
onEvent(e as NSDictionary)
|
|
79
|
+
case .waitingForNetwork:
|
|
80
|
+
onEvent(["type": "waiting_for_network", "seq": NSNumber(value: seq)])
|
|
81
|
+
}
|
|
82
|
+
}, completion: { result in
|
|
83
|
+
running = false
|
|
84
|
+
completion(wire(result))
|
|
85
|
+
})
|
|
86
|
+
return nil
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@MainActor private static func topViewController() -> UIViewController? {
|
|
90
|
+
let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
|
|
91
|
+
let foreground = scenes.filter { $0.activationState == .foregroundActive }
|
|
92
|
+
// The key window across every foreground scene first, then any foreground window, then any scene.
|
|
93
|
+
let window = foreground.lazy.flatMap { $0.windows }.first { $0.isKeyWindow }
|
|
94
|
+
?? foreground.lazy.flatMap { $0.windows }.first
|
|
95
|
+
?? scenes.first?.windows.first
|
|
96
|
+
var top = window?.rootViewController
|
|
97
|
+
while let presented = top?.presentedViewController, !presented.isBeingDismissed { top = presented }
|
|
98
|
+
return top
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// True when `presenter` can present right now (in a window, not transitioning, not presenting).
|
|
102
|
+
@MainActor static func canPresent(from presenter: UIViewController) -> Bool {
|
|
103
|
+
presenter.viewIfLoaded?.window != nil
|
|
104
|
+
&& !presenter.isBeingPresented
|
|
105
|
+
&& !presenter.isBeingDismissed
|
|
106
|
+
&& presenter.presentedViewController == nil
|
|
107
|
+
}
|
|
108
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,105 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sekyro/react-native",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0-rc.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Sekyro AI eKYC for React Native — glue over the binary Android and iOS SDKs (New Architecture)",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/IBTIKAR-Technologies/ibtikar-identity.git",
|
|
10
|
+
"directory": "sekyro-mobile/react-native"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"sekyro",
|
|
14
|
+
"kyc",
|
|
15
|
+
"ekyc",
|
|
16
|
+
"identity-verification",
|
|
17
|
+
"nfc",
|
|
18
|
+
"react-native",
|
|
19
|
+
"turbomodule",
|
|
20
|
+
"mauritania"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"main": "./dist/index.cjs",
|
|
29
|
+
"module": "./dist/index.js",
|
|
30
|
+
"react-native": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"import": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"require": {
|
|
39
|
+
"types": "./dist/index.d.cts",
|
|
40
|
+
"default": "./dist/index.cjs"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"./app.plugin.js": "./app.plugin.js",
|
|
44
|
+
"./package.json": "./package.json"
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"dist",
|
|
48
|
+
"src/specs",
|
|
49
|
+
"android/build.gradle",
|
|
50
|
+
"android/src/main",
|
|
51
|
+
"ios",
|
|
52
|
+
"SekyroReactNative.podspec",
|
|
53
|
+
"scripts/sekyro_pods.rb",
|
|
54
|
+
"app.plugin.js",
|
|
55
|
+
"plugin",
|
|
56
|
+
"README.md",
|
|
57
|
+
"CHANGELOG.md",
|
|
58
|
+
"LICENSE",
|
|
59
|
+
"sbom.cdx.json"
|
|
60
|
+
],
|
|
61
|
+
"sideEffects": false,
|
|
62
|
+
"scripts": {
|
|
63
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
64
|
+
"test": "jest --maxWorkers=2",
|
|
65
|
+
"build": "tsup && node -e \"const f=require('fs');f.renameSync('dist/index.d.ts','dist/index.d.cts');f.renameSync('dist/index.d.mts','dist/index.d.ts');f.writeFileSync('dist/package.json','{\\\"type\\\":\\\"module\\\"}\\n')\"",
|
|
66
|
+
"api:report": "api-extractor run --local",
|
|
67
|
+
"api:check": "api-extractor run"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"react": ">=18.3",
|
|
71
|
+
"react-native": ">=0.76"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@expo/config-plugins": "54.0.2",
|
|
75
|
+
"@microsoft/api-extractor": "7.52.10",
|
|
76
|
+
"@react-native/babel-preset": "0.84.1",
|
|
77
|
+
"@testing-library/react-native": "13.3.3",
|
|
78
|
+
"@types/jest": "29.5.14",
|
|
79
|
+
"@types/react": "19.2.2",
|
|
80
|
+
"jest": "29.7.0",
|
|
81
|
+
"react": "19.2.3",
|
|
82
|
+
"react-native": "0.84.1",
|
|
83
|
+
"react-test-renderer": "19.2.3",
|
|
84
|
+
"ts-jest": "29.4.4",
|
|
85
|
+
"tsup": "8.5.0",
|
|
86
|
+
"typescript": "5.9.3"
|
|
87
|
+
},
|
|
88
|
+
"codegenConfig": {
|
|
89
|
+
"name": "RNSekyroReactNativeSpec",
|
|
90
|
+
"type": "modules",
|
|
91
|
+
"jsSrcsDir": "src/specs",
|
|
92
|
+
"android": {
|
|
93
|
+
"javaPackageName": "ai.sekyro.reactnative"
|
|
94
|
+
},
|
|
95
|
+
"ios": {
|
|
96
|
+
"modulesProvider": {
|
|
97
|
+
"SekyroReactNative": "RCTSekyroReactNative"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"sekyro": {
|
|
102
|
+
"android": "0.1.0-rc.2",
|
|
103
|
+
"ios": "0.1.0-rc.2"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Android needs no manifest edits: the sekyro-ui AAR manifest merges CAMERA, NFC, the optional
|
|
2
|
+
// features and SekyroActivity. The only requirement checked here is the core's minSdk.
|
|
3
|
+
const MIN_SDK = 24;
|
|
4
|
+
|
|
5
|
+
/** @param {Record<string, any>} config @returns {number | undefined} */
|
|
6
|
+
function buildPropertiesMinSdk(config) {
|
|
7
|
+
for (const entry of config.plugins ?? []) {
|
|
8
|
+
if (Array.isArray(entry) && entry[0] === 'expo-build-properties') {
|
|
9
|
+
const value = entry[1]?.android?.minSdkVersion;
|
|
10
|
+
if (value !== undefined) return Number(value);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** @param {Record<string, any>} config */
|
|
17
|
+
function assertMinSdk(config) {
|
|
18
|
+
const minSdk = buildPropertiesMinSdk(config);
|
|
19
|
+
if (minSdk !== undefined && !(minSdk >= MIN_SDK)) {
|
|
20
|
+
throw new Error(`@sekyro/react-native needs Android minSdkVersion >= ${MIN_SDK} (expo-build-properties sets ${minSdk})`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { assertMinSdk, MIN_SDK };
|
package/plugin/index.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Expo config plugin: plugins: [['@sekyro/react-native', { nfc: true, appAttest: 'production' }]]
|
|
2
|
+
|
|
3
|
+
/** `expo/config-plugins` (Expo's documented re-export, resolvable from the app) before `@expo/config-plugins`. */
|
|
4
|
+
function loadConfigPlugins() {
|
|
5
|
+
try {
|
|
6
|
+
return require('expo/config-plugins');
|
|
7
|
+
} catch {
|
|
8
|
+
return require('@expo/config-plugins');
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const {
|
|
12
|
+
withInfoPlist,
|
|
13
|
+
withEntitlementsPlist,
|
|
14
|
+
withDangerousMod,
|
|
15
|
+
withAndroidManifest,
|
|
16
|
+
createRunOncePlugin,
|
|
17
|
+
} = loadConfigPlugins();
|
|
18
|
+
const fs = require('node:fs');
|
|
19
|
+
const path = require('node:path');
|
|
20
|
+
const ios = require('./ios');
|
|
21
|
+
const { assertMinSdk } = require('./android');
|
|
22
|
+
const pkg = require('../package.json');
|
|
23
|
+
|
|
24
|
+
const KNOWN = new Set(['nfc', 'appAttest', 'cameraPermission', 'nfcPermission']);
|
|
25
|
+
|
|
26
|
+
function validate(options) {
|
|
27
|
+
if (typeof options !== 'object' || options === null || Array.isArray(options)) {
|
|
28
|
+
throw new Error('@sekyro/react-native plugin: options must be an object');
|
|
29
|
+
}
|
|
30
|
+
for (const k of Object.keys(options)) {
|
|
31
|
+
if (!KNOWN.has(k)) throw new Error(`@sekyro/react-native plugin: unknown option "${k}"`);
|
|
32
|
+
}
|
|
33
|
+
if (options.nfc !== undefined && typeof options.nfc !== 'boolean') {
|
|
34
|
+
throw new Error('@sekyro/react-native plugin: nfc must be a boolean');
|
|
35
|
+
}
|
|
36
|
+
if (options.appAttest !== undefined && !['production', 'development', false].includes(options.appAttest)) {
|
|
37
|
+
throw new Error("@sekyro/react-native plugin: appAttest must be 'production', 'development' or false");
|
|
38
|
+
}
|
|
39
|
+
for (const k of ['cameraPermission', 'nfcPermission']) {
|
|
40
|
+
if (options[k] !== undefined && (typeof options[k] !== 'string' || options[k].length === 0)) {
|
|
41
|
+
throw new Error(`@sekyro/react-native plugin: ${k} must be a non-empty string`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {import('@expo/config-plugins').ExportedConfig} config
|
|
48
|
+
* @param {import('./ios').SekyroPluginOptions} [options]
|
|
49
|
+
*/
|
|
50
|
+
function withSekyro(config, options = {}) {
|
|
51
|
+
validate(options ?? {});
|
|
52
|
+
const opts = options ?? {};
|
|
53
|
+
config = withInfoPlist(config, (c) => {
|
|
54
|
+
c.modResults = ios.withSekyroInfoPlist(c.modResults, opts);
|
|
55
|
+
return c;
|
|
56
|
+
});
|
|
57
|
+
config = withEntitlementsPlist(config, (c) => {
|
|
58
|
+
c.modResults = ios.withSekyroEntitlements(c.modResults, opts);
|
|
59
|
+
return c;
|
|
60
|
+
});
|
|
61
|
+
config = withDangerousMod(config, [
|
|
62
|
+
'ios',
|
|
63
|
+
(c) => {
|
|
64
|
+
const file = path.join(c.modRequest.platformProjectRoot, 'Podfile');
|
|
65
|
+
fs.writeFileSync(file, ios.addSekyroPod(fs.readFileSync(file, 'utf8')));
|
|
66
|
+
return c;
|
|
67
|
+
},
|
|
68
|
+
]);
|
|
69
|
+
config = withAndroidManifest(config, (c) => {
|
|
70
|
+
assertMinSdk(c);
|
|
71
|
+
return c;
|
|
72
|
+
});
|
|
73
|
+
return config;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = createRunOncePlugin(withSekyro, pkg.name, pkg.version);
|
package/plugin/ios.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Pure helpers for the Expo config plugin (iOS). No Expo imports, so they are unit-testable.
|
|
2
|
+
const AIDS = ['A0000002471001', 'A0000002472001']; // ICAO eMRTD LDS1 application + its alternate (Plan 3 sample)
|
|
3
|
+
const AID_KEY = 'com.apple.developer.nfc.readersession.iso7816.select-identifiers';
|
|
4
|
+
const NFC_FORMATS_KEY = 'com.apple.developer.nfc.readersession.formats';
|
|
5
|
+
const APP_ATTEST_KEY = 'com.apple.developer.devicecheck.appattest-environment';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {object} SekyroPluginOptions
|
|
9
|
+
* @property {boolean} [nfc] Read the document chip (default true).
|
|
10
|
+
* @property {'production' | 'development' | false} [appAttest] App Attest environment (default 'production').
|
|
11
|
+
* @property {string} [cameraPermission] NSCameraUsageDescription when the app has none.
|
|
12
|
+
* @property {string} [nfcPermission] NFCReaderUsageDescription when the app has none.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {Record<string, any>} plist
|
|
17
|
+
* @param {SekyroPluginOptions} [options]
|
|
18
|
+
* @returns {Record<string, any>}
|
|
19
|
+
*/
|
|
20
|
+
function withSekyroInfoPlist(plist, { nfc = true, cameraPermission, nfcPermission } = {}) {
|
|
21
|
+
const out = { ...plist };
|
|
22
|
+
out.NSCameraUsageDescription ??=
|
|
23
|
+
cameraPermission ?? 'The camera is used to capture your face and your identity document for verification.';
|
|
24
|
+
if (nfc) {
|
|
25
|
+
out.NFCReaderUsageDescription ??= nfcPermission ?? 'NFC is used to read the chip of your passport or identity card.';
|
|
26
|
+
out[AID_KEY] = [...new Set([...(out[AID_KEY] ?? []), ...AIDS])];
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {Record<string, any>} ent
|
|
33
|
+
* @param {SekyroPluginOptions} [options]
|
|
34
|
+
* @returns {Record<string, any>}
|
|
35
|
+
*/
|
|
36
|
+
function withSekyroEntitlements(ent, { nfc = true, appAttest = 'production' } = {}) {
|
|
37
|
+
const out = { ...ent };
|
|
38
|
+
if (nfc) out[NFC_FORMATS_KEY] = [...new Set([...(out[NFC_FORMATS_KEY] ?? []), 'TAG'])];
|
|
39
|
+
if (appAttest) out[APP_ATTEST_KEY] = appAttest;
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const REQUIRE = `require Pod::Executable.execute_command('node', ['-p', 'require.resolve("@sekyro/react-native/package.json", {paths: [process.argv[1]]})', __dir__]).strip.sub(/package\\.json$/, 'scripts/sekyro_pods.rb')`;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {string} podfile
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
function addSekyroPod(podfile) {
|
|
50
|
+
if (podfile.includes('sekyro_kit_pod!')) return podfile;
|
|
51
|
+
const anchor = /(config = use_native_modules!.*\n)/;
|
|
52
|
+
if (!anchor.test(podfile)) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
'@sekyro/react-native plugin: the Podfile has no `config = use_native_modules!` line — add `sekyro_kit_pod!` to your target by hand',
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
const withPod = podfile.replace(anchor, `$1 sekyro_kit_pod!\n`);
|
|
58
|
+
return withPod.includes(REQUIRE) ? withPod : `${REQUIRE}\n${withPod}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = { withSekyroInfoPlist, withSekyroEntitlements, addSekyroPod, AIDS };
|
package/sbom.cdx.json
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
|
|
3
|
+
"bomFormat": "CycloneDX",
|
|
4
|
+
"specVersion": "1.6",
|
|
5
|
+
"version": 1,
|
|
6
|
+
"metadata": {
|
|
7
|
+
"tools": {
|
|
8
|
+
"components": [
|
|
9
|
+
{
|
|
10
|
+
"type": "application",
|
|
11
|
+
"name": "npm",
|
|
12
|
+
"version": "11.12.1"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"type": "library",
|
|
16
|
+
"name": "cyclonedx-library",
|
|
17
|
+
"group": "@cyclonedx",
|
|
18
|
+
"version": "10.3.0",
|
|
19
|
+
"author": "Jan Kowalleck",
|
|
20
|
+
"description": "Core functionality of CycloneDX for JavaScript (Node.js or WebBrowser).",
|
|
21
|
+
"licenses": [
|
|
22
|
+
{
|
|
23
|
+
"license": {
|
|
24
|
+
"id": "Apache-2.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"externalReferences": [
|
|
29
|
+
{
|
|
30
|
+
"url": "https://github.com/CycloneDX/cyclonedx-javascript-library/issues",
|
|
31
|
+
"type": "issue-tracker",
|
|
32
|
+
"comment": "as detected from PackageJson property \"bugs.url\""
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"url": "git+https://github.com/CycloneDX/cyclonedx-javascript-library.git",
|
|
36
|
+
"type": "vcs",
|
|
37
|
+
"comment": "as detected from PackageJson property \"repository.url\""
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"url": "https://github.com/CycloneDX/cyclonedx-javascript-library#readme",
|
|
41
|
+
"type": "website",
|
|
42
|
+
"comment": "as detected from PackageJson property \"homepage\""
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"properties": [
|
|
46
|
+
{
|
|
47
|
+
"name": "cdx:npm:package:constraint:engine:node",
|
|
48
|
+
"value": ">=20.18.0"
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"type": "application",
|
|
54
|
+
"name": "cyclonedx-npm",
|
|
55
|
+
"group": "@cyclonedx",
|
|
56
|
+
"version": "6.0.1",
|
|
57
|
+
"author": "Jan Kowalleck",
|
|
58
|
+
"description": "Create CycloneDX Software Bill of Materials (SBOM) from NPM projects.",
|
|
59
|
+
"licenses": [
|
|
60
|
+
{
|
|
61
|
+
"license": {
|
|
62
|
+
"id": "Apache-2.0"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"externalReferences": [
|
|
67
|
+
{
|
|
68
|
+
"url": "https://github.com/CycloneDX/cyclonedx-node-npm/issues",
|
|
69
|
+
"type": "issue-tracker",
|
|
70
|
+
"comment": "as detected from PackageJson property \"bugs.url\""
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"url": "git+https://github.com/CycloneDX/cyclonedx-node-npm.git",
|
|
74
|
+
"type": "vcs",
|
|
75
|
+
"comment": "as detected from PackageJson property \"repository.url\""
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"url": "https://github.com/CycloneDX/cyclonedx-node-npm#readme",
|
|
79
|
+
"type": "website",
|
|
80
|
+
"comment": "as detected from PackageJson property \"homepage\""
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
"properties": [
|
|
84
|
+
{
|
|
85
|
+
"name": "cdx:npm:package:constraint:engine:node",
|
|
86
|
+
"value": ">=20.18.0"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "cdx:npm:package:constraint:engine:npm",
|
|
90
|
+
"value": ">=9"
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
"component": {
|
|
97
|
+
"type": "application",
|
|
98
|
+
"name": "react-native",
|
|
99
|
+
"group": "@sekyro",
|
|
100
|
+
"version": "0.1.0-rc.1",
|
|
101
|
+
"bom-ref": "@sekyro/react-native@0.1.0-rc.1",
|
|
102
|
+
"description": "Sekyro AI eKYC for React Native — glue over the binary Android and iOS SDKs (New Architecture)",
|
|
103
|
+
"licenses": [
|
|
104
|
+
{
|
|
105
|
+
"license": {
|
|
106
|
+
"name": "SEE LICENSE IN LICENSE",
|
|
107
|
+
"acknowledgement": "declared"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
"purl": "pkg:npm/%40sekyro/react-native@0.1.0-rc.1?vcs_url=git%2Bhttps%3A%2F%2Fgithub.com%2FIBTIKAR-Technologies%2Fibtikar-identity.git%23sekyro-mobile%2Freact-native",
|
|
112
|
+
"externalReferences": [
|
|
113
|
+
{
|
|
114
|
+
"url": "https://github.com/IBTIKAR-Technologies/ibtikar-identity/issues",
|
|
115
|
+
"type": "issue-tracker",
|
|
116
|
+
"comment": "as detected from PackageJson property \"bugs.url\""
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"url": "git+https://github.com/IBTIKAR-Technologies/ibtikar-identity.git#sekyro-mobile/react-native",
|
|
120
|
+
"type": "vcs",
|
|
121
|
+
"comment": "as detected from PackageJson property \"repository.url\" and \"repository.directory\""
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"url": "https://github.com/IBTIKAR-Technologies/ibtikar-identity#readme",
|
|
125
|
+
"type": "website",
|
|
126
|
+
"comment": "as detected from PackageJson property \"homepage\""
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
"properties": [
|
|
130
|
+
{
|
|
131
|
+
"name": "cdx:npm:package:constraint:engine:node",
|
|
132
|
+
"value": ">=20"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"name": "cdx:npm:package:path",
|
|
136
|
+
"value": ""
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
"properties": [
|
|
141
|
+
{
|
|
142
|
+
"name": "cdx:reproducible",
|
|
143
|
+
"value": "true"
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
},
|
|
147
|
+
"components": [],
|
|
148
|
+
"dependencies": [
|
|
149
|
+
{
|
|
150
|
+
"ref": "@sekyro/react-native@0.1.0-rc.1"
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @sekyro/react-native — declares where the binary SekyroKit pod comes from. In the app's Podfile:
|
|
2
|
+
#
|
|
3
|
+
# require Pod::Executable.execute_command('node', ['-p',
|
|
4
|
+
# 'require.resolve("@sekyro/react-native/package.json", {paths: [process.argv[1]]})', __dir__]).strip.sub(/package\.json$/, 'scripts/sekyro_pods.rb')
|
|
5
|
+
#
|
|
6
|
+
# target 'YourApp' do
|
|
7
|
+
# config = use_native_modules!
|
|
8
|
+
# sekyro_kit_pod!
|
|
9
|
+
# ...
|
|
10
|
+
#
|
|
11
|
+
# Default: the podspec of the public sekyro-ios-dist repo at the pinned tag (it downloads the signed
|
|
12
|
+
# XCFramework zip from assets.sekyro.ai and checks its sha256). Development: SEKYRO_KIT_PATH=<dir> uses a
|
|
13
|
+
# `make-dist.sh <pin> --local` tree instead.
|
|
14
|
+
require 'json'
|
|
15
|
+
|
|
16
|
+
def sekyro_kit_pod!
|
|
17
|
+
pkg = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
|
18
|
+
version = pkg.fetch('sekyro').fetch('ios')
|
|
19
|
+
local = ENV['SEKYRO_KIT_PATH']
|
|
20
|
+
if local && !local.strip.empty?
|
|
21
|
+
Pod::UI.puts "[Sekyro] SekyroKit #{version} from the local dist #{local} (development only)".yellow
|
|
22
|
+
pod 'SekyroKit', :path => local
|
|
23
|
+
else
|
|
24
|
+
# :podspec, not :git — a :git pod is built from the repo checkout, and the binary is not in the repo.
|
|
25
|
+
pod 'SekyroKit', :podspec => "https://raw.githubusercontent.com/IBTIKAR-Technologies/sekyro-ios-dist/#{version}/SekyroKit.podspec"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Shipped as source on purpose: RN codegen reads it when the app builds. Types only, no logic.
|
|
2
|
+
import type { TurboModule } from 'react-native';
|
|
3
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
4
|
+
// Deep type import: the codegen of RN 0.76–0.84 resolves these names (CodegenTypes is re-exported
|
|
5
|
+
// from 'react-native' only from 0.80). For this package's own tsc, types/codegen-types.d.ts
|
|
6
|
+
// supplies the TypeScript declarations that newer RN releases no longer ship at this path.
|
|
7
|
+
import type { Double, EventEmitter, Int32, UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes';
|
|
8
|
+
|
|
9
|
+
export type NativeColors = {
|
|
10
|
+
brand?: string;
|
|
11
|
+
brandFg?: string;
|
|
12
|
+
bg?: string;
|
|
13
|
+
surface?: string;
|
|
14
|
+
fg?: string;
|
|
15
|
+
fgMuted?: string;
|
|
16
|
+
border?: string;
|
|
17
|
+
danger?: string;
|
|
18
|
+
success?: string;
|
|
19
|
+
warning?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type NativeConfig = {
|
|
23
|
+
clientToken: string;
|
|
24
|
+
apiOrigin?: string;
|
|
25
|
+
locale?: string;
|
|
26
|
+
colors?: NativeColors;
|
|
27
|
+
radius?: Double;
|
|
28
|
+
fontFamily?: string;
|
|
29
|
+
darkMode?: string;
|
|
30
|
+
/** Flat catalog-key → text map for the active locale. */
|
|
31
|
+
strings?: UnsafeObject;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type NativeResult = { status: string; sessionId?: string; displayStatus?: string; code?: string };
|
|
35
|
+
|
|
36
|
+
export type NativeEvent = { type: string; step?: string; retryReason?: string; seq: Int32 };
|
|
37
|
+
|
|
38
|
+
export interface Spec extends TurboModule {
|
|
39
|
+
start(config: NativeConfig): Promise<NativeResult>;
|
|
40
|
+
nativeVersion(): string;
|
|
41
|
+
readonly onEvent: EventEmitter<NativeEvent>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// `get`, not `getEnforcing`: importing the package must not throw in an app without the native
|
|
45
|
+
// module (Expo Go, web, a missing pod install); `startSekyro` then rejects `unsupported_platform`.
|
|
46
|
+
export default TurboModuleRegistry.get<Spec>('SekyroReactNative');
|