diff --git a/README.md b/README.md index c938a73..67b83ca 100644 --- a/README.md +++ b/README.md @@ -18,19 +18,20 @@ npm install @synonymdev/react-native-pubky - [x] [resolveHttps](#resolveHttps): Resolve HTTPS records. - [x] [signUp](#signUp): Sign-up to a homeserver and update Pkarr accordingly. - [x] [signIn](#signIn): Sign-in to a homeserver. +- [x] [session](#session): Check the current session for a given Pubky in its homeserver. - [x] [signOut](#signOut): Sign-out from a homeserver. - [x] [put](#put): Upload a small payload to a given path. - [x] [get](#get): Download a small payload from a given path relative to a pubky author. - [x] [list](#list): Returns a list of Pubky URLs of the files in the path of the `url` provided. +- [x] [delete](#delete): Delete a file at a path relative to a pubky author. - [x] [generateSecretKey](#generateSecretKey): Generate a secret key. - [x] [getPublicKeyFromSecretKey](#getPublicKeyFromSecretKey): Get the public key string and uri from a secret key. - [x] [create_recovery_file](#createRecoveryFile): Create a recovery file. - [x] [decrypt_recovery_file](#decryptRecoveryFile): Decrypt a recovery file. ### Methods to be Implemented -- [ ] session: Check the current session for a given Pubky in its homeserver. -- [ ] delete: Delete a file at a path relative to a pubky author. - +- [ ] setProfile: Set profile information for a pubky. +- [ ] getProfile: Get profile information for a pubky. ## Usage ### Auth @@ -161,6 +162,20 @@ if (listRes.isErr()) { console.log(listRes.value); ``` +### deleteFile +```js +import { deleteFile } from '@synonymdev/react-native-pubky'; + +const deleteFileRes = await deleteFile( + 'pubky://z4e8s17cou9qmuwen8p1556jzhf1wktmzo6ijsfnri9c4hnrdfty/pub/' // URL +); +if (deleteFileRes.isErr()) { + console.log(deleteFileRes.error.message); + return; +} +console.log(deleteFileRes.value); +``` + ### generateSecretKey ```js import { generateSecretKey } from '@synonymdev/react-native-pubky'; @@ -214,6 +229,20 @@ if (signInRes.isErr()) { console.log(signInRes.value); ``` +### sessionRes +```js +import { session } from '@synonymdev/react-native-pubky'; + +const sessionRes = await session( + 'z4e8s17cou9qmuwen8p1556jzhf1wktmzo6ijsfnri9c4hnrdfty' // Public Key +); +if (sessionRes.isErr()) { + console.log(sessionRes.error.message); + return; +} +console.log(sessionRes.value); +``` + ### signIn ```js import { signOut } from '@synonymdev/react-native-pubky'; diff --git a/android/src/main/java/com/pubky/PubkyModule.kt b/android/src/main/java/com/pubky/PubkyModule.kt index d5ac9d8..6ac952b 100644 --- a/android/src/main/java/com/pubky/PubkyModule.kt +++ b/android/src/main/java/com/pubky/PubkyModule.kt @@ -5,6 +5,7 @@ import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod import com.facebook.react.bridge.Promise +import com.facebook.react.modules.core.DeviceEventManagerModule import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -12,299 +13,389 @@ import kotlinx.coroutines.withContext import uniffi.pubkymobile.* class PubkyModule(reactContext: ReactApplicationContext) : - ReactContextBaseJavaModule(reactContext) { + ReactContextBaseJavaModule(reactContext) { - override fun getName(): String { - return NAME - } - - @ReactMethod - fun auth(url: String, secretKey: String, promise: Promise) { - CoroutineScope(Dispatchers.IO).launch { - try { - val result = auth(url, secretKey) - val array = Arguments.createArray().apply { - result.forEach { pushString(it) } - } - withContext(Dispatchers.Main) { - promise.resolve(array) - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - promise.reject("Error", e.message) - } - } - } - } - - @ReactMethod - fun parseAuthUrl(url: String, promise: Promise) { - try { - val result = parseAuthUrl(url) - val array = Arguments.createArray().apply { - result.forEach { pushString(it) } - } - promise.resolve(array) - } catch (e: Exception) { - promise.reject("Error", e.message) + override fun getName(): String { + return NAME } - } - @ReactMethod - fun publish(recordName: String, recordContent: String, secretKey: String, promise: Promise) { - CoroutineScope(Dispatchers.IO).launch { - try { - val result = publish(recordName, recordContent, secretKey) - val array = Arguments.createArray().apply { - result.forEach { pushString(it) } - } - withContext(Dispatchers.Main) { - promise.resolve(array) - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - promise.reject("Error", e.message) - } - } - } - } - - @ReactMethod - fun resolve(publicKey: String, promise: Promise) { - CoroutineScope(Dispatchers.IO).launch { - try { - val result = resolve(publicKey) - val array = Arguments.createArray().apply { - result.forEach { pushString(it) } - } - withContext(Dispatchers.Main) { - promise.resolve(array) - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - promise.reject("Error", e.message) - } - } - } - } - - @ReactMethod - fun signUp(secretKey: String, homeserver: String, promise: Promise) { - CoroutineScope(Dispatchers.IO).launch { - try { - val result = signUp(secretKey, homeserver) - val array = Arguments.createArray().apply { - result.forEach { pushString(it) } - } - withContext(Dispatchers.Main) { - promise.resolve(array) - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - promise.reject("Error", e.message) - } - } - } - } - - @ReactMethod - fun signIn(secretKey: String, promise: Promise) { - CoroutineScope(Dispatchers.IO).launch { - try { - val result = signIn(secretKey) - val array = Arguments.createArray().apply { - result.forEach { pushString(it) } - } - withContext(Dispatchers.Main) { - promise.resolve(array) - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - promise.reject("Error", e.message) - } - } - } - } - - @ReactMethod - fun signOut(secretKey: String, promise: Promise) { - CoroutineScope(Dispatchers.IO).launch { - try { - val result = signOut(secretKey) - val array = Arguments.createArray().apply { - result.forEach { pushString(it) } - } - withContext(Dispatchers.Main) { - promise.resolve(array) - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - promise.reject("Error", e.message) - } - } - } - } - - @ReactMethod - fun put(url: String, content: String, promise: Promise) { - CoroutineScope(Dispatchers.IO).launch { - try { - val result = put(url, content) - val array = Arguments.createArray().apply { - result.forEach { pushString(it) } - } - withContext(Dispatchers.Main) { - promise.resolve(array) - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - promise.reject("Error", e.message) - } - } - } - } - - @ReactMethod - fun get(url: String, promise: Promise) { - CoroutineScope(Dispatchers.IO).launch { - try { - val result = get(url) - val array = Arguments.createArray().apply { - result.forEach { pushString(it) } - } - withContext(Dispatchers.Main) { - promise.resolve(array) - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - promise.reject("Error", e.message) - } - } - } - } - - @ReactMethod - fun publishHttps(recordName: String, target: String, secretKey: String, promise: Promise) { - CoroutineScope(Dispatchers.IO).launch { - try { - val result = publishHttps(recordName, target, secretKey) - val array = Arguments.createArray().apply { - result.forEach { pushString(it) } - } - withContext(Dispatchers.Main) { - promise.resolve(array) - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - promise.reject("Error", e.message) - } - } - } - } - - @ReactMethod - fun resolveHttps(publicKey: String, promise: Promise) { - CoroutineScope(Dispatchers.IO).launch { - try { - val result = resolveHttps(publicKey) - val array = Arguments.createArray().apply { - result.forEach { pushString(it) } - } - withContext(Dispatchers.Main) { - promise.resolve(array) - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - promise.reject("Error", e.message) - } - } - } - } - - @ReactMethod - fun list(url: String, promise: Promise) { - CoroutineScope(Dispatchers.IO).launch { - try { - val result = list(url) - val array = Arguments.createArray().apply { - result.forEach { pushString(it) } - } - withContext(Dispatchers.Main) { - promise.resolve(array) - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - promise.reject("Error", e.message) - } - } - } - } - - @ReactMethod - fun generateSecretKey(promise: Promise) { - CoroutineScope(Dispatchers.IO).launch { - try { - val result = generate_secret_key() - val array = Arguments.createArray().apply { - result.forEach { pushString(it) } - } - withContext(Dispatchers.Main) { - promise.resolve(array) - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - promise.reject("Error", e.message) - } - } - } - } - - @ReactMethod - fun getPublicKeyFromSecretKey(secretKey: String, promise: Promise) { - CoroutineScope(Dispatchers.IO).launch { - try { - val result = getPublicKeyFromSecretKey(secretKey) - val array = Arguments.createArray().apply { - result.forEach { pushString(it) } - } - withContext(Dispatchers.Main) { - promise.resolve(array) - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - promise.reject("Error", e.message) - } - } - } - } - -@ReactMethod -fun createRecoveryFile(secretKey: String, passphrase: String, promise: Promise) { - try { - val result = createRecoveryFile(secretKey, passphrase) - val array = Arguments.createArray().apply { - result.forEach { pushString(it) } + private val eventListener = object : EventListener { + override fun onEventOccurred(eventData: String) { + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + .emit("PubkyEvent", eventData) } - promise.resolve(array) - } catch (e: Exception) { - promise.reject("Error", e.message) } -} -@ReactMethod -fun decryptRecoveryFile(recoveryFile: String, passphrase: String, promise: Promise) { - try { - val result = decryptRecoveryFile(recoveryFile, passphrase) - val array = Arguments.createArray().apply { - result.forEach { pushString(it) } + @ReactMethod + fun setEventListener(promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + setEventListener(eventListener) + withContext(Dispatchers.Main) { + promise.resolve(null) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } } - promise.resolve(array) - } catch (e: Exception) { - promise.reject("Error", e.message) + } + + @ReactMethod + fun removeEventListener(promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + removeEventListener() + withContext(Dispatchers.Main) { + promise.resolve(null) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + @ReactMethod + fun deleteFile(url: String, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = deleteFile(url) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + @ReactMethod + fun session(pubky: String, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = session(pubky) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + @ReactMethod + fun auth(url: String, secretKey: String, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = auth(url, secretKey) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + @ReactMethod + fun parseAuthUrl(url: String, promise: Promise) { + try { + val result = parseAuthUrl(url) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + promise.resolve(array) + } catch (e: Exception) { + promise.reject("Error", e.message) + } + } + + @ReactMethod + fun publish(recordName: String, recordContent: String, secretKey: String, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = publish(recordName, recordContent, secretKey) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + @ReactMethod + fun resolve(publicKey: String, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = resolve(publicKey) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + @ReactMethod + fun signUp(secretKey: String, homeserver: String, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = signUp(secretKey, homeserver) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + @ReactMethod + fun signIn(secretKey: String, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = signIn(secretKey) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + @ReactMethod + fun signOut(secretKey: String, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = signOut(secretKey) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + @ReactMethod + fun put(url: String, content: String, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = put(url, content) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + @ReactMethod + fun get(url: String, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = get(url) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + @ReactMethod + fun publishHttps(recordName: String, target: String, secretKey: String, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = publishHttps(recordName, target, secretKey) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + @ReactMethod + fun resolveHttps(publicKey: String, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = resolveHttps(publicKey) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + @ReactMethod + fun list(url: String, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = list(url) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + @ReactMethod + fun generateSecretKey(promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = generateSecretKey() + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + @ReactMethod + fun getPublicKeyFromSecretKey(secretKey: String, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = getPublicKeyFromSecretKey(secretKey) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + @ReactMethod + fun createRecoveryFile(secretKey: String, passphrase: String, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = createRecoveryFile(secretKey, passphrase) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + @ReactMethod + fun decryptRecoveryFile(recoveryFile: String, passphrase: String, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = decryptRecoveryFile(recoveryFile, passphrase) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } + } + + companion object { + const val NAME = "Pubky" } } - - companion object { - const val NAME = "Pubky" - } -} diff --git a/android/src/main/java/uniffi/pubkymobile/pubkymobile.kt b/android/src/main/java/uniffi/pubkymobile/pubkymobile.kt index de21a32..832b931 100644 --- a/android/src/main/java/uniffi/pubkymobile/pubkymobile.kt +++ b/android/src/main/java/uniffi/pubkymobile/pubkymobile.kt @@ -29,9 +29,10 @@ import java.nio.ByteOrder import java.nio.CharBuffer import java.nio.charset.CodingErrorAction import java.util.concurrent.ConcurrentHashMap -import kotlin.coroutines.resume -import kotlinx.coroutines.CancellableContinuation -import kotlinx.coroutines.suspendCancellableCoroutine +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicLong +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock // This is a helper for safely working with byte buffers returned from the Rust code. // A rust-owned buffer is represented by its capacity, its current length, and a @@ -380,15 +381,29 @@ internal interface _UniFFILib : Library { .also { lib: _UniFFILib -> uniffiCheckContractApiVersion(lib) uniffiCheckApiChecksums(lib) - uniffiRustFutureContinuationCallback.register(lib) + FfiConverterTypeEventListener.register(lib) } } } + fun uniffi_pubkymobile_fn_free_eventnotifier(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + fun uniffi_pubkymobile_fn_init_callback_eventlistener(`callbackStub`: ForeignCallback,_uniffi_out_err: RustCallStatus, + ): Unit fun uniffi_pubkymobile_fn_func_auth(`url`: RustBuffer.ByValue,`secretKey`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, ): RustBuffer.ByValue - fun uniffi_pubkymobile_fn_func_get(`url`: RustBuffer.ByValue, - ): Pointer + fun uniffi_pubkymobile_fn_func_create_recovery_file(`secretKey`: RustBuffer.ByValue,`passphrase`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue + fun uniffi_pubkymobile_fn_func_decrypt_recovery_file(`recoveryFile`: RustBuffer.ByValue,`passphrase`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue + fun uniffi_pubkymobile_fn_func_delete_file(`url`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue + fun uniffi_pubkymobile_fn_func_generate_secret_key(_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue + fun uniffi_pubkymobile_fn_func_get(`url`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue + fun uniffi_pubkymobile_fn_func_get_public_key_from_secret_key(`secretKey`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue fun uniffi_pubkymobile_fn_func_list(`url`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, ): RustBuffer.ByValue fun uniffi_pubkymobile_fn_func_parse_auth_url(`url`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, @@ -397,18 +412,24 @@ internal interface _UniFFILib : Library { ): RustBuffer.ByValue fun uniffi_pubkymobile_fn_func_publish_https(`recordName`: RustBuffer.ByValue,`target`: RustBuffer.ByValue,`secretKey`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, ): RustBuffer.ByValue - fun uniffi_pubkymobile_fn_func_put(`url`: RustBuffer.ByValue,`content`: RustBuffer.ByValue, - ): Pointer + fun uniffi_pubkymobile_fn_func_put(`url`: RustBuffer.ByValue,`content`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue + fun uniffi_pubkymobile_fn_func_remove_event_listener(_uniffi_out_err: RustCallStatus, + ): Unit fun uniffi_pubkymobile_fn_func_resolve(`publicKey`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, ): RustBuffer.ByValue fun uniffi_pubkymobile_fn_func_resolve_https(`publicKey`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, ): RustBuffer.ByValue - fun uniffi_pubkymobile_fn_func_sign_in(`secretKey`: RustBuffer.ByValue, - ): Pointer - fun uniffi_pubkymobile_fn_func_sign_out(`secretKey`: RustBuffer.ByValue, - ): Pointer - fun uniffi_pubkymobile_fn_func_sign_up(`secretKey`: RustBuffer.ByValue,`homeserver`: RustBuffer.ByValue, - ): Pointer + fun uniffi_pubkymobile_fn_func_session(`pubky`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue + fun uniffi_pubkymobile_fn_func_set_event_listener(`listener`: Long,_uniffi_out_err: RustCallStatus, + ): Unit + fun uniffi_pubkymobile_fn_func_sign_in(`secretKey`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue + fun uniffi_pubkymobile_fn_func_sign_out(`secretKey`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue + fun uniffi_pubkymobile_fn_func_sign_up(`secretKey`: RustBuffer.ByValue,`homeserver`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue fun ffi_pubkymobile_rustbuffer_alloc(`size`: Int,_uniffi_out_err: RustCallStatus, ): RustBuffer.ByValue fun ffi_pubkymobile_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue,_uniffi_out_err: RustCallStatus, @@ -525,8 +546,18 @@ internal interface _UniFFILib : Library { ): Unit fun uniffi_pubkymobile_checksum_func_auth( ): Short + fun uniffi_pubkymobile_checksum_func_create_recovery_file( + ): Short + fun uniffi_pubkymobile_checksum_func_decrypt_recovery_file( + ): Short + fun uniffi_pubkymobile_checksum_func_delete_file( + ): Short + fun uniffi_pubkymobile_checksum_func_generate_secret_key( + ): Short fun uniffi_pubkymobile_checksum_func_get( ): Short + fun uniffi_pubkymobile_checksum_func_get_public_key_from_secret_key( + ): Short fun uniffi_pubkymobile_checksum_func_list( ): Short fun uniffi_pubkymobile_checksum_func_parse_auth_url( @@ -537,16 +568,24 @@ internal interface _UniFFILib : Library { ): Short fun uniffi_pubkymobile_checksum_func_put( ): Short + fun uniffi_pubkymobile_checksum_func_remove_event_listener( + ): Short fun uniffi_pubkymobile_checksum_func_resolve( ): Short fun uniffi_pubkymobile_checksum_func_resolve_https( ): Short + fun uniffi_pubkymobile_checksum_func_session( + ): Short + fun uniffi_pubkymobile_checksum_func_set_event_listener( + ): Short fun uniffi_pubkymobile_checksum_func_sign_in( ): Short fun uniffi_pubkymobile_checksum_func_sign_out( ): Short fun uniffi_pubkymobile_checksum_func_sign_up( ): Short + fun uniffi_pubkymobile_checksum_method_eventlistener_on_event_occurred( + ): Short fun ffi_pubkymobile_uniffi_contract_version( ): Int @@ -567,7 +606,22 @@ private fun uniffiCheckApiChecksums(lib: _UniFFILib) { if (lib.uniffi_pubkymobile_checksum_func_auth() != 61378.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_pubkymobile_checksum_func_get() != 5395.toShort()) { + if (lib.uniffi_pubkymobile_checksum_func_create_recovery_file() != 55903.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_pubkymobile_checksum_func_decrypt_recovery_file() != 59688.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_pubkymobile_checksum_func_delete_file() != 57905.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_pubkymobile_checksum_func_generate_secret_key() != 63116.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_pubkymobile_checksum_func_get() != 21596.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_pubkymobile_checksum_func_get_public_key_from_secret_key() != 23603.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_pubkymobile_checksum_func_list() != 8522.toShort()) { @@ -582,7 +636,10 @@ private fun uniffiCheckApiChecksums(lib: _UniFFILib) { if (lib.uniffi_pubkymobile_checksum_func_publish_https() != 14705.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_pubkymobile_checksum_func_put() != 47594.toShort()) { + if (lib.uniffi_pubkymobile_checksum_func_put() != 51107.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_pubkymobile_checksum_func_remove_event_listener() != 6794.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_pubkymobile_checksum_func_resolve() != 18303.toShort()) { @@ -591,62 +648,27 @@ private fun uniffiCheckApiChecksums(lib: _UniFFILib) { if (lib.uniffi_pubkymobile_checksum_func_resolve_https() != 34593.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_pubkymobile_checksum_func_sign_in() != 53969.toShort()) { + if (lib.uniffi_pubkymobile_checksum_func_session() != 65177.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_pubkymobile_checksum_func_sign_out() != 32961.toShort()) { + if (lib.uniffi_pubkymobile_checksum_func_set_event_listener() != 19468.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_pubkymobile_checksum_func_sign_up() != 28083.toShort()) { + if (lib.uniffi_pubkymobile_checksum_func_sign_in() != 21006.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_pubkymobile_checksum_func_sign_out() != 59116.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_pubkymobile_checksum_func_sign_up() != 58756.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_pubkymobile_checksum_method_eventlistener_on_event_occurred() != 39865.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } } // Async support -// Async return type handlers - -internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toShort() -internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toShort() - -internal val uniffiContinuationHandleMap = UniFfiHandleMap>() - -// FFI type for Rust future continuations -internal object uniffiRustFutureContinuationCallback: UniFffiRustFutureContinuationCallbackType { - override fun callback(continuationHandle: USize, pollResult: Short) { - uniffiContinuationHandleMap.remove(continuationHandle)?.resume(pollResult) - } - - internal fun register(lib: _UniFFILib) { - lib.ffi_pubkymobile_rust_future_continuation_callback_set(this) - } -} - -internal suspend fun uniffiRustCallAsync( - rustFuture: Pointer, - pollFunc: (Pointer, USize) -> Unit, - completeFunc: (Pointer, RustCallStatus) -> F, - freeFunc: (Pointer) -> Unit, - liftFunc: (F) -> T, - errorHandler: CallStatusErrorHandler -): T { - try { - do { - val pollResult = suspendCancellableCoroutine { continuation -> - pollFunc( - rustFuture, - uniffiContinuationHandleMap.insert(continuation) - ) - } - } while (pollResult != UNIFFI_RUST_FUTURE_POLL_READY); - - return liftFunc( - rustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) }) - ) - } finally { - freeFunc(rustFuture) - } -} - // Public interface members begin here. @@ -706,6 +728,384 @@ public object FfiConverterString: FfiConverter { } +// Interface implemented by anything that can contain an object reference. +// +// Such types expose a `destroy()` method that must be called to cleanly +// dispose of the contained objects. Failure to call this method may result +// in memory leaks. +// +// The easiest way to ensure this method is called is to use the `.use` +// helper method to execute a block and destroy the object at the end. +interface Disposable { + fun destroy() + companion object { + fun destroy(vararg args: Any?) { + args.filterIsInstance() + .forEach(Disposable::destroy) + } + } +} + +inline fun T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + +// The base class for all UniFFI Object types. +// +// This class provides core operations for working with the Rust `Arc` pointer to +// the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an `FFIObject` is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an `FFIObject` instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so will +// leak the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// In the future we may be able to replace some of this with automatic finalization logic, such as using +// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is +// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also +// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], +// so there would still be some complexity here). +// +// Sigh...all of this for want of a robust finalization mechanism. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// +abstract class FFIObject( + protected val pointer: Pointer +): Disposable, AutoCloseable { + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + open protected fun freeRustArcPtr() { + // To be overridden in subclasses. + } + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.pointer) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } +} + +public interface EventNotifierInterface { + + companion object +} + +class EventNotifier( + pointer: Pointer +) : FFIObject(pointer), EventNotifierInterface { + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_free_eventnotifier(this.pointer, status) + } + } + + + + + companion object + +} + +public object FfiConverterTypeEventNotifier: FfiConverter { + override fun lower(value: EventNotifier): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): EventNotifier { + return EventNotifier(value) + } + + override fun read(buf: ByteBuffer): EventNotifier { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: EventNotifier) = 8 + + override fun write(value: EventNotifier, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + + + +internal typealias Handle = Long +internal class ConcurrentHandleMap( + private val leftMap: MutableMap = mutableMapOf(), + private val rightMap: MutableMap = mutableMapOf() +) { + private val lock = java.util.concurrent.locks.ReentrantLock() + private val currentHandle = AtomicLong(0L) + private val stride = 1L + + fun insert(obj: T): Handle = + lock.withLock { + rightMap[obj] ?: + currentHandle.getAndAdd(stride) + .also { handle -> + leftMap[handle] = obj + rightMap[obj] = handle + } + } + + fun get(handle: Handle) = lock.withLock { + leftMap[handle] + } + + fun delete(handle: Handle) { + this.remove(handle) + } + + fun remove(handle: Handle): T? = + lock.withLock { + leftMap.remove(handle)?.let { obj -> + rightMap.remove(obj) + obj + } + } +} + +interface ForeignCallback : com.sun.jna.Callback { + public fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int +} + +// Magic number for the Rust proxy to call using the same mechanism as every other method, +// to free the callback once it's dropped by Rust. +internal const val IDX_CALLBACK_FREE = 0 +// Callback return codes +internal const val UNIFFI_CALLBACK_SUCCESS = 0 +internal const val UNIFFI_CALLBACK_ERROR = 1 +internal const val UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2 + +public abstract class FfiConverterCallbackInterface( + protected val foreignCallback: ForeignCallback +): FfiConverter { + private val handleMap = ConcurrentHandleMap() + + // Registers the foreign callback with the Rust side. + // This method is generated for each callback interface. + internal abstract fun register(lib: _UniFFILib) + + fun drop(handle: Handle): RustBuffer.ByValue { + return handleMap.remove(handle).let { RustBuffer.ByValue() } + } + + override fun lift(value: Handle): CallbackInterface { + return handleMap.get(value) ?: throw InternalException("No callback in handlemap; this is a Uniffi bug") + } + + override fun read(buf: ByteBuffer) = lift(buf.getLong()) + + override fun lower(value: CallbackInterface) = + handleMap.insert(value).also { + assert(handleMap.get(it) === value) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." } + } + + override fun allocationSize(value: CallbackInterface) = 8 + + override fun write(value: CallbackInterface, buf: ByteBuffer) { + buf.putLong(lower(value)) + } +} + +// Declaration and FfiConverters for EventListener Callback Interface + +public interface EventListener { + fun `onEventOccurred`(`eventData`: String) + + companion object +} + +// The ForeignCallback that is passed to Rust. +internal class ForeignCallbackTypeEventListener : ForeignCallback { + @Suppress("TooGenericExceptionCaught") + override fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int { + val cb = FfiConverterTypeEventListener.lift(handle) + return when (method) { + IDX_CALLBACK_FREE -> { + FfiConverterTypeEventListener.drop(handle) + // Successful return + // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` + UNIFFI_CALLBACK_SUCCESS + } + 1 -> { + // Call the method, write to outBuf and return a status code + // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` for info + try { + this.`invokeOnEventOccurred`(cb, argsData, argsLen, outBuf) + } catch (e: Throwable) { + // Unexpected error + try { + // Try to serialize the error into a string + outBuf.setValue(FfiConverterString.lower(e.toString())) + } catch (e: Throwable) { + // If that fails, then it's time to give up and just return + } + UNIFFI_CALLBACK_UNEXPECTED_ERROR + } + } + + else -> { + // An unexpected error happened. + // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` + try { + // Try to serialize the error into a string + outBuf.setValue(FfiConverterString.lower("Invalid Callback index")) + } catch (e: Throwable) { + // If that fails, then it's time to give up and just return + } + UNIFFI_CALLBACK_UNEXPECTED_ERROR + } + } + } + + + @Suppress("UNUSED_PARAMETER") + private fun `invokeOnEventOccurred`(kotlinCallbackInterface: EventListener, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int { + val argsBuf = argsData.getByteBuffer(0, argsLen.toLong()).also { + it.order(ByteOrder.BIG_ENDIAN) + } + fun makeCall() : Int { + kotlinCallbackInterface.`onEventOccurred`( + FfiConverterString.read(argsBuf) + ) + return UNIFFI_CALLBACK_SUCCESS + } + fun makeCallAndHandleError() : Int = makeCall() + + return makeCallAndHandleError() + } + +} + +// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. +public object FfiConverterTypeEventListener: FfiConverterCallbackInterface( + foreignCallback = ForeignCallbackTypeEventListener() +) { + override fun register(lib: _UniFFILib) { + rustCall() { status -> + lib.uniffi_pubkymobile_fn_init_callback_eventlistener(this.foreignCallback, status) + } + } +} + + public object FfiConverterSequenceString: FfiConverterRustBuffer> { @@ -730,10 +1130,6 @@ public object FfiConverterSequenceString: FfiConverterRustBuffer> { } } - - - - fun `auth`(`url`: String, `secretKey`: String): List { return FfiConverterSequenceString.lift( rustCall() { _status -> @@ -742,20 +1138,54 @@ fun `auth`(`url`: String, `secretKey`: String): List { } -@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") -suspend fun `get`(`url`: String) : List { - return uniffiRustCallAsync( - _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_get(FfiConverterString.lower(`url`),), - { future, continuation -> _UniFFILib.INSTANCE.ffi_pubkymobile_rust_future_poll_rust_buffer(future, continuation) }, - { future, continuation -> _UniFFILib.INSTANCE.ffi_pubkymobile_rust_future_complete_rust_buffer(future, continuation) }, - { future -> _UniFFILib.INSTANCE.ffi_pubkymobile_rust_future_free_rust_buffer(future) }, - // lift function - { FfiConverterSequenceString.lift(it) }, - // Error FFI converter - NullCallStatusErrorHandler, - ) +fun `createRecoveryFile`(`secretKey`: String, `passphrase`: String): List { + return FfiConverterSequenceString.lift( + rustCall() { _status -> + _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_create_recovery_file(FfiConverterString.lower(`secretKey`),FfiConverterString.lower(`passphrase`),_status) +}) } + +fun `decryptRecoveryFile`(`recoveryFile`: String, `passphrase`: String): List { + return FfiConverterSequenceString.lift( + rustCall() { _status -> + _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_decrypt_recovery_file(FfiConverterString.lower(`recoveryFile`),FfiConverterString.lower(`passphrase`),_status) +}) +} + + +fun `deleteFile`(`url`: String): List { + return FfiConverterSequenceString.lift( + rustCall() { _status -> + _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_delete_file(FfiConverterString.lower(`url`),_status) +}) +} + + +fun `generateSecretKey`(): List { + return FfiConverterSequenceString.lift( + rustCall() { _status -> + _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_generate_secret_key(_status) +}) +} + + +fun `get`(`url`: String): List { + return FfiConverterSequenceString.lift( + rustCall() { _status -> + _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_get(FfiConverterString.lower(`url`),_status) +}) +} + + +fun `getPublicKeyFromSecretKey`(`secretKey`: String): List { + return FfiConverterSequenceString.lift( + rustCall() { _status -> + _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_get_public_key_from_secret_key(FfiConverterString.lower(`secretKey`),_status) +}) +} + + fun `list`(`url`: String): List { return FfiConverterSequenceString.lift( rustCall() { _status -> @@ -788,20 +1218,22 @@ fun `publishHttps`(`recordName`: String, `target`: String, `secretKey`: String): } -@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") -suspend fun `put`(`url`: String, `content`: String) : List { - return uniffiRustCallAsync( - _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_put(FfiConverterString.lower(`url`),FfiConverterString.lower(`content`),), - { future, continuation -> _UniFFILib.INSTANCE.ffi_pubkymobile_rust_future_poll_rust_buffer(future, continuation) }, - { future, continuation -> _UniFFILib.INSTANCE.ffi_pubkymobile_rust_future_complete_rust_buffer(future, continuation) }, - { future -> _UniFFILib.INSTANCE.ffi_pubkymobile_rust_future_free_rust_buffer(future) }, - // lift function - { FfiConverterSequenceString.lift(it) }, - // Error FFI converter - NullCallStatusErrorHandler, - ) +fun `put`(`url`: String, `content`: String): List { + return FfiConverterSequenceString.lift( + rustCall() { _status -> + _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_put(FfiConverterString.lower(`url`),FfiConverterString.lower(`content`),_status) +}) } + +fun `removeEventListener`() = + + rustCall() { _status -> + _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_remove_event_listener(_status) +} + + + fun `resolve`(`publicKey`: String): List { return FfiConverterSequenceString.lift( rustCall() { _status -> @@ -818,45 +1250,43 @@ fun `resolveHttps`(`publicKey`: String): List { } -@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") -suspend fun `signIn`(`secretKey`: String) : List { - return uniffiRustCallAsync( - _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_sign_in(FfiConverterString.lower(`secretKey`),), - { future, continuation -> _UniFFILib.INSTANCE.ffi_pubkymobile_rust_future_poll_rust_buffer(future, continuation) }, - { future, continuation -> _UniFFILib.INSTANCE.ffi_pubkymobile_rust_future_complete_rust_buffer(future, continuation) }, - { future -> _UniFFILib.INSTANCE.ffi_pubkymobile_rust_future_free_rust_buffer(future) }, - // lift function - { FfiConverterSequenceString.lift(it) }, - // Error FFI converter - NullCallStatusErrorHandler, - ) +fun `session`(`pubky`: String): List { + return FfiConverterSequenceString.lift( + rustCall() { _status -> + _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_session(FfiConverterString.lower(`pubky`),_status) +}) } -@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") -suspend fun `signOut`(`secretKey`: String) : List { - return uniffiRustCallAsync( - _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_sign_out(FfiConverterString.lower(`secretKey`),), - { future, continuation -> _UniFFILib.INSTANCE.ffi_pubkymobile_rust_future_poll_rust_buffer(future, continuation) }, - { future, continuation -> _UniFFILib.INSTANCE.ffi_pubkymobile_rust_future_complete_rust_buffer(future, continuation) }, - { future -> _UniFFILib.INSTANCE.ffi_pubkymobile_rust_future_free_rust_buffer(future) }, - // lift function - { FfiConverterSequenceString.lift(it) }, - // Error FFI converter - NullCallStatusErrorHandler, - ) + +fun `setEventListener`(`listener`: EventListener) = + + rustCall() { _status -> + _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_set_event_listener(FfiConverterTypeEventListener.lower(`listener`),_status) } -@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") -suspend fun `signUp`(`secretKey`: String, `homeserver`: String) : List { - return uniffiRustCallAsync( - _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_sign_up(FfiConverterString.lower(`secretKey`),FfiConverterString.lower(`homeserver`),), - { future, continuation -> _UniFFILib.INSTANCE.ffi_pubkymobile_rust_future_poll_rust_buffer(future, continuation) }, - { future, continuation -> _UniFFILib.INSTANCE.ffi_pubkymobile_rust_future_complete_rust_buffer(future, continuation) }, - { future -> _UniFFILib.INSTANCE.ffi_pubkymobile_rust_future_free_rust_buffer(future) }, - // lift function - { FfiConverterSequenceString.lift(it) }, - // Error FFI converter - NullCallStatusErrorHandler, - ) + + +fun `signIn`(`secretKey`: String): List { + return FfiConverterSequenceString.lift( + rustCall() { _status -> + _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_sign_in(FfiConverterString.lower(`secretKey`),_status) +}) } + +fun `signOut`(`secretKey`: String): List { + return FfiConverterSequenceString.lift( + rustCall() { _status -> + _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_sign_out(FfiConverterString.lower(`secretKey`),_status) +}) +} + + +fun `signUp`(`secretKey`: String, `homeserver`: String): List { + return FfiConverterSequenceString.lift( + rustCall() { _status -> + _UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_sign_up(FfiConverterString.lower(`secretKey`),FfiConverterString.lower(`homeserver`),_status) +}) +} + + diff --git a/android/src/main/jniLibs/arm64-v8a/libpubkymobile.so b/android/src/main/jniLibs/arm64-v8a/libpubkymobile.so index c5154d4..079d6dd 100755 Binary files a/android/src/main/jniLibs/arm64-v8a/libpubkymobile.so and b/android/src/main/jniLibs/arm64-v8a/libpubkymobile.so differ diff --git a/android/src/main/jniLibs/armeabi-v7a/libpubkymobile.so b/android/src/main/jniLibs/armeabi-v7a/libpubkymobile.so index a35f7d7..e605128 100755 Binary files a/android/src/main/jniLibs/armeabi-v7a/libpubkymobile.so and b/android/src/main/jniLibs/armeabi-v7a/libpubkymobile.so differ diff --git a/android/src/main/jniLibs/x86/libpubkymobile.so b/android/src/main/jniLibs/x86/libpubkymobile.so index cebb52c..e00682b 100755 Binary files a/android/src/main/jniLibs/x86/libpubkymobile.so and b/android/src/main/jniLibs/x86/libpubkymobile.so differ diff --git a/android/src/main/jniLibs/x86_64/libpubkymobile.so b/android/src/main/jniLibs/x86_64/libpubkymobile.so index 127614e..889b893 100755 Binary files a/android/src/main/jniLibs/x86_64/libpubkymobile.so and b/android/src/main/jniLibs/x86_64/libpubkymobile.so differ diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 96f43ec..b51e9ae 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1237,7 +1237,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-pubky (0.8.0): + - react-native-pubky (0.9.0): - DoubleConversion - glog - hermes-engine @@ -1757,7 +1757,7 @@ SPEC CHECKSUMS: React-logger: 4072f39df335ca443932e0ccece41fbeb5ca8404 React-Mapbuffer: 714f2fae68edcabfc332b754e9fbaa8cfc68fdd4 React-microtasksnativemodule: 4943ad8f99be8ccf5a63329fa7d269816609df9e - react-native-pubky: 1da8f3324a665ecf4495652efe0e67820a22f3a2 + react-native-pubky: 56f7277970ed798732a1268a6fac2fe4f8517c25 React-nativeconfig: 4a9543185905fe41014c06776bf126083795aed9 React-NativeModulesApple: 0506da59fc40d2e1e6e12a233db5e81c46face27 React-perflogger: 3bbb82f18e9ac29a1a6931568e99d6305ef4403b diff --git a/example/src/App.tsx b/example/src/App.tsx index fcd64a0..997067c 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,4 +1,5 @@ import { StyleSheet, View, Button } from 'react-native'; +import { useEffect } from 'react'; import { auth, parseAuthUrl, @@ -16,6 +17,10 @@ import { getPublicKeyFromSecretKey, decryptRecoveryFile, createRecoveryFile, + setEventListener, + removeEventListener, + session, + deleteFile, } from '@synonymdev/react-native-pubky'; const HOMESERVER = '8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo'; @@ -24,6 +29,34 @@ const SECRET_KEY = const PUBLIC_KEY = 'z4e8s17cou9qmuwen8p1556jzhf1wktmzo6ijsfnri9c4hnrdfty'; export default function App() { + useEffect(() => { + let cleanupListener: (() => void) | undefined; + + const setupEventListener = async () => { + const result = await setEventListener((eventData) => { + console.log('Received event:', eventData); + }); + + if (result.isOk()) { + console.log('Event listener set up successfully'); + } else { + console.error('Failed to set up event listener:', result.error); + } + + cleanupListener = () => { + removeEventListener(); + }; + }; + + setupEventListener(); + + // Cleanup function + return () => { + if (cleanupListener) { + cleanupListener(); + } + }; + }, []); return (