feat: implement parseAuthUrl

Adds & Implements parseAuthUrl.
Updates README.md.
Bumps version to 0.2.0.
This commit is contained in:
coreyphillips
2024-09-15 21:17:10 -04:00
parent b5185fe7ba
commit c2a699d2d2
19 changed files with 221 additions and 41 deletions

View File

@@ -11,8 +11,8 @@ npm install @synonymdev/react-native-pubky
## Implementation Status
### Implemented Methods
- [x] [auth](#auth): Authentication functionality.
- [x] [parseAuthUrl](#parseAuthUrl): Method to decode an authUrl.
### Methods to be Implemented
- [ ] parseAuthUrl: Method to decode an authUrl.
- [ ] publish: Functionality to publish content.
- [ ] resolve: Functionality to resolve content.
- [ ] signIn: Functionality to sign in.
@@ -31,6 +31,18 @@ if (authRes.isErr()) {
}
console.log(authRes.value);
```
### <a name="parseAuthUrl"></a>parseAuthUrl
```js
import { parseAuthUrl } from '@synonymdev/react-native-pubky';
const pubkyAuthUrl = 'pubkyauth:///?relay=https://demo.httprelay.io/link&capabilities=/pub/pubky.app:rw,/pub/example.com/nested:rw&secret=FyzJ3gJ1W7boyFZC1Do9fYrRmDNgCLNRwEu_gaBgPUA';
const parseRes = await parseAuthUrl(pubkyAuthUrl);
if (parseRes.isErr()) {
console.log(parseRes.error.message);
return;
}
console.log(parseRes.value);
```
## Local Installation

View File

@@ -10,6 +10,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import uniffi.pubkymobile.auth
import uniffi.pubkymobile.parseAuthUrl
class PubkyModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
@@ -37,6 +38,19 @@ class PubkyModule(reactContext: ReactApplicationContext) :
}
}
@ReactMethod
fun parseAuthUrl(url: String, promise: Promise) {
try {
val result = parseAuthUrl(url)
val array = Arguments.createArray().apply {
result.forEach { pushString(it) }
}
promise.resolve(array)
} catch (e: Exception) {
promise.reject("Error", e.message)
}
}
companion object {
const val NAME = "Pubky"
}

View File

@@ -387,6 +387,8 @@ internal interface _UniFFILib : Library {
fun uniffi_pubkymobile_fn_func_auth(`url`: RustBuffer.ByValue,`secretKey`: RustBuffer.ByValue,
): Pointer
fun uniffi_pubkymobile_fn_func_parse_auth_url(`url`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus,
): RustBuffer.ByValue
fun ffi_pubkymobile_rustbuffer_alloc(`size`: Int,_uniffi_out_err: RustCallStatus,
): RustBuffer.ByValue
fun ffi_pubkymobile_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue,_uniffi_out_err: RustCallStatus,
@@ -503,6 +505,8 @@ internal interface _UniFFILib : Library {
): Unit
fun uniffi_pubkymobile_checksum_func_auth(
): Short
fun uniffi_pubkymobile_checksum_func_parse_auth_url(
): Short
fun ffi_pubkymobile_uniffi_contract_version(
): Int
@@ -523,6 +527,9 @@ private fun uniffiCheckApiChecksums(lib: _UniFFILib) {
if (lib.uniffi_pubkymobile_checksum_func_auth() != 46918.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")
}
}
// Async support
@@ -671,3 +678,11 @@ suspend fun `auth`(`url`: String, `secretKey`: String) : List<String> {
)
}
fun `parseAuthUrl`(`url`: String): List<String> {
return FfiConverterSequenceString.lift(
rustCall() { _status ->
_UniFFILib.INSTANCE.uniffi_pubkymobile_fn_func_parse_auth_url(FfiConverterString.lower(`url`),_status)
})
}

View File

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

View File

@@ -1,33 +1,9 @@
import { StyleSheet, View, Button } from 'react-native';
import { auth } from '@synonymdev/react-native-pubky';
import { getAddress } from 'react-native-address-generator';
import { auth, parseAuthUrl } from '@synonymdev/react-native-pubky';
export default function App() {
return (
<View style={styles.container}>
<Button
title={'getAddress'}
onPress={async (): Promise<void> => {
const mnemonic =
'lazy rally chat way pet outside flame cup oval absurd innocent balcony';
const passphrase = 'passphrase';
const path = "m/84'/1'/0'/0/0";
const network = 'testnet';
const getAddressRes = await getAddress({
mnemonic,
path,
network,
passphrase,
});
if (getAddressRes.isErr()) {
console.log(getAddressRes.error.message);
return;
}
console.log(getAddressRes.value);
}}
/>
<Button
title={'auth'}
onPress={async (): Promise<void> => {
@@ -46,6 +22,23 @@ export default function App() {
}
}}
/>
<Button
title={'parseAuthUrl'}
onPress={async (): Promise<void> => {
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'
);
if (res.isErr()) {
console.log(res.error.message);
return;
}
console.log(res.value);
} catch (e) {
console.log(e);
}
}}
/>
</View>
);
}

View File

@@ -65,6 +65,8 @@ typedef void (*UniFfiRustFutureContinuation)(void * _Nonnull, int8_t);
// Scaffolding functions
void* _Nonnull 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 ffi_pubkymobile_rustbuffer_alloc(int32_t size, RustCallStatus *_Nonnull out_status
);
RustBuffer ffi_pubkymobile_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status
@@ -181,6 +183,9 @@ void ffi_pubkymobile_rust_future_complete_void(void* _Nonnull handle, RustCallSt
);
uint16_t uniffi_pubkymobile_checksum_func_auth(void
);
uint16_t uniffi_pubkymobile_checksum_func_parse_auth_url(void
);
uint32_t ffi_pubkymobile_uniffi_contract_version(void

View File

@@ -65,6 +65,8 @@ typedef void (*UniFfiRustFutureContinuation)(void * _Nonnull, int8_t);
// Scaffolding functions
void* _Nonnull 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 ffi_pubkymobile_rustbuffer_alloc(int32_t size, RustCallStatus *_Nonnull out_status
);
RustBuffer ffi_pubkymobile_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status
@@ -181,6 +183,9 @@ void ffi_pubkymobile_rust_future_complete_void(void* _Nonnull handle, RustCallSt
);
uint16_t uniffi_pubkymobile_checksum_func_auth(void
);
uint16_t uniffi_pubkymobile_checksum_func_parse_auth_url(void
);
uint32_t ffi_pubkymobile_uniffi_contract_version(void

View File

@@ -7,6 +7,10 @@ RCT_EXTERN_METHOD(auth:(NSString *)url
withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(parseAuthUrl:(NSString *)url
withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject)
+ (BOOL)requiresMainQueueSetup
{
return NO;

View File

@@ -2,15 +2,26 @@ import Foundation
@objc(Pubky)
class Pubky: NSObject {
@objc(auth:secretKey:withResolver:withRejecter:)
func auth(_ url: String, secretKey: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
Task {
do {
let result = try await react_native_pubky.auth(url: url, secretKey: secretKey)
resolve(result)
} catch {
reject("auth Error", "Failed to auth", error)
}
}
}
@objc(auth:secretKey:withResolver:withRejecter:)
func auth(_ url: String, secretKey: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
Task {
do {
let result = try await react_native_pubky.auth(url: url, secretKey: secretKey)
resolve(result)
} catch {
reject("auth Error", "Failed to auth", error)
}
}
}
@objc(parseAuthUrl:withResolver:withRejecter:)
func parseAuthUrl(_ url: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
Task {
do {
let result = react_native_pubky.parseAuthUrl(url: url)
resolve(result)
} catch {
reject("parseAuthUrl Error", "Failed to parse auth url", error)
}
}
}
}

View File

@@ -438,6 +438,15 @@ public func auth(url: String, secretKey: String) async -> [String] {
public func parseAuthUrl(url: String) -> [String] {
return try! FfiConverterSequenceString.lift(
try! rustCall() {
uniffi_pubkymobile_fn_func_parse_auth_url(
FfiConverterString.lower(url),$0)
}
)
}
private enum InitializationResult {
case ok
case contractVersionMismatch
@@ -456,6 +465,9 @@ private var initializationResult: InitializationResult {
if (uniffi_pubkymobile_checksum_func_auth() != 46918) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_pubkymobile_checksum_func_parse_auth_url() != 29088) {
return InitializationResult.apiChecksumMismatch
}
uniffiInitContinuationCallback()
return InitializationResult.ok

View File

@@ -1,6 +1,6 @@
{
"name": "@synonymdev/react-native-pubky",
"version": "0.1.1",
"version": "0.2.0",
"description": "React Native Implementation of Pubky",
"source": "./src/index.tsx",
"main": "./lib/commonjs/index.js",
@@ -49,7 +49,8 @@
"cargo-build": "cd rust && cargo build && cd pubky && cargo build && cd pubky && cargo build && cd ../ && cd pubky-common && cargo build && cd ../ && cd pubky-homeserver && cargo build && cd ../../../",
"update-bindings:ios": "npm run cargo-build && node setup-ios-bindings.js && npm run reinstall",
"update-bindings:android": "npm run cargo-build && node setup-android-bindings.js && npm run reinstall",
"update-bindings": "npm run reinstall && npm run cargo-build && npm run update-bindings:ios && npm run update-bindings:android"
"update-bindings": "npm run reinstall && npm run cargo-build && npm run update-bindings:ios && npm run update-bindings:android",
"rebuild": "rm -rf node_modules && cd example && rm -rf node_modules && cd ios && rm -rf Pods Podfile.lock build && cd ../../ && npm run cargo-build && yarn install && npm run update-bindings && cd example && yarn install && bundle install && cd ios && pod install && cd ../ && yarn build:ios && yarn ios"
},
"keywords": [
"pubky",

View File

@@ -1,6 +1,9 @@
uniffi::setup_scaffolding!();
use std::collections::HashMap;
use pubky::PubkyClient;
use hex;
use serde::Serialize;
use url::Url;
#[uniffi::export]
@@ -31,6 +34,85 @@ async fn auth(url: String, secret_key: String) -> Vec<String> {
}
}
#[uniffi::export]
fn parse_auth_url(url: String) -> Vec<String> {
let parsed_details = match parse_pubky_auth_url(&url) {
Ok(details) => details,
Err(error) => return create_response_vector(true, error),
};
match pubky_auth_details_to_json(&parsed_details) {
Ok(json) => create_response_vector(false, json),
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")
.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]

View File

@@ -28,3 +28,29 @@ export async function auth(
}
return ok(res[1]);
}
type Capability = {
path: string;
permission: string;
};
type PubkyAuthDetails = {
relay: string;
capabilities: Capability[];
secret: string;
};
export async function parseAuthUrl(
url: string
): Promise<Result<PubkyAuthDetails>> {
try {
const res = await Pubky.parseAuthUrl(url);
if (res[0] === 'error') {
return err(res[1]);
}
const parsed = JSON.parse(res[1]);
return ok(parsed);
} catch (e) {
return err(JSON.stringify(e));
}
}