diff --git a/README.md b/README.md index 72353c2..5acdea5 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,11 @@ npm install @synonymdev/react-native-pubky - [x] [publish](#publish): Functionality to publish content. - [x] [resolve](#resolve): Functionality to resolve content. ### Methods to be Implemented -- [ ] signIn: Functionality to sign in. -- [ ] signUp: Functionality to sign up. -- [ ] signOut: Functionality to sign out. +- [ ] signIn: Sign-in to a homeserver. +- [ ] signUp: Sign-up to a homeserver and update Pkarr accordingly. +- [ ] signOut: Sign-out from a homeserver. +- [ ] put: Upload a small payload to a given path. +- [ ] get: Download a small payload from a given path relative to a pubky author. ## Usage diff --git a/android/src/main/java/com/pubky/PubkyModule.kt b/android/src/main/java/com/pubky/PubkyModule.kt index 599087a..05247d5 100644 --- a/android/src/main/java/com/pubky/PubkyModule.kt +++ b/android/src/main/java/com/pubky/PubkyModule.kt @@ -13,6 +13,11 @@ import uniffi.pubkymobile.auth import uniffi.pubkymobile.parseAuthUrl import uniffi.pubkymobile.publish import uniffi.pubkymobile.resolve +import uniffi.pubkymobile.signUp +import uniffi.pubkymobile.signIn +import uniffi.pubkymobile.signOut +import uniffi.pubkymobile.put +import uniffi.pubkymobile.get class PubkyModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { @@ -91,6 +96,101 @@ class PubkyModule(reactContext: ReactApplicationContext) : } } + @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) + } + } + } + } + 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 6973574..5cc31ef 100644 --- a/android/src/main/java/uniffi/pubkymobile/pubkymobile.kt +++ b/android/src/main/java/uniffi/pubkymobile/pubkymobile.kt @@ -29,6 +29,9 @@ 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 // 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 @@ -377,18 +380,29 @@ internal interface _UniFFILib : Library { .also { lib: _UniFFILib -> uniffiCheckContractApiVersion(lib) uniffiCheckApiChecksums(lib) + uniffiRustFutureContinuationCallback.register(lib) } } } 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_parse_auth_url(`url`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, ): RustBuffer.ByValue fun uniffi_pubkymobile_fn_func_publish(`recordName`: RustBuffer.ByValue,`recordContent`: 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_resolve(`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 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, @@ -505,12 +519,22 @@ internal interface _UniFFILib : Library { ): Unit fun uniffi_pubkymobile_checksum_func_auth( ): Short + fun uniffi_pubkymobile_checksum_func_get( + ): Short fun uniffi_pubkymobile_checksum_func_parse_auth_url( ): Short fun uniffi_pubkymobile_checksum_func_publish( ): Short + fun uniffi_pubkymobile_checksum_func_put( + ): Short fun uniffi_pubkymobile_checksum_func_resolve( ): 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 ffi_pubkymobile_uniffi_contract_version( ): Int @@ -531,18 +555,77 @@ 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()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_pubkymobile_checksum_func_parse_auth_url() != 29088.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_pubkymobile_checksum_func_publish() != 20156.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_pubkymobile_checksum_func_put() != 47594.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_pubkymobile_checksum_func_resolve() != 18303.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_pubkymobile_checksum_func_sign_in() != 53969.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_pubkymobile_checksum_func_sign_out() != 32961.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_pubkymobile_checksum_func_sign_up() != 28083.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. @@ -626,6 +709,10 @@ public object FfiConverterSequenceString: FfiConverterRustBuffer> { } } + + + + fun `auth`(`url`: String, `secretKey`: String): List { return FfiConverterSequenceString.lift( rustCall() { _status -> @@ -634,6 +721,20 @@ 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 `parseAuthUrl`(`url`: String): List { return FfiConverterSequenceString.lift( rustCall() { _status -> @@ -650,6 +751,20 @@ fun `publish`(`recordName`: String, `recordContent`: 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 `resolve`(`publicKey`: String): List { return FfiConverterSequenceString.lift( rustCall() { _status -> @@ -658,3 +773,45 @@ fun `resolve`(`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, + ) +} + +@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, + ) +} + +@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, + ) +} + diff --git a/android/src/main/jniLibs/arm64-v8a/libpubkymobile.so b/android/src/main/jniLibs/arm64-v8a/libpubkymobile.so index 5963a27..4c391e0 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 f7d3b11..6b5e047 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 4e4e245..1f9f451 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 409b497..6726930 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/src/App.tsx b/example/src/App.tsx index d13bd08..395446c 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -4,6 +4,11 @@ import { parseAuthUrl, publish, resolve, + signUp, + signIn, + signOut, + put, + get, } from '@synonymdev/react-native-pubky'; export default function App() { @@ -31,9 +36,9 @@ export default function App() { title={'parseAuthUrl'} onPress={async (): Promise => { try { - const res = await parseAuthUrl( - 'pubkyauth:///?relay=https://demo.httprelay.io/link&capabilities=/pub/pubky.app:rw,/pub/example.com/nested:rw&secret=FyzJ3gJ1W7boyFZC1Do9fYrRmDNgCLNRwEu_gaBgPUA' - ); + const pubkyAuthUrl = + 'pubkyauth:///?relay=https://demo.httprelay.io/link&capabilities=/pub/pubky.app:rw,/pub/example.com/nested:rw&secret=FyzJ3gJ1W7boyFZC1Do9fYrRmDNgCLNRwEu_gaBgPUA'; + const res = await parseAuthUrl(pubkyAuthUrl); if (res.isErr()) { console.log(res.error.message); return; @@ -49,9 +54,9 @@ export default function App() { onPress={async (): Promise => { try { const res = await publish( - 'recordnametest', - 'recordcontenttest', - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + 'recordnametest', // Record Name + 'recordcontenttest', // Record Content + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' // Secret Key ); if (res.isErr()) { console.log(res.error.message); @@ -68,7 +73,7 @@ export default function App() { onPress={async (): Promise => { try { const res = await resolve( - 'z4e8s17cou9qmuwen8p1556jzhf1wktmzo6ijsfnri9c4hnrdfty' + 'z4e8s17cou9qmuwen8p1556jzhf1wktmzo6ijsfnri9c4hnrdfty' // Public key ); if (res.isErr()) { console.log(res.error.message); @@ -80,6 +85,88 @@ export default function App() { } }} /> +