diff --git a/README.md b/README.md index 3d95119..c4613e1 100644 --- a/README.md +++ b/README.md @@ -16,15 +16,17 @@ npm install @synonymdev/react-native-pubky - [x] [resolve](#resolve): Functionality to resolve content. - [x] [publishHttps](#publishHttps): Publish HTTPS records. - [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] [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] [generateSecretKey](#generateSecretKey): Generate a secret key. - [x] [getPublicKeyFromSecretKey](#getPublicKeyFromSecretKey): Get the public key string and uri from a secret key. ### Methods to be Implemented -- [ ] signIn: Sign-in to a homeserver. -- [ ] signUp: Sign-up to a homeserver and update Pkarr accordingly. -- [ ] signOut: Sign-out from a homeserver. +- [ ] getProfile: Retrieve the profile of a user. +- [ ] editProfile: Submit changes to the specified profile. ## Usage @@ -118,8 +120,8 @@ console.log(resolveHttpsRes.value); import { put } from '@synonymdev/react-native-pubky'; const putRes = await put( - 'url', // URL - 'content', // Content + 'pubky://z4e8s17cou9qmuwen8p1556jzhf1wktmzo6ijsfnri9c4hnrdfty/pub/synonym.to', // URL + { data: 'test content' }, // Content ); if (putRes.isErr()) { console.log(putRes.error.message); @@ -133,7 +135,7 @@ console.log(putRes.value); import { get } from '@synonymdev/react-native-pubky'; const getRes = await get( - 'url' // URL + 'pubky://z4e8s17cou9qmuwen8p1556jzhf1wktmzo6ijsfnri9c4hnrdfty/pub/synonym.to' // URL ); if (getRes.isErr()) { console.log(getRes.error.message); @@ -147,7 +149,7 @@ console.log(getRes.value); import { list } from '@synonymdev/react-native-pubky'; const listRes = await list( - 'url' // URL + 'pubky://z4e8s17cou9qmuwen8p1556jzhf1wktmzo6ijsfnri9c4hnrdfty/pub/' // URL ); if (listRes.isErr()) { console.log(listRes.error.message); @@ -180,6 +182,49 @@ if (getPublicKeyFromSecretKeyRes.isErr()) { console.log(getPublicKeyFromSecretKeyRes.value); ``` +### signUp +```js +import { signUp } from '@synonymdev/react-native-pubky'; + +const signUpRes = await signUp( + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', // Secret + 'pubky://8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo', // Homeserver +); +if (signUpRes.isErr()) { + console.log(signUpRes.error.message); + return; +} +console.log(signUpRes.value); +``` + +### signIn +```js +import { signIn } from '@synonymdev/react-native-pubky'; + +const signInRes = await signIn( + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' // Secret Key +); +if (signInRes.isErr()) { + console.log(signInRes.error.message); + return; +} +console.log(signInRes.value); +``` + +### signIn +```js +import { signOut } from '@synonymdev/react-native-pubky'; + +const signOutRes = await signOut( + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' // Secret Key +); +if (signOutRes.isErr()) { + console.log(signOutRes.error.message); + return; +} +console.log(signOutRes.value); +``` + ## Local Installation 1. Clone & npm install: diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index c523763..8080bf3 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.7.0): + - react-native-pubky (0.7.1): - DoubleConversion - glog - hermes-engine @@ -1757,7 +1757,7 @@ SPEC CHECKSUMS: React-logger: 4072f39df335ca443932e0ccece41fbeb5ca8404 React-Mapbuffer: 714f2fae68edcabfc332b754e9fbaa8cfc68fdd4 React-microtasksnativemodule: 4943ad8f99be8ccf5a63329fa7d269816609df9e - react-native-pubky: e296eaeb8422b0864f5807592eac144d7d9a6eae + react-native-pubky: 1740252f1e510886c4239242d0b731bc4d96b91b React-nativeconfig: 4a9543185905fe41014c06776bf126083795aed9 React-NativeModulesApple: 0506da59fc40d2e1e6e12a233db5e81c46face27 React-perflogger: 3bbb82f18e9ac29a1a6931568e99d6305ef4403b diff --git a/example/src/App.tsx b/example/src/App.tsx index 4cc7547..a395143 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -16,6 +16,11 @@ import { getPublicKeyFromSecretKey, } from '@synonymdev/react-native-pubky'; +const HOMESERVER = '8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo'; +const SECRET_KEY = + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'; +const PUBLIC_KEY = 'z4e8s17cou9qmuwen8p1556jzhf1wktmzo6ijsfnri9c4hnrdfty'; + export default function App() { return ( @@ -25,7 +30,7 @@ export default function App() { try { const res = await auth( 'pubkyauth:///?caps=/pub/pubky.app/:rw,/pub/foo.bar/file:r&secret=U55XnoH6vsMCpx1pxHtt8fReVg4Brvu9C0gUBuw-Jkw&relay=http://167.86.102.121:4173/', - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + SECRET_KEY ); if (res.isErr()) { console.log(res.error.message); @@ -61,7 +66,7 @@ export default function App() { const res = await publish( 'recordnametest', // Record Name 'recordcontenttest', // Record Content - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' // Secret Key + SECRET_KEY // Secret Key ); if (res.isErr()) { console.log(res.error.message); @@ -78,7 +83,7 @@ export default function App() { onPress={async (): Promise => { try { const res = await resolve( - 'z4e8s17cou9qmuwen8p1556jzhf1wktmzo6ijsfnri9c4hnrdfty' // Public key + PUBLIC_KEY // Public key ); if (res.isErr()) { console.log(res.error.message); @@ -95,8 +100,8 @@ export default function App() { onPress={async (): Promise => { try { const res = await signUp( - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', // Secret Key - 'pubky://8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo' // Homeserver + SECRET_KEY, // Secret Key + `pubky://${HOMESERVER}` // Homeserver ); if (res.isErr()) { console.log(res.error.message); @@ -113,7 +118,7 @@ export default function App() { onPress={async (): Promise => { try { const res = await signIn( - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' // Secret Key + SECRET_KEY // Secret Key ); if (res.isErr()) { console.log(res.error.message); @@ -130,7 +135,7 @@ export default function App() { onPress={async (): Promise => { try { const res = await signOut( - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' // Secret Key + SECRET_KEY // Secret Key ); if (res.isErr()) { console.log(res.error.message); @@ -146,7 +151,9 @@ export default function App() { title={'put'} onPress={async (): Promise => { try { - const res = await put('', { data: 'test data' }); + const res = await put(`pubky://${PUBLIC_KEY}/pub/synonym.to`, { + data: 'test data', + }); if (res.isErr()) { console.log(res.error.message); return; @@ -161,7 +168,7 @@ export default function App() { title={'get'} onPress={async (): Promise => { try { - const res = await get(''); + const res = await get(`pubky://${PUBLIC_KEY}/pub/synonym.to`); if (res.isErr()) { console.log(res.error.message); return; @@ -180,7 +187,7 @@ export default function App() { const res = await publishHttps( 'example.com', // Record Name 'target.example.com', // Target - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' // Secret Key + SECRET_KEY // Secret Key ); if (res.isErr()) { console.log(res.error.message); @@ -198,7 +205,7 @@ export default function App() { onPress={async (): Promise => { try { const res = await resolveHttps( - 'z4e8s17cou9qmuwen8p1556jzhf1wktmzo6ijsfnri9c4hnrdfty' // Public key + PUBLIC_KEY // Public key ); if (res.isErr()) { console.log(res.error.message); @@ -216,7 +223,7 @@ export default function App() { onPress={async (): Promise => { try { const res = await list( - 'url' // URL + `pubky://${PUBLIC_KEY}/pub/synonym.to` // URL ); if (res.isErr()) { console.log(res.error.message); @@ -249,7 +256,7 @@ export default function App() { onPress={async (): Promise => { try { const res = await getPublicKeyFromSecretKey( - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' // Secret Key + SECRET_KEY // Secret Key ); if (res.isErr()) { console.log(res.error.message); diff --git a/ios/Frameworks/PubkyMobile.xcframework/Info.plist b/ios/Frameworks/PubkyMobile.xcframework/Info.plist index bb3ac5e..e1d4787 100644 --- a/ios/Frameworks/PubkyMobile.xcframework/Info.plist +++ b/ios/Frameworks/PubkyMobile.xcframework/Info.plist @@ -4,22 +4,6 @@ AvailableLibraries - - BinaryPath - libpubkymobile.a - HeadersPath - Headers - LibraryIdentifier - ios-arm64 - LibraryPath - libpubkymobile.a - SupportedArchitectures - - arm64 - - SupportedPlatform - ios - BinaryPath libpubkymobile.a @@ -38,6 +22,22 @@ SupportedPlatformVariant simulator + + BinaryPath + libpubkymobile.a + HeadersPath + Headers + LibraryIdentifier + ios-arm64 + LibraryPath + libpubkymobile.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + CFBundlePackageType XFWK diff --git a/ios/Frameworks/PubkyMobile.xcframework/ios-arm64-simulator/Headers/pubkymobileFFI.h b/ios/Frameworks/PubkyMobile.xcframework/ios-arm64-simulator/Headers/pubkymobileFFI.h index 1d9357c..c3eb798 100644 --- a/ios/Frameworks/PubkyMobile.xcframework/ios-arm64-simulator/Headers/pubkymobileFFI.h +++ b/ios/Frameworks/PubkyMobile.xcframework/ios-arm64-simulator/Headers/pubkymobileFFI.h @@ -68,7 +68,7 @@ RustBuffer uniffi_pubkymobile_fn_func_auth(RustBuffer url, RustBuffer secret_key RustBuffer uniffi_pubkymobile_fn_func_generate_secret_key(RustCallStatus *_Nonnull out_status ); -void* _Nonnull uniffi_pubkymobile_fn_func_get(RustBuffer url +RustBuffer uniffi_pubkymobile_fn_func_get(RustBuffer url, RustCallStatus *_Nonnull out_status ); RustBuffer uniffi_pubkymobile_fn_func_get_public_key_from_secret_key(RustBuffer secret_key, RustCallStatus *_Nonnull out_status ); @@ -80,17 +80,17 @@ RustBuffer uniffi_pubkymobile_fn_func_publish(RustBuffer record_name, RustBuffer ); RustBuffer uniffi_pubkymobile_fn_func_publish_https(RustBuffer record_name, RustBuffer target, RustBuffer secret_key, RustCallStatus *_Nonnull out_status ); -void* _Nonnull uniffi_pubkymobile_fn_func_put(RustBuffer url, RustBuffer content +RustBuffer uniffi_pubkymobile_fn_func_put(RustBuffer url, RustBuffer content, RustCallStatus *_Nonnull out_status ); RustBuffer uniffi_pubkymobile_fn_func_resolve(RustBuffer public_key, RustCallStatus *_Nonnull out_status ); RustBuffer uniffi_pubkymobile_fn_func_resolve_https(RustBuffer public_key, RustCallStatus *_Nonnull out_status ); -void* _Nonnull uniffi_pubkymobile_fn_func_sign_in(RustBuffer secret_key +RustBuffer uniffi_pubkymobile_fn_func_sign_in(RustBuffer secret_key, RustCallStatus *_Nonnull out_status ); -void* _Nonnull uniffi_pubkymobile_fn_func_sign_out(RustBuffer secret_key +RustBuffer uniffi_pubkymobile_fn_func_sign_out(RustBuffer secret_key, RustCallStatus *_Nonnull out_status ); -void* _Nonnull uniffi_pubkymobile_fn_func_sign_up(RustBuffer secret_key, RustBuffer homeserver +RustBuffer uniffi_pubkymobile_fn_func_sign_up(RustBuffer secret_key, RustBuffer homeserver, RustCallStatus *_Nonnull out_status ); RustBuffer ffi_pubkymobile_rustbuffer_alloc(int32_t size, RustCallStatus *_Nonnull out_status ); diff --git a/ios/Frameworks/PubkyMobile.xcframework/ios-arm64-simulator/libpubkymobile.a b/ios/Frameworks/PubkyMobile.xcframework/ios-arm64-simulator/libpubkymobile.a index 9e662f0..5cf3087 100644 Binary files a/ios/Frameworks/PubkyMobile.xcframework/ios-arm64-simulator/libpubkymobile.a and b/ios/Frameworks/PubkyMobile.xcframework/ios-arm64-simulator/libpubkymobile.a differ diff --git a/ios/Frameworks/PubkyMobile.xcframework/ios-arm64/Headers/pubkymobileFFI.h b/ios/Frameworks/PubkyMobile.xcframework/ios-arm64/Headers/pubkymobileFFI.h index 1d9357c..c3eb798 100644 --- a/ios/Frameworks/PubkyMobile.xcframework/ios-arm64/Headers/pubkymobileFFI.h +++ b/ios/Frameworks/PubkyMobile.xcframework/ios-arm64/Headers/pubkymobileFFI.h @@ -68,7 +68,7 @@ RustBuffer uniffi_pubkymobile_fn_func_auth(RustBuffer url, RustBuffer secret_key RustBuffer uniffi_pubkymobile_fn_func_generate_secret_key(RustCallStatus *_Nonnull out_status ); -void* _Nonnull uniffi_pubkymobile_fn_func_get(RustBuffer url +RustBuffer uniffi_pubkymobile_fn_func_get(RustBuffer url, RustCallStatus *_Nonnull out_status ); RustBuffer uniffi_pubkymobile_fn_func_get_public_key_from_secret_key(RustBuffer secret_key, RustCallStatus *_Nonnull out_status ); @@ -80,17 +80,17 @@ RustBuffer uniffi_pubkymobile_fn_func_publish(RustBuffer record_name, RustBuffer ); RustBuffer uniffi_pubkymobile_fn_func_publish_https(RustBuffer record_name, RustBuffer target, RustBuffer secret_key, RustCallStatus *_Nonnull out_status ); -void* _Nonnull uniffi_pubkymobile_fn_func_put(RustBuffer url, RustBuffer content +RustBuffer uniffi_pubkymobile_fn_func_put(RustBuffer url, RustBuffer content, RustCallStatus *_Nonnull out_status ); RustBuffer uniffi_pubkymobile_fn_func_resolve(RustBuffer public_key, RustCallStatus *_Nonnull out_status ); RustBuffer uniffi_pubkymobile_fn_func_resolve_https(RustBuffer public_key, RustCallStatus *_Nonnull out_status ); -void* _Nonnull uniffi_pubkymobile_fn_func_sign_in(RustBuffer secret_key +RustBuffer uniffi_pubkymobile_fn_func_sign_in(RustBuffer secret_key, RustCallStatus *_Nonnull out_status ); -void* _Nonnull uniffi_pubkymobile_fn_func_sign_out(RustBuffer secret_key +RustBuffer uniffi_pubkymobile_fn_func_sign_out(RustBuffer secret_key, RustCallStatus *_Nonnull out_status ); -void* _Nonnull uniffi_pubkymobile_fn_func_sign_up(RustBuffer secret_key, RustBuffer homeserver +RustBuffer uniffi_pubkymobile_fn_func_sign_up(RustBuffer secret_key, RustBuffer homeserver, RustCallStatus *_Nonnull out_status ); RustBuffer ffi_pubkymobile_rustbuffer_alloc(int32_t size, RustCallStatus *_Nonnull out_status ); diff --git a/ios/Frameworks/PubkyMobile.xcframework/ios-arm64/libpubkymobile.a b/ios/Frameworks/PubkyMobile.xcframework/ios-arm64/libpubkymobile.a index 3104b16..72f9fae 100644 Binary files a/ios/Frameworks/PubkyMobile.xcframework/ios-arm64/libpubkymobile.a and b/ios/Frameworks/PubkyMobile.xcframework/ios-arm64/libpubkymobile.a differ diff --git a/ios/pubkymobile.swift b/ios/pubkymobile.swift index ae97cd3..862e86e 100644 --- a/ios/pubkymobile.swift +++ b/ios/pubkymobile.swift @@ -356,68 +356,6 @@ fileprivate struct FfiConverterSequenceString: FfiConverterRustBuffer { return seq } } -private let UNIFFI_RUST_FUTURE_POLL_READY: Int8 = 0 -private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1 - -fileprivate func uniffiRustCallAsync( - rustFutureFunc: () -> UnsafeMutableRawPointer, - pollFunc: (UnsafeMutableRawPointer, UnsafeMutableRawPointer) -> (), - completeFunc: (UnsafeMutableRawPointer, UnsafeMutablePointer) -> F, - freeFunc: (UnsafeMutableRawPointer) -> (), - liftFunc: (F) throws -> T, - errorHandler: ((RustBuffer) throws -> Error)? -) async throws -> T { - // Make sure to call uniffiEnsureInitialized() since future creation doesn't have a - // RustCallStatus param, so doesn't use makeRustCall() - uniffiEnsureInitialized() - let rustFuture = rustFutureFunc() - defer { - freeFunc(rustFuture) - } - var pollResult: Int8; - repeat { - pollResult = await withUnsafeContinuation { - pollFunc(rustFuture, ContinuationHolder($0).toOpaque()) - } - } while pollResult != UNIFFI_RUST_FUTURE_POLL_READY - - return try liftFunc(makeRustCall( - { completeFunc(rustFuture, $0) }, - errorHandler: errorHandler - )) -} - -// Callback handlers for an async calls. These are invoked by Rust when the future is ready. They -// lift the return value or error and resume the suspended function. -fileprivate func uniffiFutureContinuationCallback(ptr: UnsafeMutableRawPointer, pollResult: Int8) { - ContinuationHolder.fromOpaque(ptr).resume(pollResult) -} - -// Wraps UnsafeContinuation in a class so that we can use reference counting when passing it across -// the FFI -fileprivate class ContinuationHolder { - let continuation: UnsafeContinuation - - init(_ continuation: UnsafeContinuation) { - self.continuation = continuation - } - - func resume(_ pollResult: Int8) { - self.continuation.resume(returning: pollResult) - } - - func toOpaque() -> UnsafeMutableRawPointer { - return Unmanaged.passRetained(self).toOpaque() - } - - static func fromOpaque(_ ptr: UnsafeRawPointer) -> ContinuationHolder { - return Unmanaged.fromOpaque(ptr).takeRetainedValue() - } -} - -fileprivate func uniffiInitContinuationCallback() { - ffi_pubkymobile_rust_future_continuation_callback_set(uniffiFutureContinuationCallback) -} public func auth(url: String, secretKey: String) -> [String] { return try! FfiConverterSequenceString.lift( @@ -437,24 +375,15 @@ public func generateSecretKey() -> [String] { ) } -public func get(url: String) async -> [String] { - return try! await uniffiRustCallAsync( - rustFutureFunc: { - uniffi_pubkymobile_fn_func_get( - FfiConverterString.lower(url) - ) - }, - pollFunc: ffi_pubkymobile_rust_future_poll_rust_buffer, - completeFunc: ffi_pubkymobile_rust_future_complete_rust_buffer, - freeFunc: ffi_pubkymobile_rust_future_free_rust_buffer, - liftFunc: FfiConverterSequenceString.lift, - errorHandler: nil - +public func get(url: String) -> [String] { + return try! FfiConverterSequenceString.lift( + try! rustCall() { + uniffi_pubkymobile_fn_func_get( + FfiConverterString.lower(url),$0) +} ) } - - public func getPublicKeyFromSecretKey(secretKey: String) -> [String] { return try! FfiConverterSequenceString.lift( try! rustCall() { @@ -504,25 +433,16 @@ public func publishHttps(recordName: String, target: String, secretKey: String) ) } -public func put(url: String, content: String) async -> [String] { - return try! await uniffiRustCallAsync( - rustFutureFunc: { - uniffi_pubkymobile_fn_func_put( - FfiConverterString.lower(url), - FfiConverterString.lower(content) - ) - }, - pollFunc: ffi_pubkymobile_rust_future_poll_rust_buffer, - completeFunc: ffi_pubkymobile_rust_future_complete_rust_buffer, - freeFunc: ffi_pubkymobile_rust_future_free_rust_buffer, - liftFunc: FfiConverterSequenceString.lift, - errorHandler: nil - +public func put(url: String, content: String) -> [String] { + return try! FfiConverterSequenceString.lift( + try! rustCall() { + uniffi_pubkymobile_fn_func_put( + FfiConverterString.lower(url), + FfiConverterString.lower(content),$0) +} ) } - - public func resolve(publicKey: String) -> [String] { return try! FfiConverterSequenceString.lift( try! rustCall() { @@ -541,61 +461,34 @@ public func resolveHttps(publicKey: String) -> [String] { ) } -public func signIn(secretKey: String) async -> [String] { - return try! await uniffiRustCallAsync( - rustFutureFunc: { - uniffi_pubkymobile_fn_func_sign_in( - FfiConverterString.lower(secretKey) - ) - }, - pollFunc: ffi_pubkymobile_rust_future_poll_rust_buffer, - completeFunc: ffi_pubkymobile_rust_future_complete_rust_buffer, - freeFunc: ffi_pubkymobile_rust_future_free_rust_buffer, - liftFunc: FfiConverterSequenceString.lift, - errorHandler: nil - +public func signIn(secretKey: String) -> [String] { + return try! FfiConverterSequenceString.lift( + try! rustCall() { + uniffi_pubkymobile_fn_func_sign_in( + FfiConverterString.lower(secretKey),$0) +} ) } - - -public func signOut(secretKey: String) async -> [String] { - return try! await uniffiRustCallAsync( - rustFutureFunc: { - uniffi_pubkymobile_fn_func_sign_out( - FfiConverterString.lower(secretKey) - ) - }, - pollFunc: ffi_pubkymobile_rust_future_poll_rust_buffer, - completeFunc: ffi_pubkymobile_rust_future_complete_rust_buffer, - freeFunc: ffi_pubkymobile_rust_future_free_rust_buffer, - liftFunc: FfiConverterSequenceString.lift, - errorHandler: nil - +public func signOut(secretKey: String) -> [String] { + return try! FfiConverterSequenceString.lift( + try! rustCall() { + uniffi_pubkymobile_fn_func_sign_out( + FfiConverterString.lower(secretKey),$0) +} ) } - - -public func signUp(secretKey: String, homeserver: String) async -> [String] { - return try! await uniffiRustCallAsync( - rustFutureFunc: { - uniffi_pubkymobile_fn_func_sign_up( - FfiConverterString.lower(secretKey), - FfiConverterString.lower(homeserver) - ) - }, - pollFunc: ffi_pubkymobile_rust_future_poll_rust_buffer, - completeFunc: ffi_pubkymobile_rust_future_complete_rust_buffer, - freeFunc: ffi_pubkymobile_rust_future_free_rust_buffer, - liftFunc: FfiConverterSequenceString.lift, - errorHandler: nil - +public func signUp(secretKey: String, homeserver: String) -> [String] { + return try! FfiConverterSequenceString.lift( + try! rustCall() { + uniffi_pubkymobile_fn_func_sign_up( + FfiConverterString.lower(secretKey), + FfiConverterString.lower(homeserver),$0) +} ) } - - private enum InitializationResult { case ok case contractVersionMismatch @@ -617,7 +510,7 @@ private var initializationResult: InitializationResult { if (uniffi_pubkymobile_checksum_func_generate_secret_key() != 63116) { return InitializationResult.apiChecksumMismatch } - if (uniffi_pubkymobile_checksum_func_get() != 5395) { + if (uniffi_pubkymobile_checksum_func_get() != 21596) { return InitializationResult.apiChecksumMismatch } if (uniffi_pubkymobile_checksum_func_get_public_key_from_secret_key() != 23603) { @@ -635,7 +528,7 @@ private var initializationResult: InitializationResult { if (uniffi_pubkymobile_checksum_func_publish_https() != 14705) { return InitializationResult.apiChecksumMismatch } - if (uniffi_pubkymobile_checksum_func_put() != 47594) { + if (uniffi_pubkymobile_checksum_func_put() != 51107) { return InitializationResult.apiChecksumMismatch } if (uniffi_pubkymobile_checksum_func_resolve() != 18303) { @@ -644,17 +537,16 @@ private var initializationResult: InitializationResult { if (uniffi_pubkymobile_checksum_func_resolve_https() != 34593) { return InitializationResult.apiChecksumMismatch } - if (uniffi_pubkymobile_checksum_func_sign_in() != 53969) { + if (uniffi_pubkymobile_checksum_func_sign_in() != 21006) { return InitializationResult.apiChecksumMismatch } - if (uniffi_pubkymobile_checksum_func_sign_out() != 32961) { + if (uniffi_pubkymobile_checksum_func_sign_out() != 59116) { return InitializationResult.apiChecksumMismatch } - if (uniffi_pubkymobile_checksum_func_sign_up() != 28083) { + if (uniffi_pubkymobile_checksum_func_sign_up() != 58756) { return InitializationResult.apiChecksumMismatch } - uniffiInitContinuationCallback() return InitializationResult.ok } diff --git a/package.json b/package.json index 4407f80..e6d7e02 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@synonymdev/react-native-pubky", - "version": "0.7.0", + "version": "0.7.1", "description": "React Native Implementation of Pubky", "source": "./src/index.tsx", "main": "./lib/commonjs/index.js", diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 7bfb062..6a635ba 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -10,6 +10,7 @@ pub use utils::*; uniffi::setup_scaffolding!(); +use std::str; use std::collections::HashMap; use base64::Engine; use base64::engine::general_purpose; @@ -26,16 +27,9 @@ use serde_json::json; use utils::*; use once_cell::sync::Lazy; use std::sync::Arc; +use pkarr::bytes::Bytes; use tokio::runtime::Runtime; -static PKARR_CLIENT: Lazy> = Lazy::new(|| { - Arc::new( - PkarrClient::builder() - .build() - .expect("Failed to build PkarrClient"), - ) -}); - static PUBKY_CLIENT: Lazy> = Lazy::new(|| { Arc::new(PubkyClient::testnet()) }); @@ -87,209 +81,239 @@ pub fn get_public_key_from_secret_key(secret_key: String) -> Vec { #[uniffi::export] pub fn publish_https(record_name: String, target: String, secret_key: String) -> Vec { - let client = PKARR_CLIENT.clone(); + let runtime = TOKIO_RUNTIME.clone(); + runtime.block_on(async { + let client = PUBKY_CLIENT.clone(); - let keypair = match get_keypair_from_secret_key(&secret_key) { - Ok(keypair) => keypair, - Err(error) => return create_response_vector(true, error), - }; + let keypair = match get_keypair_from_secret_key(&secret_key) { + Ok(keypair) => keypair, + Err(error) => return create_response_vector(true, error), + }; - // Create SVCB record with the target domain - let target = match target.as_str().try_into() { - Ok(target) => target, - Err(e) => return create_response_vector(true, format!("Invalid target: {}", e)), - }; - let svcb = SVCB::new(0, target); + // Create SVCB record with the target domain + let target = match target.as_str().try_into() { + Ok(target) => target, + Err(e) => return create_response_vector(true, format!("Invalid target: {}", e)), + }; + let svcb = SVCB::new(0, target); - // Create HTTPS record - let https_record = HTTPS(svcb); + // Create HTTPS record + let https_record = HTTPS(svcb); - // Create DNS packet - let mut packet = Packet::new_reply(0); - let dns_name = match dns::Name::new(&record_name) { - Ok(name) => name, - Err(e) => return create_response_vector(true, format!("Invalid DNS name: {}", e)), - }; + // Create DNS packet + let mut packet = Packet::new_reply(0); + let dns_name = match dns::Name::new(&record_name) { + Ok(name) => name, + Err(e) => return create_response_vector(true, format!("Invalid DNS name: {}", e)), + }; - packet.answers.push(ResourceRecord::new( - dns_name, - dns::CLASS::IN, - 3600, // TTL in seconds - dns::rdata::RData::HTTPS(https_record), - )); + packet.answers.push(ResourceRecord::new( + dns_name, + dns::CLASS::IN, + 3600, // TTL in seconds + dns::rdata::RData::HTTPS(https_record), + )); - let signed_packet = match SignedPacket::from_packet(&keypair, &packet) { - Ok(signed_packet) => signed_packet, - Err(e) => return create_response_vector(true, format!("Failed to create signed packet: {}", e)), - }; + let signed_packet = match SignedPacket::from_packet(&keypair, &packet) { + Ok(signed_packet) => signed_packet, + Err(e) => return create_response_vector(true, format!("Failed to create signed packet: {}", e)), + }; - match client.publish(&signed_packet) { - Ok(()) => create_response_vector(false, keypair.public_key().to_string()), - Err(e) => create_response_vector(true, format!("Failed to publish: {}", e)), - } + match client.pkarr().publish(&signed_packet).await { + Ok(()) => create_response_vector(false, keypair.public_key().to_string()), + Err(e) => create_response_vector(true, format!("Failed to publish: {}", e)), + } + }) } #[uniffi::export] pub fn resolve_https(public_key: String) -> Vec { - let public_key = match public_key.as_str().try_into() { - Ok(key) => key, - Err(e) => return create_response_vector(true, format!("Invalid public key: {}", e)), - }; + let runtime = TOKIO_RUNTIME.clone(); + runtime.block_on(async { + let public_key = match public_key.as_str().try_into() { + Ok(key) => key, + Err(e) => return create_response_vector(true, format!("Invalid public key: {}", e)), + }; - let client = PKARR_CLIENT.clone(); + let client = PUBKY_CLIENT.clone(); - match client.resolve(&public_key) { - Ok(Some(signed_packet)) => { - // Extract HTTPS records from the signed packet - let https_records: Vec = signed_packet.packet().answers.iter() - .filter_map(|record| { - if let dns::rdata::RData::HTTPS(https) = &record.rdata { - // Create a JSON object - let mut https_json = serde_json::json!({ - "name": record.name.to_string(), - "class": format!("{:?}", record.class), - "ttl": record.ttl, - "priority": https.0.priority, - "target": https.0.target.to_string(), - }); + match client.pkarr().resolve(&public_key).await { + Ok(Some(signed_packet)) => { + // Extract HTTPS records from the signed packet + let https_records: Vec = signed_packet.packet().answers.iter() + .filter_map(|record| { + if let dns::rdata::RData::HTTPS(https) = &record.rdata { + // Create a JSON object + let mut https_json = serde_json::json!({ + "name": record.name.to_string(), + "class": format!("{:?}", record.class), + "ttl": record.ttl, + "priority": https.0.priority, + "target": https.0.target.to_string(), + }); - // Access specific parameters using the constants from SVCB - if let Some(port_param) = https.0.get_param(SVCB::PORT) { - if port_param.len() == 2 { - let port = u16::from_be_bytes([port_param[0], port_param[1]]); - https_json["port"] = serde_json::json!(port); - } - } - - // Access ALPN parameter if needed - if let Some(alpn_param) = https.0.get_param(SVCB::ALPN) { - // Parse ALPN protocols (list of character strings) - let mut position = 0; - let mut alpn_protocols = Vec::new(); - while position < alpn_param.len() { - let length = alpn_param[position] as usize; - position += 1; - if position + length <= alpn_param.len() { - let protocol = String::from_utf8_lossy( - &alpn_param[position..position + length], - ); - alpn_protocols.push(protocol.to_string()); - position += length; - } else { - break; // Malformed ALPN parameter + // Access specific parameters using the constants from SVCB + if let Some(port_param) = https.0.get_param(SVCB::PORT) { + if port_param.len() == 2 { + let port = u16::from_be_bytes([port_param[0], port_param[1]]); + https_json["port"] = serde_json::json!(port); } } - https_json["alpn"] = serde_json::json!(alpn_protocols); + + // Access ALPN parameter if needed + if let Some(alpn_param) = https.0.get_param(SVCB::ALPN) { + // Parse ALPN protocols (list of character strings) + let mut position = 0; + let mut alpn_protocols = Vec::new(); + while position < alpn_param.len() { + let length = alpn_param[position] as usize; + position += 1; + if position + length <= alpn_param.len() { + let protocol = String::from_utf8_lossy( + &alpn_param[position..position + length], + ); + alpn_protocols.push(protocol.to_string()); + position += length; + } else { + break; // Malformed ALPN parameter + } + } + https_json["alpn"] = serde_json::json!(alpn_protocols); + } + // TODO: Add other parameters as needed. + Some(https_json) + } else { + None } - // TODO: Add other parameters as needed. - Some(https_json) - } else { - None - } - }) - .collect(); + }) + .collect(); - if https_records.is_empty() { - return create_response_vector(true, "No HTTPS records found".to_string()); + if https_records.is_empty() { + return create_response_vector(true, "No HTTPS records found".to_string()); + } + + // Create JSON response + let json_obj = json!({ + "public_key": public_key.to_string(), + "https_records": https_records, + "last_seen": signed_packet.last_seen(), + "timestamp": signed_packet.timestamp(), + }); + + let json_str = match serde_json::to_string(&json_obj) { + Ok(json) => json, + Err(e) => return create_response_vector(true, format!("Failed to serialize JSON: {}", e)), + }; + + create_response_vector(false, json_str) + }, + Ok(None) => create_response_vector(true, "No signed packet found".to_string()), + Err(e) => create_response_vector(true, format!("Failed to resolve: {}", e)), + } + }) +} + +#[uniffi::export] +pub fn sign_up(secret_key: String, homeserver: String) -> Vec { + let runtime = TOKIO_RUNTIME.clone(); + runtime.block_on(async { + let client = PUBKY_CLIENT.clone(); + let keypair = match get_keypair_from_secret_key(&secret_key) { + Ok(keypair) => keypair, + Err(error) => return create_response_vector(true, error), + }; + + let homeserver_public_key = match PublicKey::try_from(homeserver) { + Ok(key) => key, + Err(error) => return create_response_vector(true, format!("Invalid homeserver public key: {}", error)), + }; + + match client.signup(&keypair, &homeserver_public_key).await { + Ok(session) => create_response_vector(false, session.pubky().to_string()), + Err(error) => create_response_vector(true, format!("signup failure: {}", error)), + } + }) +} + +#[uniffi::export] +pub fn sign_in(secret_key: String) -> Vec { + let runtime = TOKIO_RUNTIME.clone(); + runtime.block_on(async { + let client = PUBKY_CLIENT.clone(); + let keypair = match get_keypair_from_secret_key(&secret_key) { + Ok(keypair) => keypair, + Err(error) => return create_response_vector(true, error), + }; + match client.signin(&keypair).await { + Ok(_) => create_response_vector(false, "Sign in success".to_string()), + Err(error) => { + create_response_vector(true, format!("Failed to sign in: {}", error)) } - - // Create JSON response - let json_obj = json!({ - "public_key": public_key.to_string(), - "https_records": https_records, - "last_seen": signed_packet.last_seen(), - "timestamp": signed_packet.timestamp(), - }); - - let json_str = match serde_json::to_string(&json_obj) { - Ok(json) => json, - Err(e) => return create_response_vector(true, format!("Failed to serialize JSON: {}", e)), - }; - - create_response_vector(false, json_str) - }, - Ok(None) => create_response_vector(true, "No signed packet found".to_string()), - Err(e) => create_response_vector(true, format!("Failed to resolve: {}", e)), - } -} - -#[uniffi::export] -pub async fn sign_up(secret_key: String, homeserver: String) -> Vec { - let client = PUBKY_CLIENT.clone(); - let keypair = match get_keypair_from_secret_key(&secret_key) { - Ok(keypair) => keypair, - Err(error) => return create_response_vector(true, error), - }; - - let homeserver_public_key = match PublicKey::try_from(homeserver) { - Ok(key) => key, - Err(error) => return create_response_vector(true, format!("Invalid homeserver public key: {}", error)), - }; - - match client.signup(&keypair, &homeserver_public_key).await { - Ok(_) => create_response_vector(false, "signup success".to_string()), - Err(error) => create_response_vector(true, format!("signup failure: {}", error)), - } -} - -#[uniffi::export] -pub async fn sign_in(secret_key: String) -> Vec { - let client = PUBKY_CLIENT.clone(); - let keypair = match get_keypair_from_secret_key(&secret_key) { - Ok(keypair) => keypair, - Err(error) => return create_response_vector(true, error), - }; - match client.signin(&keypair).await { - Ok(_) => create_response_vector(false, "Sign in success".to_string()), - Err(error) => { - create_response_vector(true, format!("Failed to sign in: {}", error)) } - } + }) } #[uniffi::export] -pub async fn sign_out(secret_key: String) -> Vec { - let client = PUBKY_CLIENT.clone(); - let keypair = match get_keypair_from_secret_key(&secret_key) { - Ok(keypair) => keypair, - Err(error) => return create_response_vector(true, error), - }; - match client.signout(&keypair.public_key()).await { - Ok(_) => create_response_vector(false, "Sign out success".to_string()), - Err(error) => { - create_response_vector(true, format!("Failed to sign out: {}", error)) +pub fn sign_out(secret_key: String) -> Vec { + let runtime = TOKIO_RUNTIME.clone(); + runtime.block_on(async { + let client = PUBKY_CLIENT.clone(); + let keypair = match get_keypair_from_secret_key(&secret_key) { + Ok(keypair) => keypair, + Err(error) => return create_response_vector(true, error), + }; + match client.signout(&keypair.public_key()).await { + Ok(_) => create_response_vector(false, "Sign out success".to_string()), + Err(error) => { + create_response_vector(true, format!("Failed to sign out: {}", error)) + } } - } + }) } #[uniffi::export] -pub async fn put(url: String, content: String) -> Vec { - let client = PUBKY_CLIENT.clone(); - let parsed_url = match Url::parse(&url) { - Ok(url) => url, - Err(_) => return create_response_vector(true, "Failed to parse URL".to_string()), - }; - match client.put(parsed_url, &content.as_bytes()).await { - Ok(_) => create_response_vector(false, "Put success".to_string()), - Err(error) => { - create_response_vector(true, format!("Failed to put: {}", error)) +pub fn put(url: String, content: String) -> Vec { + let runtime = TOKIO_RUNTIME.clone(); + runtime.block_on(async { + let client = PUBKY_CLIENT.clone(); + let trimmed_url = url.trim_end_matches('/'); + let parsed_url = match Url::parse(&trimmed_url) { + Ok(url) => url, + Err(_) => return create_response_vector(true, "Failed to parse URL".to_string()), + }; + match client.put(parsed_url, &content.as_bytes()).await { + Ok(_) => create_response_vector(false, trimmed_url.to_string()), + Err(error) => { + create_response_vector(true, format!("Failed to put: {}", error)) + } } - } + }) } #[uniffi::export] -pub async fn get(url: String) -> Vec { - let client = PUBKY_CLIENT.clone(); - let parsed_url = match Url::parse(&url) { - Ok(url) => url, - Err(_) => return create_response_vector(true, "Failed to parse URL".to_string()), - }; - match client.get(parsed_url).await { - Ok(_) => create_response_vector(false, "Get success".to_string()), - Err(error) => { - create_response_vector(true, format!("Failed to get: {}", error)) - } - } +pub fn get(url: String) -> Vec { + let runtime = TOKIO_RUNTIME.clone(); + runtime.block_on(async { + let client = PUBKY_CLIENT.clone(); + let trimmed_url = url.trim_end_matches('/'); + let parsed_url = match Url::parse(&trimmed_url) { + Ok(url) => url, + Err(_) => return create_response_vector(true, "Failed to parse URL".to_string()), + }; + let result: Option = match client.get(parsed_url).await { + Ok(res) => res, + Err(_) => return create_response_vector(true, "Request failed".to_string()), + }; + let bytes = match result { + Some(bytes) => bytes, + None => return create_response_vector(true, "No data returned".to_string()), + }; + let string = match str::from_utf8(&bytes) { + Ok(s) => s.to_string(), + Err(_) => return create_response_vector(true, "Invalid UTF-8 sequence".to_string()), + }; + create_response_vector(false, string) + }) } /** @@ -300,115 +324,122 @@ pub async fn get(url: String) -> Vec { **/ #[uniffi::export] pub fn resolve(public_key: String) -> Vec { - let public_key = match public_key.as_str().try_into() { - Ok(key) => key, - Err(e) => return create_response_vector(true, format!("Invalid zbase32 encoded key: {}", e)), - }; - let client = PKARR_CLIENT.clone(); + let runtime = TOKIO_RUNTIME.clone(); + runtime.block_on(async { + let public_key = match public_key.as_str().try_into() { + Ok(key) => key, + Err(e) => return create_response_vector(true, format!("Invalid zbase32 encoded key: {}", e)), + }; + let client = PUBKY_CLIENT.clone(); - match client.resolve(&public_key) { - Ok(Some(signed_packet)) => { - // Collect references to ResourceRecords from the signed packet's answers - let all_records: Vec<&ResourceRecord> = signed_packet.packet().answers.iter().collect(); - // Convert each ResourceRecord to a JSON value, handling errors appropriately - let json_records: Vec = all_records - .iter() - .filter_map(|record| { - match resource_record_to_json(record) { - Ok(json_value) => Some(json_value), - Err(e) => { - eprintln!("Error converting record to JSON: {}", e); - None + match client.pkarr().resolve(&public_key).await { + Ok(Some(signed_packet)) => { + // Collect references to ResourceRecords from the signed packet's answers + let all_records: Vec<&ResourceRecord> = signed_packet.packet().answers.iter().collect(); + // Convert each ResourceRecord to a JSON value, handling errors appropriately + let json_records: Vec = all_records + .iter() + .filter_map(|record| { + match resource_record_to_json(record) { + Ok(json_value) => Some(json_value), + Err(e) => { + eprintln!("Error converting record to JSON: {}", e); + None + } } - } - }) - .collect(); + }) + .collect(); - let bytes = signed_packet.as_bytes(); - let public_key = &bytes[..32]; - let signature = &bytes[32..96]; - let timestamp = signed_packet.timestamp(); - let dns_packet = &bytes[104..]; - let hex: String = signed_packet.encode_hex(); + let bytes = signed_packet.as_bytes(); + let public_key = &bytes[..32]; + let signature = &bytes[32..96]; + let timestamp = signed_packet.timestamp(); + let dns_packet = &bytes[104..]; + let hex: String = signed_packet.encode_hex(); - let json_obj = json!({ - "signed_packet": hex, - "public_key": general_purpose::STANDARD.encode(public_key), - "signature": general_purpose::STANDARD.encode(signature), - "timestamp": timestamp, - "last_seen": signed_packet.last_seen(), - "dns_packet": general_purpose::STANDARD.encode(dns_packet), - "records": json_records - }); + let json_obj = json!({ + "signed_packet": hex, + "public_key": general_purpose::STANDARD.encode(public_key), + "signature": general_purpose::STANDARD.encode(signature), + "timestamp": timestamp, + "last_seen": signed_packet.last_seen(), + "dns_packet": general_purpose::STANDARD.encode(dns_packet), + "records": json_records + }); - let json_str = serde_json::to_string(&json_obj) - .expect("Failed to convert JSON object to string"); + let json_str = serde_json::to_string(&json_obj) + .expect("Failed to convert JSON object to string"); - create_response_vector(false, json_str) - }, - Ok(None) => { - create_response_vector(true, "No signed packet found".to_string()) + create_response_vector(false, json_str) + }, + Ok(None) => { + create_response_vector(true, "No signed packet found".to_string()) + } + Err(e) => { + create_response_vector(true, format!("Failed to resolve: {}", e)) + } } - Err(e) => { - create_response_vector(true, format!("Failed to resolve: {}", e)) - } - } + }) } #[uniffi::export] pub fn publish(record_name: String, record_content: String, secret_key: String) -> Vec { - let client = PKARR_CLIENT.clone(); + let runtime = TOKIO_RUNTIME.clone(); + runtime.block_on(async { + let client = PUBKY_CLIENT.clone(); - let keypair = match get_keypair_from_secret_key(&secret_key) { - Ok(keypair) => keypair, - Err(error) => return create_response_vector(true, error), - }; + let keypair = match get_keypair_from_secret_key(&secret_key) { + Ok(keypair) => keypair, + Err(error) => return create_response_vector(true, error), + }; - let mut packet = dns::Packet::new_reply(0); + let mut packet = dns::Packet::new_reply(0); - let dns_name = match dns::Name::new(&record_name) { - Ok(name) => name, - Err(e) => return create_response_vector(true, format!("Failed to create DNS name: {}", e)), - }; + let dns_name = match dns::Name::new(&record_name) { + Ok(name) => name, + Err(e) => return create_response_vector(true, format!("Failed to create DNS name: {}", e)), + }; - let record_content_str: &str = record_content.as_str(); + let record_content_str: &str = record_content.as_str(); - let txt_record = match record_content_str.try_into() { - Ok(value) => RData::TXT(value), - Err(e) => { - return create_response_vector(true, format!("Failed to convert string to TXT record: {}", e)) - } - }; + let txt_record = match record_content_str.try_into() { + Ok(value) => RData::TXT(value), + Err(e) => { + return create_response_vector(true, format!("Failed to convert string to TXT record: {}", e)) + } + }; - packet.answers.push(dns::ResourceRecord::new( - dns_name, - dns::CLASS::IN, - 30, - txt_record, - )); + packet.answers.push(dns::ResourceRecord::new( + dns_name, + dns::CLASS::IN, + 30, + txt_record, + )); - match SignedPacket::from_packet(&keypair, &packet) { - Ok(signed_packet) => { - match client.publish(&signed_packet) { - Ok(()) => { - create_response_vector(false, keypair.public_key().to_string()) - } - Err(e) => { - create_response_vector(true, format!("Failed to publish: {}", e)) + match SignedPacket::from_packet(&keypair, &packet) { + Ok(signed_packet) => { + match client.pkarr().publish(&signed_packet).await { + Ok(()) => { + create_response_vector(false, keypair.public_key().to_string()) + } + Err(e) => { + create_response_vector(true, format!("Failed to publish: {}", e)) + } } } + Err(e) => { + create_response_vector(true, format!("Failed to create signed packet: {}", e)) + } } - Err(e) => { - create_response_vector(true, format!("Failed to create signed packet: {}", e)) - } - } + }) } #[uniffi::export] pub fn list(url: String) -> Vec { let runtime = TOKIO_RUNTIME.clone(); runtime.block_on(async { let client = PUBKY_CLIENT.clone(); - let parsed_url = match Url::parse(&url) { + let trimmed_url = url.trim_end_matches('/'); + let parsed_url = match Url::parse(&trimmed_url) { Ok(url) => url, Err(_) => return create_response_vector(true, "Failed to parse URL".to_string()), }; diff --git a/rust/src/utils.rs b/rust/src/utils.rs index f85d6b0..58833f7 100644 --- a/rust/src/utils.rs +++ b/rust/src/utils.rs @@ -201,3 +201,44 @@ pub fn resource_record_to_json(record: &ResourceRecord) -> Result String { + // Construct the base URL + let mut url = format!("pubky://{}/pub/{}", public_key, domain); + + // Append each path segment, separated by '/' + for segment in path_segments { + if !segment.is_empty() { + url.push('/'); + url.push_str(segment); + } + } + + // Remove trailing slash if present + if url.ends_with('/') { + url.pop(); + } + + url +} + +/** +* Extract everything up to the first instance of "pub/" in a Pubky URL +* +* # Arguments +* * `full_url` - The full URL +* +* # Returns +* * `Some(String)` - The "pub/" part of the URL +* * `None` - If "pub/" is not found in the URL +*/ +pub fn get_list_url(full_url: &str) -> Option { + if let Some(index) = full_url.find("pub/") { + let end_index = index + "pub/".len(); + let substring = &full_url[..end_index]; + Some(substring.to_string()) + } else { + // "pub/" not found in the string + None + } +} diff --git a/rust/testing/main.rs b/rust/testing/main.rs index 0b8e1cf..c37f146 100644 --- a/rust/testing/main.rs +++ b/rust/testing/main.rs @@ -2,10 +2,11 @@ use std::string::ToString; use std::sync::Arc; use once_cell::sync::Lazy; use pkarr::{dns, Keypair, PublicKey, SignedPacket}; +use pkarr::bytes::Bytes; use pkarr::dns::rdata::RData; -use pkarr::mainline::Testnet; use pubky::PubkyClient; use url::Url; +use std::str; static PUBKY_CLIENT: Lazy> = Lazy::new(|| { // let custom_testnet = Testnet { @@ -43,15 +44,55 @@ static PUBKY_CLIENT: Lazy> = Lazy::new(|| { const HOMESERVER: &str = "pubky://8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo"; const SECRET_KEY: &str = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; +fn construct_pubky_url(public_key: &str, domain: &str, path_segments: &[&str]) -> String { + // Construct the base URL + let mut url = format!("pubky://{}/pub/{}", public_key, domain); + + // Append each path segment, separated by '/' + for segment in path_segments { + if !segment.is_empty() { + url.push('/'); + url.push_str(segment); + } + } + + // Remove trailing slash if present + if url.ends_with('/') { + url.pop(); + } + + url +} + +fn get_list_url(full_url: &str) -> Option { + if let Some(index) = full_url.find("pub/") { + // Add length of "pub/" to include it in the substring + let end_index = index + "pub/".len(); + let substring = &full_url[..end_index]; + Some(substring.to_string()) + } else { + // "pub/" not found in the string + None + } +} + + + + #[tokio::main] async fn main() { let sign_in_res = signin_or_signup(SECRET_KEY, HOMESERVER).await; - println!("{:?}", sign_in_res); + println!("Sign In/Up Response: {:?}", sign_in_res); // let res = publish("recordname".to_string(), "recordcontent".to_string(), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string()).await; - // println!("{:?}", res); - let url = "pubky://z4e8s17cou9qmuwen8p1556jzhf1wktmzo6ijsfnri9c4hnrdfty/pub/mydomain.com"; - let putRes = put(url.to_string(), "content".to_string()).await; - println!("{:?}", putRes); + // // println!("{:?}", res); + let public_key = &sign_in_res[1]; + let url = construct_pubky_url(public_key, "mydomain.com", &[]); + let put_res = put(&url, &"newcontent".to_string()).await; + println!("Put Response: {:?}", put_res); + let get_res = get(&url).await; + println!("Get Response: {:?}", get_res); + let list_res = list(url).await; + println!("List Response: {:?}", list_res); } pub async fn signin_or_signup(secret_key: &str, homeserver: &str) -> Vec { @@ -76,7 +117,7 @@ pub async fn sign_up(secret_key: &str, homeserver: &str) -> Vec { }; match client.signup(&keypair, &homeserver_public_key).await { - Ok(session) => create_response_vector(false, session.pubky().to_uri_string()), + Ok(session) => create_response_vector(false, session.pubky().to_string()), Err(error) => create_response_vector(true, format!("signup failure: {}", error)), } } @@ -89,7 +130,7 @@ pub async fn sign_in(secret_key: &str) -> Vec { }; match client.signin(&keypair).await { Ok(session) => { - create_response_vector(false, session.pubky().to_uri_string()) + create_response_vector(false, session.pubky().to_string()) }, Err(error) => { create_response_vector(true, format!("Failed to sign in: {}", error)) @@ -169,16 +210,73 @@ pub fn create_response_vector(error: bool, data: String) -> Vec { } } -pub async fn put(url: String, content: String) -> Vec { +pub async fn put(url: &String, content: &String) -> Vec { let client = PUBKY_CLIENT.clone(); - let parsed_url = match Url::parse(&url) { + let trimmed_url = url.trim_end_matches('/'); + let parsed_url = match Url::parse(&trimmed_url) { Ok(url) => url, Err(_) => return create_response_vector(true, "Failed to parse URL".to_string()), }; match client.put(parsed_url, &content.as_bytes()).await { - Ok(_) => create_response_vector(false, "put success".to_string()), + Ok(_) => create_response_vector(false, trimmed_url.to_string()), Err(error) => { create_response_vector(true, format!("Failed to put: {}", error)) } } } + +pub async fn get(url: &String) -> Vec { + let client = PUBKY_CLIENT.clone(); + let trimmed_url = url.trim_end_matches('/'); + + // Parse the URL and return error early if it fails + let parsed_url = match Url::parse(&trimmed_url) { + Ok(url) => url, + Err(_) => return create_response_vector(true, "Failed to parse URL".to_string()), + }; + + // Perform the request and return error early if no data is returned + let result: Option = match client.get(parsed_url).await { + Ok(res) => res, + Err(_) => return create_response_vector(true, "Request failed".to_string()), + }; + + // If there are bytes, attempt to convert to UTF-8 + let bytes = match result { + Some(bytes) => bytes, + None => return create_response_vector(true, "No data returned".to_string()), + }; + + // Try to convert bytes to string and return error if it fails + let string = match str::from_utf8(&bytes) { + Ok(s) => s.to_string(), + Err(_) => return create_response_vector(true, "Invalid UTF-8 sequence".to_string()), + }; + + // If everything is successful, return the formatted response + create_response_vector(false, string) +} + +pub async fn list(url: String) -> Vec { + let client = PUBKY_CLIENT.clone(); + let trimmed_url = url.trim_end_matches('/'); + let parsed_url = match Url::parse(&trimmed_url) { + Ok(url) => url, + Err(_) => return create_response_vector(true, "Failed to parse URL".to_string()), + }; + let list_builder = match client.list(parsed_url) { + Ok(list) => list, + Err(error) => return create_response_vector(true, format!("Failed to list: {}", error)), + }; + // Execute the non-Send part synchronously + let send_future = list_builder.send(); + let send_res = match send_future.await { + Ok(res) => res, + Err(error) => return create_response_vector(true, format!("Failed to send list request: {}", error)) + }; + let json_string = match serde_json::to_string(&send_res) { + Ok(json) => json, + Err(error) => return create_response_vector(true, format!("Failed to serialize JSON: {}", error)), + }; + create_response_vector(false, json_string) +} \ No newline at end of file