fix: add publish and resolve

Adds publish & resolve methods.
Updates example app.
Add npmignore.
Bumps version to 0.3.0.
This commit is contained in:
coreyphillips
2024-09-21 16:35:14 -04:00
parent acde22bd5f
commit 476235399f
27 changed files with 721 additions and 140 deletions

2
.npmignore Normal file
View File

@@ -0,0 +1,2 @@
example/
rust/

View File

@@ -11,6 +11,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import uniffi.pubkymobile.auth import uniffi.pubkymobile.auth
import uniffi.pubkymobile.parseAuthUrl import uniffi.pubkymobile.parseAuthUrl
import uniffi.pubkymobile.publish
import uniffi.pubkymobile.resolve
class PubkyModule(reactContext: ReactApplicationContext) : class PubkyModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) { ReactContextBaseJavaModule(reactContext) {
@@ -51,6 +53,44 @@ class PubkyModule(reactContext: ReactApplicationContext) :
} }
} }
@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)
}
}
}
}
companion object { companion object {
const val NAME = "Pubky" const val NAME = "Pubky"
} }

View File

@@ -385,6 +385,10 @@ internal interface _UniFFILib : Library {
): RustBuffer.ByValue ): RustBuffer.ByValue
fun uniffi_pubkymobile_fn_func_parse_auth_url(`url`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, fun uniffi_pubkymobile_fn_func_parse_auth_url(`url`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus,
): RustBuffer.ByValue ): 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_resolve(`publicKey`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus,
): RustBuffer.ByValue
fun ffi_pubkymobile_rustbuffer_alloc(`size`: Int,_uniffi_out_err: RustCallStatus, fun ffi_pubkymobile_rustbuffer_alloc(`size`: Int,_uniffi_out_err: RustCallStatus,
): RustBuffer.ByValue ): RustBuffer.ByValue
fun ffi_pubkymobile_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue,_uniffi_out_err: RustCallStatus, fun ffi_pubkymobile_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue,_uniffi_out_err: RustCallStatus,
@@ -503,6 +507,10 @@ internal interface _UniFFILib : Library {
): Short ): Short
fun uniffi_pubkymobile_checksum_func_parse_auth_url( fun uniffi_pubkymobile_checksum_func_parse_auth_url(
): Short ): Short
fun uniffi_pubkymobile_checksum_func_publish(
): Short
fun uniffi_pubkymobile_checksum_func_resolve(
): Short
fun ffi_pubkymobile_uniffi_contract_version( fun ffi_pubkymobile_uniffi_contract_version(
): Int ): Int
@@ -526,6 +534,12 @@ private fun uniffiCheckApiChecksums(lib: _UniFFILib) {
if (lib.uniffi_pubkymobile_checksum_func_parse_auth_url() != 29088.toShort()) { if (lib.uniffi_pubkymobile_checksum_func_parse_auth_url() != 29088.toShort()) {
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") 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_resolve() != 18303.toShort()) {
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
}
} }
// Async support // Async support
@@ -628,3 +642,19 @@ fun `parseAuthUrl`(`url`: String): List<String> {
} }
fun `publish`(`recordName`: String, `recordContent`: String, `secretKey`: String): List<String> {
return FfiConverterSequenceString.lift(
rustCall() { _status ->
_UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_publish(FfiConverterString.lower(`recordName`),FfiConverterString.lower(`recordContent`),FfiConverterString.lower(`secretKey`),_status)
})
}
fun `resolve`(`publicKey`: String): List<String> {
return FfiConverterSequenceString.lift(
rustCall() { _status ->
_UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_resolve(FfiConverterString.lower(`publicKey`),_status)
})
}

View File

@@ -1237,7 +1237,7 @@ PODS:
- ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core - ReactCommon/turbomodule/core
- Yoga - Yoga
- react-native-pubky (0.2.3): - react-native-pubky (0.3.0):
- DoubleConversion - DoubleConversion
- glog - glog
- hermes-engine - hermes-engine
@@ -1757,7 +1757,7 @@ SPEC CHECKSUMS:
React-logger: 4072f39df335ca443932e0ccece41fbeb5ca8404 React-logger: 4072f39df335ca443932e0ccece41fbeb5ca8404
React-Mapbuffer: 714f2fae68edcabfc332b754e9fbaa8cfc68fdd4 React-Mapbuffer: 714f2fae68edcabfc332b754e9fbaa8cfc68fdd4
React-microtasksnativemodule: 4943ad8f99be8ccf5a63329fa7d269816609df9e React-microtasksnativemodule: 4943ad8f99be8ccf5a63329fa7d269816609df9e
react-native-pubky: 71108903c771df07c623ed14196f0c49d0d49810 react-native-pubky: 9fd2633ee974bafa9b77e0cd59e2619a0d9d708d
React-nativeconfig: 4a9543185905fe41014c06776bf126083795aed9 React-nativeconfig: 4a9543185905fe41014c06776bf126083795aed9
React-NativeModulesApple: 0506da59fc40d2e1e6e12a233db5e81c46face27 React-NativeModulesApple: 0506da59fc40d2e1e6e12a233db5e81c46face27
React-perflogger: 3bbb82f18e9ac29a1a6931568e99d6305ef4403b React-perflogger: 3bbb82f18e9ac29a1a6931568e99d6305ef4403b

View File

@@ -4,7 +4,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"android": "react-native run-android", "android": "react-native run-android",
"ios": "bundle install && cd ios && pod install && cd ../ && react-native run-ios", "ios": "bundle install && cd ios && pod install && cd ../ && react-native run-ios --simulator \"iPhone 15 Pro\"",
"start": "react-native start", "start": "react-native start",
"build:android": "react-native build-android --extra-params \"--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a\"", "build:android": "react-native build-android --extra-params \"--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a\"",
"install-pods": "bundle install && cd ios && pod install && cd ../", "install-pods": "bundle install && cd ios && pod install && cd ../",

View File

@@ -1,5 +1,10 @@
import { StyleSheet, View, Button } from 'react-native'; import { StyleSheet, View, Button } from 'react-native';
import { auth, parseAuthUrl } from '@synonymdev/react-native-pubky'; import {
auth,
parseAuthUrl,
publish,
resolve,
} from '@synonymdev/react-native-pubky';
export default function App() { export default function App() {
return ( return (
@@ -9,7 +14,7 @@ export default function App() {
onPress={async (): Promise<void> => { onPress={async (): Promise<void> => {
try { try {
const res = await auth( const res = await auth(
'pubkyauth:///?caps=/pub/pubky.app/:rw,/pub/foo.bar/file:r&secret=_K8yj2nS4naHWytpECCX48XhjhGc8KAhlpnuLUiHYBI&relay=http://localhost:52244/', 'pubkyauth:///?caps=/pub/pubky.app/:rw,/pub/foo.bar/file:r&secret=U55XnoH6vsMCpx1pxHtt8fReVg4Brvu9C0gUBuw-Jkw&relay=http://167.86.102.121:4173/',
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
); );
if (res.isErr()) { if (res.isErr()) {
@@ -39,6 +44,42 @@ export default function App() {
} }
}} }}
/> />
<Button
title={'publish'}
onPress={async (): Promise<void> => {
try {
const res = await publish(
'recordnametest',
'recordcontenttest',
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
);
if (res.isErr()) {
console.log(res.error.message);
return;
}
console.log(res.value);
} catch (e) {
console.log(e);
}
}}
/>
<Button
title={'resolve'}
onPress={async (): Promise<void> => {
try {
const res = await resolve(
'z4e8s17cou9qmuwen8p1556jzhf1wktmzo6ijsfnri9c4hnrdfty'
);
if (res.isErr()) {
console.log(res.error.message);
return;
}
console.log(res.value);
} catch (e) {
console.log(e);
}
}}
/>
</View> </View>
); );
} }

View File

@@ -4,22 +4,6 @@
<dict> <dict>
<key>AvailableLibraries</key> <key>AvailableLibraries</key>
<array> <array>
<dict>
<key>BinaryPath</key>
<string>libpubkymobile.a</string>
<key>HeadersPath</key>
<string>Headers</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>libpubkymobile.a</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
<dict> <dict>
<key>BinaryPath</key> <key>BinaryPath</key>
<string>libpubkymobile.a</string> <string>libpubkymobile.a</string>
@@ -38,6 +22,22 @@
<key>SupportedPlatformVariant</key> <key>SupportedPlatformVariant</key>
<string>simulator</string> <string>simulator</string>
</dict> </dict>
<dict>
<key>BinaryPath</key>
<string>libpubkymobile.a</string>
<key>HeadersPath</key>
<string>Headers</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>libpubkymobile.a</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array> </array>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>XFWK</string> <string>XFWK</string>

View File

@@ -67,6 +67,10 @@ RustBuffer uniffi_pubkymobile_fn_func_auth(RustBuffer url, RustBuffer secret_key
); );
RustBuffer uniffi_pubkymobile_fn_func_parse_auth_url(RustBuffer url, RustCallStatus *_Nonnull out_status RustBuffer uniffi_pubkymobile_fn_func_parse_auth_url(RustBuffer url, RustCallStatus *_Nonnull out_status
); );
RustBuffer uniffi_pubkymobile_fn_func_publish(RustBuffer record_name, RustBuffer record_content, RustBuffer secret_key, RustCallStatus *_Nonnull out_status
);
RustBuffer uniffi_pubkymobile_fn_func_resolve(RustBuffer public_key, RustCallStatus *_Nonnull out_status
);
RustBuffer ffi_pubkymobile_rustbuffer_alloc(int32_t size, RustCallStatus *_Nonnull out_status RustBuffer ffi_pubkymobile_rustbuffer_alloc(int32_t size, RustCallStatus *_Nonnull out_status
); );
RustBuffer ffi_pubkymobile_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status RustBuffer ffi_pubkymobile_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status
@@ -186,6 +190,12 @@ uint16_t uniffi_pubkymobile_checksum_func_auth(void
); );
uint16_t uniffi_pubkymobile_checksum_func_parse_auth_url(void uint16_t uniffi_pubkymobile_checksum_func_parse_auth_url(void
);
uint16_t uniffi_pubkymobile_checksum_func_publish(void
);
uint16_t uniffi_pubkymobile_checksum_func_resolve(void
); );
uint32_t ffi_pubkymobile_uniffi_contract_version(void uint32_t ffi_pubkymobile_uniffi_contract_version(void

View File

@@ -67,6 +67,10 @@ RustBuffer uniffi_pubkymobile_fn_func_auth(RustBuffer url, RustBuffer secret_key
); );
RustBuffer uniffi_pubkymobile_fn_func_parse_auth_url(RustBuffer url, RustCallStatus *_Nonnull out_status RustBuffer uniffi_pubkymobile_fn_func_parse_auth_url(RustBuffer url, RustCallStatus *_Nonnull out_status
); );
RustBuffer uniffi_pubkymobile_fn_func_publish(RustBuffer record_name, RustBuffer record_content, RustBuffer secret_key, RustCallStatus *_Nonnull out_status
);
RustBuffer uniffi_pubkymobile_fn_func_resolve(RustBuffer public_key, RustCallStatus *_Nonnull out_status
);
RustBuffer ffi_pubkymobile_rustbuffer_alloc(int32_t size, RustCallStatus *_Nonnull out_status RustBuffer ffi_pubkymobile_rustbuffer_alloc(int32_t size, RustCallStatus *_Nonnull out_status
); );
RustBuffer ffi_pubkymobile_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status RustBuffer ffi_pubkymobile_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status
@@ -186,6 +190,12 @@ uint16_t uniffi_pubkymobile_checksum_func_auth(void
); );
uint16_t uniffi_pubkymobile_checksum_func_parse_auth_url(void uint16_t uniffi_pubkymobile_checksum_func_parse_auth_url(void
);
uint16_t uniffi_pubkymobile_checksum_func_publish(void
);
uint16_t uniffi_pubkymobile_checksum_func_resolve(void
); );
uint32_t ffi_pubkymobile_uniffi_contract_version(void uint32_t ffi_pubkymobile_uniffi_contract_version(void

View File

@@ -11,6 +11,16 @@ RCT_EXTERN_METHOD(parseAuthUrl:(NSString *)url
withResolver:(RCTPromiseResolveBlock)resolve withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject) withRejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(publish:(NSString *)recordName
recordContent:(NSString *)recordContent
secretKey:(NSString *)secretKey
withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(resolve:(NSString *)publicKey
withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject)
+ (BOOL)requiresMainQueueSetup + (BOOL)requiresMainQueueSetup
{ {
return NO; return NO;

View File

@@ -13,6 +13,7 @@ class Pubky: NSObject {
} }
} }
} }
@objc(parseAuthUrl:withResolver:withRejecter:) @objc(parseAuthUrl:withResolver:withRejecter:)
func parseAuthUrl(_ url: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { func parseAuthUrl(_ url: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
Task { Task {
@@ -24,4 +25,28 @@ class Pubky: NSObject {
} }
} }
} }
@objc(publish:recordContent:secretKey:withResolver:withRejecter:)
func publish(recordName: String, recordContent: String, secretKey: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
Task {
do {
let result = react_native_pubky.publish(recordName: recordName, recordContent: recordContent, secretKey: secretKey)
resolve(result)
} catch {
reject("publish Error", "Failed to publish", error)
}
}
}
@objc(resolve:withResolver:withRejecter:)
func resolve(publicKey: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
Task {
do {
let result = react_native_pubky.resolve(publicKey: publicKey)
resolve(result)
} catch {
reject("resolve Error", "Failed to resolve", error)
}
}
}
} }

View File

@@ -376,6 +376,26 @@ public func parseAuthUrl(url: String) -> [String] {
) )
} }
public func publish(recordName: String, recordContent: String, secretKey: String) -> [String] {
return try! FfiConverterSequenceString.lift(
try! rustCall() {
uniffi_pubkymobile_fn_func_publish(
FfiConverterString.lower(recordName),
FfiConverterString.lower(recordContent),
FfiConverterString.lower(secretKey),$0)
}
)
}
public func resolve(publicKey: String) -> [String] {
return try! FfiConverterSequenceString.lift(
try! rustCall() {
uniffi_pubkymobile_fn_func_resolve(
FfiConverterString.lower(publicKey),$0)
}
)
}
private enum InitializationResult { private enum InitializationResult {
case ok case ok
case contractVersionMismatch case contractVersionMismatch
@@ -397,6 +417,12 @@ private var initializationResult: InitializationResult {
if (uniffi_pubkymobile_checksum_func_parse_auth_url() != 29088) { if (uniffi_pubkymobile_checksum_func_parse_auth_url() != 29088) {
return InitializationResult.apiChecksumMismatch return InitializationResult.apiChecksumMismatch
} }
if (uniffi_pubkymobile_checksum_func_publish() != 20156) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_pubkymobile_checksum_func_resolve() != 18303) {
return InitializationResult.apiChecksumMismatch
}
return InitializationResult.ok return InitializationResult.ok
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@synonymdev/react-native-pubky", "name": "@synonymdev/react-native-pubky",
"version": "0.2.3", "version": "0.3.0",
"description": "React Native Implementation of Pubky", "description": "React Native Implementation of Pubky",
"source": "./src/index.tsx", "source": "./src/index.tsx",
"main": "./lib/commonjs/index.js", "main": "./lib/commonjs/index.js",

23
rust/Cargo.lock generated
View File

@@ -386,9 +386,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.7.1" version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
[[package]] [[package]]
name = "camino" name = "camino"
@@ -424,9 +424,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.1.19" version = "1.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d74707dde2ba56f86ae90effb3b43ddd369504387e718014de010cec7959800" checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
@@ -1566,7 +1566,7 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "pkarr" name = "pkarr"
version = "2.2.0" version = "2.2.0"
source = "git+https://github.com/Pubky/pkarr?branch=v3#7a4575f4c60689765ba0567aa27248df2b31b2d4" source = "git+https://github.com/Pubky/pkarr?branch=v3#5b2f8e53735f63521b0d587167609a2cb9b8f2ab"
dependencies = [ dependencies = [
"base32", "base32",
"bytes", "bytes",
@@ -1822,6 +1822,7 @@ dependencies = [
name = "react_native_pubky" name = "react_native_pubky"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"base64 0.21.7",
"hex", "hex",
"pkarr", "pkarr",
"pubky", "pubky",
@@ -2504,9 +2505,9 @@ dependencies = [
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.22.20" version = "0.22.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde",
@@ -2674,9 +2675,9 @@ checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]] [[package]]
name = "unicode-normalization" name = "unicode-normalization"
version = "0.1.23" version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
dependencies = [ dependencies = [
"tinyvec", "tinyvec",
] ]
@@ -2951,9 +2952,9 @@ dependencies = [
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "0.26.5" version = "0.26.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958"
dependencies = [ dependencies = [
"rustls-pki-types", "rustls-pki-types",
] ]

View File

@@ -19,7 +19,8 @@ sha2 = "0.10.8"
serde = { version = "^1.0.209", features = ["derive"] } serde = { version = "^1.0.209", features = ["derive"] }
tokio = "1.40.0" tokio = "1.40.0"
url = "2.5.2" url = "2.5.2"
pkarr = { git = "https://github.com/Pubky/pkarr", branch = "v3", package = "pkarr", features = ["async", "rand"] } pkarr = { git = "https://github.com/Pubky/pkarr", branch = "v3", features = ["async", "rand"] }
pubky = { version = "0.1.0", path = "pubky/pubky" } pubky = { version = "0.1.0", path = "pubky/pubky" }
pubky-common = { version = "0.1.0", path = "pubky/pubky-common" } pubky-common = { version = "0.1.0", path = "pubky/pubky-common" }
pubky_homeserver = { version = "0.1.0", path = "pubky/pubky-homeserver" } pubky_homeserver = { version = "0.1.0", path = "pubky/pubky-homeserver" }
base64 = "0.21.7"

92
rust/src/auth.rs Normal file
View File

@@ -0,0 +1,92 @@
use crate::keypair::get_keypair_from_secret_key;
use crate::{PubkyAuthDetails, Capability};
use crate::utils::create_response_vector;
use std::collections::HashMap;
use pubky::PubkyClient;
use serde_json;
use url::Url;
pub async fn authorize(url: String, secret_key: String) -> Vec<String> {
let client = PubkyClient::testnet();
let keypair = match get_keypair_from_secret_key(&secret_key) {
Ok(keypair) => keypair,
Err(error) => return create_response_vector(true, error),
};
// const HOMESERVER: &'static str = "8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo";
// const URL: &'static str = "http://localhost:6287?relay=http://demo.httprelay.io/link";
// match client.signin(&keypair).await {
// Ok(_) => {}, // Signin successful, continue to send_auth_token
// Err(_) => {
// match client.signup(&keypair, &PublicKey::try_from(HOMESERVER).unwrap()).await {
// Ok(_) => {}, // Signup successful, continue to send_auth_token
// Err(error) => return create_response_vector(true, format!("Failed to signup: {}", error)),
// }
// }
// }
let parsed_url = match Url::parse(&url) {
Ok(url) => url,
Err(_) => return create_response_vector(true, "Failed to parse URL".to_string()),
};
match client.send_auth_token(&keypair, parsed_url).await {
Ok(_) => create_response_vector(false, "send_auth_token success".to_string()),
Err(error) => create_response_vector(true, format!("send_auth_token failure: {}", error)),
}
}
pub fn pubky_auth_details_to_json(details: &PubkyAuthDetails) -> Result<String, String> {
serde_json::to_string(details).map_err(|_| "Error serializing to JSON".to_string())
}
pub fn parse_pubky_auth_url(url_str: &str) -> Result<PubkyAuthDetails, String> {
let url = Url::parse(url_str).map_err(|_| "Invalid URL".to_string())?;
if url.scheme() != "pubkyauth" {
return Err("Invalid scheme, expected 'pubkyauth'".to_string());
}
// Collect query pairs into a HashMap for efficient access
let query_params: HashMap<_, _> = url.query_pairs().into_owned().collect();
let relay = query_params
.get("relay")
.cloned()
.ok_or_else(|| "Missing relay".to_string())?;
let capabilities_str = query_params
.get("capabilities")
.or_else(|| query_params.get("caps"))
.cloned()
.ok_or_else(|| "Missing capabilities".to_string())?;
let secret = query_params
.get("secret")
.cloned()
.ok_or_else(|| "Missing secret".to_string())?;
// Parse capabilities
let capabilities = capabilities_str
.split(',')
.map(|capability| {
let mut parts = capability.splitn(2, ':');
let path = parts
.next()
.ok_or_else(|| format!("Invalid capability format in '{}'", capability))?;
let permission = parts
.next()
.ok_or_else(|| format!("Invalid capability format in '{}'", capability))?;
Ok(Capability {
path: path.to_string(),
permission: permission.to_string(),
})
})
.collect::<Result<Vec<_>, String>>()?;
Ok(PubkyAuthDetails {
relay,
capabilities,
secret,
})
}

17
rust/src/keypair.rs Normal file
View File

@@ -0,0 +1,17 @@
use pkarr::Keypair;
pub fn get_keypair_from_secret_key(secret_key: &str) -> Result<Keypair, String> {
let bytes = match hex::decode(&secret_key) {
Ok(bytes) => bytes,
Err(_) => return Err("Failed to decode secret key".to_string())
};
let secret_key_bytes: [u8; 32] = match bytes.try_into() {
Ok(secret_key) => secret_key,
Err(_) => {
return Err("Failed to convert secret key to 32-byte array".to_string());
}
};
Ok(Keypair::from_secret_key(&secret_key_bytes))
}

View File

@@ -1,48 +1,138 @@
mod types;
mod keypair;
mod auth;
mod utils;
pub use types::*;
pub use keypair::*;
pub use auth::*;
pub use utils::*;
uniffi::setup_scaffolding!(); uniffi::setup_scaffolding!();
use std::collections::HashMap; use std::collections::HashMap;
use base64::Engine;
use base64::engine::general_purpose;
use pubky::PubkyClient; use pubky::PubkyClient;
use hex; use hex;
use serde::Serialize; use serde::Serialize;
use url::Url; use url::Url;
use tokio; use tokio;
use pkarr::{PkarrClient, SignedPacket, Keypair, dns, PublicKey};
use pkarr::dns::rdata::RData;
use pkarr::dns::ResourceRecord;
use serde_json::json;
use utils::*;
async fn authorize(url: String, secret_key: String) -> Vec<String> { #[uniffi::export]
let bytes = match hex::decode(&secret_key) { fn resolve(public_key: String) -> Vec<String> {
Ok(bytes) => bytes, let public_key = match public_key.as_str().try_into() {
Err(_) => return create_response_vector(true, "Failed to decode secret key".to_string()) Ok(key) => key,
Err(e) => return create_response_vector(true, format!("Invalid zbase32 encoded key: {}", e)),
};
let client = match PkarrClient::builder().build() {
Ok(client) => client,
Err(e) => return create_response_vector(true, format!("Failed to build PkarrClient: {}", e)),
}; };
let secret_key_bytes: [u8; 32] = match bytes.try_into() { match client.resolve(&public_key) {
Ok(secret_key) => secret_key, Ok(Some(signed_packet)) => {
Err(_) => { // Collect references to ResourceRecords from the signed packet's answers
return create_response_vector(true, "Failed to convert secret key to 32-byte array".to_string()); 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<serde_json::Value> = 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();
let bytes = signed_packet.as_bytes();
let public_key = &bytes[..32];
let signature = &bytes[32..96];
let timestamp = u64::from_be_bytes(match bytes[96..104].try_into() {
Ok(tsbytes) => tsbytes,
Err(_) => return create_response_vector(true, "Failed to convert timestamp bytes".to_string())
});
let dns_packet = &bytes[104..];
let json_obj = json!({
"public_key": general_purpose::STANDARD.encode(public_key),
"signature": general_purpose::STANDARD.encode(signature),
"timestamp": timestamp,
"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");
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]
fn publish(record_name: String, record_content: String, secret_key: String) -> Vec<String> {
let client = match PkarrClient::builder().build() {
Ok(client) => client,
Err(e) => return create_response_vector(true, format!("Failed to build PkarrClient: {}", e)),
};
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 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 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 client = PubkyClient::testnet(); packet.answers.push(dns::ResourceRecord::new(
let keypair = pkarr::Keypair::from_secret_key(&secret_key_bytes); dns_name,
dns::CLASS::IN,
30,
txt_record,
));
// const HOMESERVER: &'static str = "8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo"; match SignedPacket::from_packet(&keypair, &packet) {
// const URL: &'static str = "http://localhost:6287?relay=http://demo.httprelay.io/link"; Ok(signed_packet) => {
// match client.signin(&keypair).await { match client.publish(&signed_packet) {
// Ok(_) => {}, // Signin successful, continue to send_auth_token Ok(()) => {
// Err(_) => { create_response_vector(false, keypair.public_key().to_string())
// match client.signup(&keypair, &PublicKey::try_from(HOMESERVER).unwrap()).await { }
// Ok(_) => {}, // Signup successful, continue to send_auth_token Err(e) => {
// Err(error) => return create_response_vector(true, format!("Failed to signup: {}", error)), create_response_vector(true, format!("Failed to publish: {}", e))
// } }
// } }
// } }
Err(e) => {
let parsed_url = match Url::parse(&url) { create_response_vector(true, format!("Failed to create signed packet: {}", e))
Ok(url) => url, }
Err(_) => return create_response_vector(true, "Failed to parse URL".to_string()),
};
match client.send_auth_token(&keypair, parsed_url).await {
Ok(_) => create_response_vector(false, "send_auth_token success".to_string()),
Err(error) => create_response_vector(true, format!("send_auth_token failure: {}", error)),
} }
} }
@@ -64,79 +154,3 @@ fn parse_auth_url(url: String) -> Vec<String> {
Err(error) => create_response_vector(true, error), Err(error) => create_response_vector(true, error),
} }
} }
#[derive(Debug, Serialize)]
struct Capability {
path: String,
permission: String,
}
#[derive(Debug, Serialize)]
struct PubkyAuthDetails {
relay: String,
capabilities: Vec<Capability>,
secret: String,
}
fn pubky_auth_details_to_json(details: &PubkyAuthDetails) -> Result<String, String> {
serde_json::to_string(details).map_err(|_| "Error serializing to JSON".to_string())
}
fn parse_pubky_auth_url(url_str: &str) -> Result<PubkyAuthDetails, String> {
let url = Url::parse(url_str).map_err(|_| "Invalid URL".to_string())?;
if url.scheme() != "pubkyauth" {
return Err("Invalid scheme, expected 'pubkyauth'".to_string());
}
// Collect query pairs into a HashMap for efficient access
let query_params: HashMap<_, _> = url.query_pairs().into_owned().collect();
let relay = query_params
.get("relay")
.cloned()
.ok_or_else(|| "Missing relay".to_string())?;
let capabilities_str = query_params
.get("capabilities")
.or_else(|| query_params.get("caps"))
.cloned()
.ok_or_else(|| "Missing capabilities".to_string())?;
let secret = query_params
.get("secret")
.cloned()
.ok_or_else(|| "Missing secret".to_string())?;
// Parse capabilities
let capabilities = capabilities_str
.split(',')
.map(|capability| {
let mut parts = capability.splitn(2, ':');
let path = parts
.next()
.ok_or_else(|| format!("Invalid capability format in '{}'", capability))?;
let permission = parts
.next()
.ok_or_else(|| format!("Invalid capability format in '{}'", capability))?;
Ok(Capability {
path: path.to_string(),
permission: permission.to_string(),
})
})
.collect::<Result<Vec<_>, String>>()?;
Ok(PubkyAuthDetails {
relay,
capabilities,
secret,
})
}
fn create_response_vector(error: bool, data: String) -> Vec<String> {
if error {
vec!["error".to_string(), data]
} else {
vec!["success".to_string(), data]
}
}

14
rust/src/types.rs Normal file
View File

@@ -0,0 +1,14 @@
use serde::Serialize;
#[derive(Debug, Serialize)]
pub struct Capability {
pub path: String,
pub permission: String,
}
#[derive(Debug, Serialize)]
pub struct PubkyAuthDetails {
pub relay: String,
pub capabilities: Vec<Capability>,
pub secret: String,
}

203
rust/src/utils.rs Normal file
View File

@@ -0,0 +1,203 @@
use std::error::Error;
use std::net::{Ipv4Addr, Ipv6Addr};
use serde_json::json;
use base64::{engine::general_purpose, Engine};
use pkarr::dns::rdata::RData;
use pkarr::dns::ResourceRecord;
pub fn create_response_vector(error: bool, data: String) -> Vec<String> {
if error {
vec!["error".to_string(), data]
} else {
vec!["success".to_string(), data]
}
}
pub fn extract_rdata_for_json(record: &ResourceRecord) -> serde_json::Value {
match &record.rdata {
RData::TXT(txt) => {
let attributes = txt.attributes();
let strings: Vec<String> = attributes.into_iter()
.map(|(key, value)| {
match value {
Some(v) => format!("{}={}", key, v),
None => key,
}
})
.collect();
json!({
"type": "TXT",
"strings": strings
})
},
RData::A(a) => {
let ipv4 = Ipv4Addr::from(a.address);
json!({
"type": "A",
"address": ipv4.to_string()
})
},
RData::AAAA(aaaa) => {
let ipv6 = Ipv6Addr::from(aaaa.address);
json!({
"type": "AAAA",
"address": ipv6.to_string()
})
},
RData::AFSDB(afsdb) => {
json!({
"type": "AFSDB",
"subtype": afsdb.subtype,
"hostname": afsdb.hostname.to_string()
})
},
RData::CAA(caa) => {
json!({
"type": "CAA",
"flag": caa.flag,
"tag": caa.tag.to_string(),
"value": caa.value.to_string()
})
},
RData::HINFO(hinfo) => {
json!({
"type": "HINFO",
"cpu": hinfo.cpu.to_string(),
"os": hinfo.os.to_string()
})
},
RData::ISDN(isdn) => {
json!({
"type": "ISDN",
"address": isdn.address.to_string(),
"sa": isdn.sa.to_string()
})
},
RData::LOC(loc) => {
json!({
"type": "LOC",
"version": loc.version,
"size": loc.size,
"horizontal_precision": loc.horizontal_precision,
"vertical_precision": loc.vertical_precision,
"latitude": loc.latitude,
"longitude": loc.longitude,
"altitude": loc.altitude
})
},
RData::MINFO(minfo) => {
json!({
"type": "MINFO",
"rmailbox": minfo.rmailbox.to_string(),
"emailbox": minfo.emailbox.to_string()
})
},
RData::MX(mx) => {
json!({
"type": "MX",
"preference": mx.preference,
"exchange": mx.exchange.to_string()
})
},
RData::NAPTR(naptr) => {
json!({
"type": "NAPTR",
"order": naptr.order,
"preference": naptr.preference,
"flags": naptr.flags.to_string(),
"services": naptr.services.to_string(),
"regexp": naptr.regexp.to_string(),
"replacement": naptr.replacement.to_string()
})
},
RData::NULL(_, null_record) => {
json!({
"type": "NULL",
"data": base64::encode(null_record.get_data())
})
},
RData::OPT(opt) => {
json!({
"type": "OPT",
"udp_packet_size": opt.udp_packet_size,
"version": opt.version,
"opt_codes": opt.opt_codes.iter().map(|code| {
json!({
"code": code.code,
"data": base64::encode(&code.data)
})
}).collect::<Vec<_>>()
})
},
RData::RouteThrough(rt) => {
json!({
"type": "RT",
"preference": rt.preference,
"intermediate_host": rt.intermediate_host.to_string()
})
},
RData::RP(rp) => {
json!({
"type": "RP",
"mbox": rp.mbox.to_string(),
"txt": rp.txt.to_string()
})
},
RData::SOA(soa) => {
json!({
"type": "SOA",
"mname": soa.mname.to_string(),
"rname": soa.rname.to_string(),
"serial": soa.serial,
"refresh": soa.refresh,
"retry": soa.retry,
"expire": soa.expire,
"minimum": soa.minimum
})
},
RData::SRV(srv) => {
json!({
"type": "SRV",
"priority": srv.priority,
"weight": srv.weight,
"port": srv.port,
"target": srv.target.to_string()
})
},
RData::SVCB(svcb) => {
let mut params = serde_json::Map::new();
for (key, value) in svcb.iter_params() {
params.insert(key.to_string(), json!(base64::encode(value)));
}
json!({
"type": "SVCB",
"priority": svcb.priority,
"target": svcb.target.to_string(),
"params": params
})
},
RData::WKS(wks) => {
json!({
"type": "WKS",
"address": Ipv4Addr::from(wks.address).to_string(),
"protocol": wks.protocol,
"bit_map": base64::encode(&wks.bit_map)
})
},
_ => json!({
"type": format!("{:?}", record.rdata.type_code()),
"data": "Unhandled record type"
}),
}
}
pub fn resource_record_to_json(record: &ResourceRecord) -> Result<serde_json::Value, Box<dyn Error>> {
Ok(json!({
"name": record.name.to_string(),
"class": format!("{:?}", record.class),
"ttl": record.ttl,
"rdata": extract_rdata_for_json(record),
"cache_flush": record.cache_flush
}))
}

View File

@@ -54,3 +54,48 @@ export async function parseAuthUrl(
return err(JSON.stringify(e)); return err(JSON.stringify(e));
} }
} }
export async function publish(
recordName: string,
recordContent: string,
secretKey: string
): Promise<Result<string[]>> {
try {
const res = await Pubky.publish(recordName, recordContent, secretKey);
if (res[0] === 'error') {
return err(res[1]);
}
return ok(res[1]);
} catch (e) {
return err(JSON.stringify(e));
}
}
interface ITxt {
cache_flush: boolean;
class: string;
name: string;
rdata: {
strings: string[];
type: string;
};
ttl: number;
}
interface IDNSPacket {
dns_packet: string;
public_key: string;
records: ITxt[];
signature: string;
timestamp: number;
}
export async function resolve(publicKey: string): Promise<Result<IDNSPacket>> {
try {
const res = await Pubky.resolve(publicKey);
if (res[0] === 'error') {
return err(res[1]);
}
return ok(JSON.parse(res[1]));
} catch (e) {
return err(JSON.stringify(e));
}
}