From 2c93393ec1d3e95454e6548d70f6b6b842026308 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Wed, 3 Jul 2024 12:01:04 +0200 Subject: [PATCH] feat: sdk working on iOS Signed-off-by: Timo Glastra --- README.md | 36 ++++++++++++- ausweis-example/app/index.tsx | 28 ++++++++++ ausweis-example/metro.config.js | 20 ++++++++ ausweis-example/package.json | 8 +-- ausweis-example/pnpm-lock.yaml | 91 --------------------------------- ausweis-example/tsconfig.json | 3 +- ios/AusweisSdkModule.swift | 81 +++++++++++++++++++++++------ src/AusweisSdk.types.ts | 11 +++- src/AusweisSdkModule.ts | 9 +++- src/index.ts | 20 +++----- 10 files changed, 181 insertions(+), 126 deletions(-) create mode 100644 ausweis-example/metro.config.js diff --git a/README.md b/README.md index 1e43142..e679c63 100644 --- a/README.md +++ b/README.md @@ -84,10 +84,42 @@ That's it, you now have Ausweis App SDK configured for your iOS and Android proj ## Usage -You can now import `@animo-id/expo-ausweis-sdk` in your application and use the methods from the SDK. +You can now import `@animo-id/expo-ausweis-sdk` in your application and use the methods from the SDK. ```javascript -import AusweisSdk from '@animo-id/expo-ausweis-sdk' +import { useEffect, useState } from 'react' +import { initializeSdk, sendCommand, addMessageListener } from '@animo-id/expo-ausweis-sdk' + + +export function App() { + const [isSdkInitialized, setIsSdkInitialized] = useState(false) + + // Setup listener + useEffect( + addMessageListener((message) => { + console.log('received message', JSON.stringify(message, null, 2)) + }).remove, + [] + ) + + // Initialize SDK + useEffect(() => { + initializeSdk() + .then(() => setIsSdkInitialized(true)) + .catch((e) => { + console.log('error setting up', e) + }) + }, []) + + // Send command once SDK is initialized + useEffect(() => { + if (!isSdkInitialized) return + + sendCommand({ cmd: 'GET_INFO' }) + }, [isSdkInitialized]) + + return null +} ``` ## Contributing diff --git a/ausweis-example/app/index.tsx b/ausweis-example/app/index.tsx index d3733ae..39e936d 100644 --- a/ausweis-example/app/index.tsx +++ b/ausweis-example/app/index.tsx @@ -1,6 +1,34 @@ import { StyleSheet, Text, View } from 'react-native' +import { initializeSdk, sendCommand, addMessageListener } from '@animo-id/expo-ausweis-sdk' +import { useEffect, useState } from 'react' export default function App() { + const [isSdkInitialized, setIsSdkInitialized] = useState(false) + + // Setup listener + useEffect( + addMessageListener((message) => { + console.log('received message', JSON.stringify(message, null, 2)) + }).remove, + [] + ) + + // Initialize SDK + useEffect(() => { + initializeSdk() + .then(() => setIsSdkInitialized(true)) + .catch((e) => { + console.log('error setting up', e) + }) + }, []) + + // Send command once SDK is initialized + useEffect(() => { + if (!isSdkInitialized) return + + sendCommand({ cmd: 'GET_INFO' }) + }, [isSdkInitialized]) + return ( Hello diff --git a/ausweis-example/metro.config.js b/ausweis-example/metro.config.js new file mode 100644 index 0000000..ce1ed18 --- /dev/null +++ b/ausweis-example/metro.config.js @@ -0,0 +1,20 @@ +const { getDefaultConfig } = require('expo/metro-config'); +const path = require('node:path'); + +const defaultConfig = getDefaultConfig(__dirname); + +defaultConfig.watchFolders = [ + path.resolve(__dirname, '..', 'src') +]; + +defaultConfig.resolver.extraNodeModules = { + ...defaultConfig.resolver, + '@animo-id/expo-ausweis-sdk': path.resolve(__dirname, '..', 'src') +} + +defaultConfig.resolver.nodeModulesPaths = [ + path.resolve(__dirname, 'node_modules'), + path.resolve(__dirname, '..', 'node_modules') +] + +module.exports = defaultConfig; \ No newline at end of file diff --git a/ausweis-example/package.json b/ausweis-example/package.json index 12b1a19..fb5da34 100644 --- a/ausweis-example/package.json +++ b/ausweis-example/package.json @@ -7,9 +7,6 @@ "android": "expo run:android", "ios": "expo run:ios" }, - "jest": { - "preset": "jest-expo" - }, "dependencies": { "@animo-id/expo-ausweis-sdk": "../", "@react-navigation/native": "^6.0.2", @@ -26,5 +23,10 @@ "@types/react": "~18.2.45", "typescript": "~5.3.3" }, + "expo": { + "autoLinking": { + "nativeModulesDir": ".." + } + }, "private": true } diff --git a/ausweis-example/pnpm-lock.yaml b/ausweis-example/pnpm-lock.yaml index 2395a9b..f84fd10 100644 --- a/ausweis-example/pnpm-lock.yaml +++ b/ausweis-example/pnpm-lock.yaml @@ -11,9 +11,6 @@ importers: '@animo-id/expo-ausweis-sdk': specifier: ../ version: link:.. - '@animo-id/indy-sdk-expo-plugin': - specifier: ^0.4.0 - version: 0.4.0(expo@51.0.17(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7)))(indy-sdk-react-native@0.3.1(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0)) '@react-navigation/native': specifier: ^6.0.2 version: 6.1.17(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) @@ -55,13 +52,6 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@animo-id/indy-sdk-expo-plugin@0.4.0': - resolution: {integrity: sha512-eeSiJSKcavDE9aKsWbiN4ZjXlQm7Ed1nDn+J27Wy4I8fG7z0mTolRm0eDGryt+1Sml3Y+YMwrBiOZKHvXPYvPA==} - peerDependencies: - expo: ~49 - indy-sdk-react-native: ^0.3.0 - react-native: '>= 0.70' - '@babel/code-frame@7.10.4': resolution: {integrity: sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==} @@ -835,15 +825,9 @@ packages: '@expo/code-signing-certificates@0.0.5': resolution: {integrity: sha512-BNhXkY1bblxKZpltzAx98G2Egj9g1Q+JRcvR7E99DOj862FTCX+ZPsAUtPTr7aHxwtrL7+fL3r0JSmM9kBm+Bw==} - '@expo/config-plugins@7.2.5': - resolution: {integrity: sha512-w+5ccu1IxBHgyQk9CPFKLZOk8yZQEyTjbJwOzESK1eR7QwosbcsLkN1c1WWUZYiCXwORu3UTwJYll4+X2xxJhQ==} - '@expo/config-plugins@8.0.6': resolution: {integrity: sha512-Vmn/BSg/hBmliU/Bl+G4sExDoWd4iHXQG7ITUNR5Uar7uLko1A5vdVV+EOEUFA0f8jEZMHG3uZJUoXmr4LPaxA==} - '@expo/config-types@49.0.0': - resolution: {integrity: sha512-8eyREVi+K2acnMBe/rTIu1dOfyR2+AMnTLHlut+YpMV9OZPdeKV0Bs9BxAewGqBA2slslbQ9N39IS2CuTKpXkA==} - '@expo/config-types@51.0.2': resolution: {integrity: sha512-IglkIoiDwJMY01lYkF/ZSBoe/5cR+O3+Gx6fpLFjLfgZGBTdyPkKa1g8NWoWQCk+D3cKL2MDbszT2DyRRB0YqQ==} @@ -862,9 +846,6 @@ packages: '@expo/image-utils@0.5.1': resolution: {integrity: sha512-U/GsFfFox88lXULmFJ9Shfl2aQGcwoKPF7fawSCLixIKtMCpsI+1r0h+5i0nQnmt9tHuzXZDL8+Dg1z6OhkI9A==} - '@expo/json-file@8.2.37': - resolution: {integrity: sha512-YaH6rVg11JoTS2P6LsW7ybS2CULjf40AbnAHw2F1eDPuheprNjARZMnyHFPkKv7GuxCy+B9GPcbOKgc4cgA80Q==} - '@expo/json-file@8.3.3': resolution: {integrity: sha512-eZ5dld9AD0PrVRiIWpRkm5aIoWBw3kAyd8VkuWEy92sEthBKDDDHAnK2a0dw0Eil6j7rK7lS/Qaq/Zzngv2h5A==} @@ -883,9 +864,6 @@ packages: '@expo/package-manager@1.5.2': resolution: {integrity: sha512-IuA9XtGBilce0q8cyxtWINqbzMB1Fia0Yrug/O53HNuRSwQguV/iqjV68bsa4z8mYerePhcFgtvISWLAlNEbUA==} - '@expo/plist@0.0.20': - resolution: {integrity: sha512-UXQ4LXCfTZ580LDHGJ5q62jSTwJFFJ1GqBu8duQMThiHKWbMJ+gajJh6rsB6EJ3aLUr9wcauxneL5LVRFxwBEA==} - '@expo/plist@0.1.3': resolution: {integrity: sha512-GW/7hVlAylYg1tUrEASclw1MMk9FP4ZwyFAY/SUTJIhPDQHtfOlXREyWV3hhrHdX/K+pS73GNgdfT6E/e+kBbg==} @@ -1097,9 +1075,6 @@ packages: peerDependencies: '@babel/core': '*' - '@react-native/normalize-color@2.1.0': - resolution: {integrity: sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA==} - '@react-native/normalize-colors@0.74.84': resolution: {integrity: sha512-Y5W6x8cC5RuakUcTVUFNAIhUZ/tYpuqHZlRBoAuakrTwVuoNHXfQki8lj1KsYU7rW6e3VWgdEx33AfOQpdNp6A==} @@ -1522,9 +1497,6 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - builtins@1.0.3: resolution: {integrity: sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==} @@ -2334,12 +2306,6 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} - indy-sdk-react-native@0.3.1: - resolution: {integrity: sha512-BSG/9QHKL0hjAsARxB+PEtlZomntBkG2DsUvCTItETwTOT2ns49BeUjZUHdm2GSyHT5NcOmjyXivNEWWjDaRLw==} - peerDependencies: - react: '>= 16' - react-native: '>=0.59.0-rc.0 <1.0.x' - inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -4187,16 +4153,6 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - '@animo-id/indy-sdk-expo-plugin@0.4.0(expo@51.0.17(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7)))(indy-sdk-react-native@0.3.1(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))': - dependencies: - '@expo/config-plugins': 7.2.5 - expo: 51.0.17(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7)) - indy-sdk-react-native: 0.3.1(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) - react-native: 0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0) - resolve-from: 5.0.0 - transitivePeerDependencies: - - supports-color - '@babel/code-frame@7.10.4': dependencies: '@babel/highlight': 7.24.7 @@ -5285,26 +5241,6 @@ snapshots: node-forge: 1.3.1 nullthrows: 1.1.1 - '@expo/config-plugins@7.2.5': - dependencies: - '@expo/config-types': 49.0.0 - '@expo/json-file': 8.2.37 - '@expo/plist': 0.0.20 - '@expo/sdk-runtime-versions': 1.0.0 - '@react-native/normalize-color': 2.1.0 - chalk: 4.1.2 - debug: 4.3.5 - find-up: 5.0.0 - getenv: 1.0.0 - glob: 7.1.6 - resolve-from: 5.0.0 - semver: 7.6.2 - slash: 3.0.0 - xcode: 3.0.1 - xml2js: 0.6.0 - transitivePeerDependencies: - - supports-color - '@expo/config-plugins@8.0.6': dependencies: '@expo/config-types': 51.0.2 @@ -5325,8 +5261,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/config-types@49.0.0': {} - '@expo/config-types@51.0.2': {} '@expo/config@9.0.1': @@ -5404,12 +5338,6 @@ snapshots: transitivePeerDependencies: - encoding - '@expo/json-file@8.2.37': - dependencies: - '@babel/code-frame': 7.10.4 - json5: 2.2.3 - write-file-atomic: 2.4.3 - '@expo/json-file@8.3.3': dependencies: '@babel/code-frame': 7.10.4 @@ -5463,12 +5391,6 @@ snapshots: split: 1.0.1 sudo-prompt: 9.1.1 - '@expo/plist@0.0.20': - dependencies: - '@xmldom/xmldom': 0.7.13 - base64-js: 1.5.1 - xmlbuilder: 14.0.0 - '@expo/plist@0.1.3': dependencies: '@xmldom/xmldom': 0.7.13 @@ -5929,8 +5851,6 @@ snapshots: - '@babel/preset-env' - supports-color - '@react-native/normalize-color@2.1.0': {} - '@react-native/normalize-colors@0.74.84': {} '@react-native/virtualized-lists@0.74.84(@types/react@18.2.79)(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)': @@ -6416,11 +6336,6 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - builtins@1.0.3: {} bytes@3.0.0: {} @@ -7333,12 +7248,6 @@ snapshots: indent-string@4.0.0: {} - indy-sdk-react-native@0.3.1(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0): - dependencies: - buffer: 6.0.3 - react: 18.2.0 - react-native: 0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0) - inflight@1.0.6: dependencies: once: 1.4.0 diff --git a/ausweis-example/tsconfig.json b/ausweis-example/tsconfig.json index ce27fee..0c89aee 100644 --- a/ausweis-example/tsconfig.json +++ b/ausweis-example/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "strict": true, "paths": { - "@/*": ["./*"] + "@/*": ["./*"], + "@animo-id/expo-ausweis-sdk": ["../src"] } }, "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"] diff --git a/ios/AusweisSdkModule.swift b/ios/AusweisSdkModule.swift index cea11ea..021a138 100644 --- a/ios/AusweisSdkModule.swift +++ b/ios/AusweisSdkModule.swift @@ -1,6 +1,55 @@ import ExpoModulesCore +import AusweisApp2 + +var initializationSemaphore = DispatchSemaphore(value: 0) + +// Define the callback function +func ausweisSdkCallback(_ pMsg: UnsafePointer?) { + if let msg = pMsg { + let message = String(cString: msg) + if (AusweisSdkModule.moduleInstance != nil) { + AusweisSdkModule.moduleInstance?.sendEvent("onMessage", ["value": message]) + } else { + print("No module instance registered") + return + + } + + } else { + // SDK is ready to be used + initializationSemaphore.signal() + } +} + public class AusweisSdkModule: Module { + public required init(appContext: ExpoModulesCore.AppContext) { + super.init(appContext: appContext) + + // Set the module instance + AusweisSdkModule.moduleInstance = self + } + + var isSdkInitialized = false + static var moduleInstance: AusweisSdkModule? + + // Async function to initialize the SDK and wait for it to be ready + func initializeSdk() async -> Bool { + return await withCheckedContinuation { continuation in + let initialized = ausweisapp2_init(ausweisSdkCallback, nil) + + if initialized { + DispatchQueue.global().async { + _ = initializationSemaphore.wait(timeout: .now() + 10) // Wait for up to 10 seconds + continuation.resume(returning: true) + self.isSdkInitialized = true + } + } else { + continuation.resume(returning: false) + } + } + } + // Each module class must implement the definition function. The definition consists of components // that describes the module's functionality and behavior. // See https://docs.expo.dev/modules/module-api for more details about available components. @@ -10,26 +59,28 @@ public class AusweisSdkModule: Module { // The module will be accessible from `requireNativeModule('AusweisSdk')` in JavaScript. Name("AusweisSdk") - // Sets constant properties on the module. Can take a dictionary or a closure that returns a dictionary. - Constants([ - "PI": Double.pi - ]) - // Defines event names that the module can send to JavaScript. - Events("onChange") + Events("onMessage") // Defines a JavaScript synchronous function that runs the native code on the JavaScript thread. - Function("hello") { - return "Hello world! 👋" + Function("sendCommand") { (command: String) in + if (!isSdkInitialized) { + throw NSError(domain: "AusweisSdkError", code: 1, userInfo: [NSLocalizedDescriptionKey: "SDK Is not initalized. Make sure to call initialize first."]) + } + + ausweisapp2_send(command) } - // Defines a JavaScript function that always returns a Promise and whose native code - // is by default dispatched on the different thread than the JavaScript runtime runs on. - AsyncFunction("setValueAsync") { (value: String) in - // Send an event to JavaScript. - self.sendEvent("onChange", [ - "value": value - ]) + AsyncFunction("initialize") { + if isSdkInitialized { + return true + } + + let isInitialized = await initializeSdk() + if !isInitialized { + throw Exception(name: "SdkInitializationError", description: "Unable to initialize SDK") + } + return true } } } diff --git a/src/AusweisSdk.types.ts b/src/AusweisSdk.types.ts index 2df1b92..a16d087 100644 --- a/src/AusweisSdk.types.ts +++ b/src/AusweisSdk.types.ts @@ -1,3 +1,12 @@ -export type ChangeEventPayload = { +export type NativeMessageEventPayload = { value: string } + +export type MessageEventPayload = { + msg: string +} + +export type CommandPayload = { + cmd: string + [key: string]: unknown +} diff --git a/src/AusweisSdkModule.ts b/src/AusweisSdkModule.ts index 492f93a..8a6303f 100644 --- a/src/AusweisSdkModule.ts +++ b/src/AusweisSdkModule.ts @@ -1,5 +1,12 @@ import { requireNativeModule } from 'expo-modules-core' +import type { NativeModule } from 'react-native' + +interface AusweisSdk extends NativeModule { + sendCommand: (command: string) => void + initialize(): Promise + asyncFunction: () => Promise +} // It loads the native module object from the JSI or falls back to // the bridge module (from NativeModulesProxy) if the remote debugger is on. -export default requireNativeModule('AusweisSdk') +export default requireNativeModule('AusweisSdk') diff --git a/src/index.ts b/src/index.ts index 2311d61..67d9647 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,23 +1,19 @@ import { EventEmitter, NativeModulesProxy, type Subscription } from 'expo-modules-core' -import { ChangeEventPayload } from './AusweisSdk.types' +import type { CommandPayload, MessageEventPayload, NativeMessageEventPayload } from './AusweisSdk.types' import AusweisSdkModule from './AusweisSdkModule' -// Get the native constant value. -export const PI = AusweisSdkModule.PI - -export function hello(): string { - return AusweisSdkModule.hello() +export function sendCommand(command: CommandPayload): void { + AusweisSdkModule.sendCommand(JSON.stringify(command)) } -export async function setValueAsync(value: string) { - return await AusweisSdkModule.setValueAsync(value) +export async function initializeSdk() { + return await AusweisSdkModule.initialize() } const emitter = new EventEmitter(AusweisSdkModule ?? NativeModulesProxy.AusweisSdk) - -export function addChangeListener(listener: (event: ChangeEventPayload) => void): Subscription { - return emitter.addListener('onChange', listener) +export function addMessageListener(listener: (message: MessageEventPayload) => void): Subscription { + return emitter.addListener('onMessage', (event) => listener(JSON.parse(event.value))) } -export { ChangeEventPayload } +export type { MessageEventPayload, CommandPayload }