diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cfe2bb4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,82 @@ +# OSX +# +.DS_Store + +# XDE +.expo/ + +# VSCode +.vscode/ +jsconfig.json + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# Android/IJ +# +.classpath +.cxx +.gradle +.idea +.project +.settings +local.properties +android.iml + +# Cocoapods +# +example/ios/Pods + +# Ruby +example/vendor/ + +# node.js +# +node_modules/ +npm-debug.log +yarn-debug.log +yarn-error.log + +# BUCK +buck-out/ +\.buckd/ +android/app/libs +android/keystores/debug.keystore + +# Yarn +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Expo +.expo/ + +# Turborepo +.turbo/ + +# generated by bob +lib/ + +# React Native Codegen +ios/generated +android/generated diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..463c7c8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,120 @@ +# Contributing + +Contributions are always welcome, no matter how large or small! + +## Development workflow + +This project is a monorepo managed using [Yarn workspaces](https://yarnpkg.com/features/workspaces). It contains the following packages: + +- The library package in the root directory. +- An example app in the `example/` directory. + +To get started with the project, run `yarn` in the root directory to install the required dependencies for each package: + +```sh +yarn +``` + +> Since the project relies on Yarn workspaces, you cannot use [`npm`](https://github.com/npm/cli) for development. + +The [example app](/example/) demonstrates usage of the library. You need to run it to test any changes you make. + +It is configured to use the local version of the library, so any changes you make to the library's source code will be reflected in the example app. Changes to the library's JavaScript code will be reflected in the example app without a rebuild, but native code changes will require a rebuild of the example app. + +If you want to use Android Studio or XCode to edit the native code, you can open the `example/android` or `example/ios` directories respectively in those editors. To edit the Objective-C or Swift files, open `example/ios/PubkyExample.xcworkspace` in XCode and find the source files at `Pods > Development Pods > react-native-pubky`. + +To edit the Java or Kotlin files, open `example/android` in Android studio and find the source files at `react-native-pubky` under `Android`. + +You can use various commands from the root directory to work with the project. + +To start the packager: + +```sh +yarn example start +``` + +To run the example app on Android: + +```sh +yarn example android +``` + +To run the example app on iOS: + +```sh +yarn example ios +``` + +Make sure your code passes TypeScript and ESLint. Run the following to verify: + +```sh +yarn typecheck +yarn lint +``` + +To fix formatting errors, run the following: + +```sh +yarn lint --fix +``` + +Remember to add tests for your change if possible. Run the unit tests by: + +```sh +yarn test +``` + +### Commit message convention + +We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages: + +- `fix`: bug fixes, e.g. fix crash due to deprecated method. +- `feat`: new features, e.g. add new method to the module. +- `refactor`: code refactor, e.g. migrate from class components to hooks. +- `docs`: changes into documentation, e.g. add usage example for the module.. +- `test`: adding or updating tests, e.g. add integration tests using detox. +- `chore`: tooling changes, e.g. change CI config. + +Our pre-commit hooks verify that your commit message matches this format when committing. + +### Linting and tests + +[ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/) + +We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing. + +Our pre-commit hooks verify that the linter and tests pass when committing. + +### Publishing to npm + +We use [release-it](https://github.com/release-it/release-it) to make it easier to publish new versions. It handles common tasks like bumping version based on semver, creating tags and releases etc. + +To publish new versions, run the following: + +```sh +yarn release +``` + +### Scripts + +The `package.json` file contains various scripts for common tasks: + +- `yarn`: setup project by installing dependencies. +- `yarn typecheck`: type-check files with TypeScript. +- `yarn lint`: lint files with ESLint. +- `yarn test`: run unit tests with Jest. +- `yarn example start`: start the Metro server for the example app. +- `yarn example android`: run the example app on Android. +- `yarn example ios`: run the example app on iOS. + +### Sending a pull request + +> **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github). + +When you're sending a pull request: + +- Prefer small pull requests focused on one change. +- Verify that linters and tests are passing. +- Review the documentation to make sure it looks good. +- Follow the pull request template when opening a pull request. +- For pull requests that change the API or implementation, discuss with maintainers first by opening an issue. diff --git a/LICENSE b/LICENSE index 84873c7..68f06bb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,6 @@ MIT License -Copyright (c) 2024 Pubky - +Copyright (c) 2024 pubky Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/README.md b/README.md index f7fe299..2e67359 100644 --- a/README.md +++ b/README.md @@ -1 +1,45 @@ -# react-native-pubky \ No newline at end of file +# react-native-pubky + +React Native implementation of [pubky](https://github.com/pubky/pubky) + +## Installation + +```sh +npm install react-native-pubky +``` + +## Usage + +```js +import { auth } from 'react-native-pubky'; + +const authRes = await auth("pubkyAuthUrl", "secretKey"); +if (authRes.isErr()) { + console.log(authRes.error.message); + return; +} +console.log(authRes.value); +``` + +## Update Bindings + +After making changes to any of the Rust files, the bindings will need to be updated. To do this, run the following command: + +```sh +npm run update-bindings +``` + +Finally, ensure that `PubkyModule.kt`, `Pubky.swift`, `Pubky.mm` & `src/index.tsx` are updated accordingly based on the changes made to the Rust files. + +## License + +MIT + +--- + +## Resources + +- Project created with: [create-react-native-library](https://github.com/callstack/react-native-builder-bob) +- [Building an Android App with Rust Using UniFFI](https://forgen.tech/en/blog/post/building-an-android-app-with-rust-using-uniffi) +- [Building an iOS App with Rust Using UniFFI](https://forgen.tech/en/blog/post/building-an-ios-app-with-rust-using-uniffi) + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..a6f84c9 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,101 @@ +buildscript { + // Buildscript is evaluated before everything else so we can't use getExtOrDefault + def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["Pubky_kotlinVersion"] + + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "com.android.tools.build:gradle:7.2.1" + // noinspection DifferentKotlinGradleVersion + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +def reactNativeArchitectures() { + def value = rootProject.getProperties().get("reactNativeArchitectures") + return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] +} + +def isNewArchitectureEnabled() { + return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" +} + +apply plugin: "com.android.library" +apply plugin: "kotlin-android" + +if (isNewArchitectureEnabled()) { + apply plugin: "com.facebook.react" +} + +def getExtOrDefault(name) { + return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["Pubky_" + name] +} + +def getExtOrIntegerDefault(name) { + return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Pubky_" + name]).toInteger() +} + +def supportsNamespace() { + def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.') + def major = parsed[0].toInteger() + def minor = parsed[1].toInteger() + + // Namespace support was added in 7.3.0 + return (major == 7 && minor >= 3) || major >= 8 +} + +android { + if (supportsNamespace()) { + namespace "com.pubky" + + sourceSets { + main { + manifest.srcFile "src/main/AndroidManifestNew.xml" + } + } + } + + compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") + + defaultConfig { + minSdkVersion getExtOrIntegerDefault("minSdkVersion") + targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") + + } + + buildTypes { + release { + minifyEnabled false + } + } + + lintOptions { + disable "GradleCompatible" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +repositories { + mavenCentral() + google() +} + +def kotlin_version = getExtOrDefault("kotlinVersion") + +dependencies { + // For < 0.71, this will be from the local maven repo + // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin + //noinspection GradleDynamicVersion + implementation "com.facebook.react:react-native:+" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation("net.java.dev.jna:jna:5.7.0@aar") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") +} + diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..753adbb --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,5 @@ +Pubky_kotlinVersion=1.7.0 +Pubky_minSdkVersion=21 +Pubky_targetSdkVersion=31 +Pubky_compileSdkVersion=31 +Pubky_ndkversion=21.4.7075529 diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6a50652 --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/android/src/main/AndroidManifestNew.xml b/android/src/main/AndroidManifestNew.xml new file mode 100644 index 0000000..a2f47b6 --- /dev/null +++ b/android/src/main/AndroidManifestNew.xml @@ -0,0 +1,2 @@ + + diff --git a/android/src/main/java/com/pubky/PubkyModule.kt b/android/src/main/java/com/pubky/PubkyModule.kt new file mode 100644 index 0000000..9462880 --- /dev/null +++ b/android/src/main/java/com/pubky/PubkyModule.kt @@ -0,0 +1,43 @@ +package com.pubky + +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.Promise +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import uniffi.mobile.auth + +class PubkyModule(reactContext: ReactApplicationContext) : + ReactContextBaseJavaModule(reactContext) { + + override fun getName(): String { + return NAME + } + +@ReactMethod +fun auth(url: String, secretKey: String, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val result = auth(url, secretKey) + val array = Arguments.createArray().apply { + result.forEach { pushString(it) } + } + withContext(Dispatchers.Main) { + promise.resolve(array) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("Error", e.message) + } + } + } +} + + companion object { + const val NAME = "Pubky" + } +} diff --git a/android/src/main/java/com/pubky/PubkyPackage.kt b/android/src/main/java/com/pubky/PubkyPackage.kt new file mode 100644 index 0000000..79f598d --- /dev/null +++ b/android/src/main/java/com/pubky/PubkyPackage.kt @@ -0,0 +1,17 @@ +package com.pubky + +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager + + +class PubkyPackage : ReactPackage { + override fun createNativeModules(reactContext: ReactApplicationContext): List { + return listOf(PubkyModule(reactContext)) + } + + override fun createViewManagers(reactContext: ReactApplicationContext): List> { + return emptyList() + } +} diff --git a/android/src/main/java/com/pubky/pubky.iml b/android/src/main/java/com/pubky/pubky.iml new file mode 100644 index 0000000..36885ff --- /dev/null +++ b/android/src/main/java/com/pubky/pubky.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/src/main/java/uniffi/mobile/mobile.kt b/android/src/main/java/uniffi/mobile/mobile.kt new file mode 100644 index 0000000..33f85c6 --- /dev/null +++ b/android/src/main/java/uniffi/mobile/mobile.kt @@ -0,0 +1,688 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +@file:Suppress("NAME_SHADOWING") + +package uniffi.mobile; + +// Common helper code. +// +// Ideally this would live in a separate .kt file where it can be unittested etc +// in isolation, and perhaps even published as a re-useable package. +// +// However, it's important that the details of how this helper code works (e.g. the +// way that different builtin types are passed across the FFI) exactly match what's +// expected by the Rust code on the other side of the interface. In practice right +// now that means coming from the exact some version of `uniffi` that was used to +// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin +// helpers directly inline like we're doing here. + +import com.sun.jna.Library +import com.sun.jna.IntegerType +import com.sun.jna.Native +import com.sun.jna.Pointer +import com.sun.jna.Structure +import com.sun.jna.Callback +import com.sun.jna.ptr.* +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.CharBuffer +import java.nio.charset.CodingErrorAction +import java.util.concurrent.ConcurrentHashMap +import kotlin.coroutines.resume +import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.suspendCancellableCoroutine + +// This is a helper for safely working with byte buffers returned from the Rust code. +// A rust-owned buffer is represented by its capacity, its current length, and a +// pointer to the underlying data. + +@Structure.FieldOrder("capacity", "len", "data") +open class RustBuffer : Structure() { + @JvmField var capacity: Int = 0 + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue: RustBuffer(), Structure.ByValue + class ByReference: RustBuffer(), Structure.ByReference + + companion object { + internal fun alloc(size: Int = 0) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_mobile_rustbuffer_alloc(size, status) + }.also { + if(it.data == null) { + throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") + } + } + + internal fun create(capacity: Int, len: Int, data: Pointer?): RustBuffer.ByValue { + var buf = RustBuffer.ByValue() + buf.capacity = capacity + buf.len = len + buf.data = data + return buf + } + + internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_mobile_rustbuffer_free(buf, status) + } + } + + @Suppress("TooGenericExceptionThrown") + fun asByteBuffer() = + this.data?.getByteBuffer(0, this.len.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + } +} + +/** + * The equivalent of the `*mut RustBuffer` type. + * Required for callbacks taking in an out pointer. + * + * Size is the sum of all values in the struct. + */ +class RustBufferByReference : ByReference(16) { + /** + * Set the pointed-to `RustBuffer` to the given value. + */ + fun setValue(value: RustBuffer.ByValue) { + // NOTE: The offsets are as they are in the C-like struct. + val pointer = getPointer() + pointer.setInt(0, value.capacity) + pointer.setInt(4, value.len) + pointer.setPointer(8, value.data) + } + + /** + * Get a `RustBuffer.ByValue` from this reference. + */ + fun getValue(): RustBuffer.ByValue { + val pointer = getPointer() + val value = RustBuffer.ByValue() + value.writeField("capacity", pointer.getInt(0)) + value.writeField("len", pointer.getInt(4)) + value.writeField("data", pointer.getPointer(8)) + + return value + } +} + +// This is a helper for safely passing byte references into the rust code. +// It's not actually used at the moment, because there aren't many things that you +// can take a direct pointer to in the JVM, and if we're going to copy something +// then we might as well copy it into a `RustBuffer`. But it's here for API +// completeness. + +@Structure.FieldOrder("len", "data") +open class ForeignBytes : Structure() { + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue : ForeignBytes(), Structure.ByValue +} +// The FfiConverter interface handles converter types to and from the FFI +// +// All implementing objects should be public to support external types. When a +// type is external we need to import it's FfiConverter. +public interface FfiConverter { + // Convert an FFI type to a Kotlin type + fun lift(value: FfiType): KotlinType + + // Convert an Kotlin type to an FFI type + fun lower(value: KotlinType): FfiType + + // Read a Kotlin type from a `ByteBuffer` + fun read(buf: ByteBuffer): KotlinType + + // Calculate bytes to allocate when creating a `RustBuffer` + // + // This must return at least as many bytes as the write() function will + // write. It can return more bytes than needed, for example when writing + // Strings we can't know the exact bytes needed until we the UTF-8 + // encoding, so we pessimistically allocate the largest size possible (3 + // bytes per codepoint). Allocating extra bytes is not really a big deal + // because the `RustBuffer` is short-lived. + fun allocationSize(value: KotlinType): Int + + // Write a Kotlin type to a `ByteBuffer` + fun write(value: KotlinType, buf: ByteBuffer) + + // Lower a value into a `RustBuffer` + // + // This method lowers a value into a `RustBuffer` rather than the normal + // FfiType. It's used by the callback interface code. Callback interface + // returns are always serialized into a `RustBuffer` regardless of their + // normal FFI type. + fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue { + val rbuf = RustBuffer.alloc(allocationSize(value)) + try { + val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity.toLong()).also { + it.order(ByteOrder.BIG_ENDIAN) + } + write(value, bbuf) + rbuf.writeField("len", bbuf.position()) + return rbuf + } catch (e: Throwable) { + RustBuffer.free(rbuf) + throw e + } + } + + // Lift a value from a `RustBuffer`. + // + // This here mostly because of the symmetry with `lowerIntoRustBuffer()`. + // It's currently only used by the `FfiConverterRustBuffer` class below. + fun liftFromRustBuffer(rbuf: RustBuffer.ByValue): KotlinType { + val byteBuf = rbuf.asByteBuffer()!! + try { + val item = read(byteBuf) + if (byteBuf.hasRemaining()) { + throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") + } + return item + } finally { + RustBuffer.free(rbuf) + } + } +} + +// FfiConverter that uses `RustBuffer` as the FfiType +public interface FfiConverterRustBuffer: FfiConverter { + override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value) + override fun lower(value: KotlinType) = lowerIntoRustBuffer(value) +} +// A handful of classes and functions to support the generated data structures. +// This would be a good candidate for isolating in its own ffi-support lib. +// Error runtime. +@Structure.FieldOrder("code", "error_buf") +internal open class RustCallStatus : Structure() { + @JvmField var code: Byte = 0 + @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() + + class ByValue: RustCallStatus(), Structure.ByValue + + fun isSuccess(): Boolean { + return code == 0.toByte() + } + + fun isError(): Boolean { + return code == 1.toByte() + } + + fun isPanic(): Boolean { + return code == 2.toByte() + } +} + +class InternalException(message: String) : Exception(message) + +// Each top-level error class has a companion object that can lift the error from the call status's rust buffer +interface CallStatusErrorHandler { + fun lift(error_buf: RustBuffer.ByValue): E; +} + +// Helpers for calling Rust +// In practice we usually need to be synchronized to call this safely, so it doesn't +// synchronize itself + +// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err +private inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, callback: (RustCallStatus) -> U): U { + var status = RustCallStatus(); + val return_value = callback(status) + checkCallStatus(errorHandler, status) + return return_value +} + +// Check RustCallStatus and throw an error if the call wasn't successful +private fun checkCallStatus(errorHandler: CallStatusErrorHandler, status: RustCallStatus) { + if (status.isSuccess()) { + return + } else if (status.isError()) { + throw errorHandler.lift(status.error_buf) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.error_buf.len > 0) { + throw InternalException(FfiConverterString.lift(status.error_buf)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } +} + +// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR +object NullCallStatusErrorHandler: CallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): InternalException { + RustBuffer.free(error_buf) + return InternalException("Unexpected CALL_ERROR") + } +} + +// Call a rust function that returns a plain value +private inline fun rustCall(callback: (RustCallStatus) -> U): U { + return rustCallWithError(NullCallStatusErrorHandler, callback); +} + +// IntegerType that matches Rust's `usize` / C's `size_t` +public class USize(value: Long = 0) : IntegerType(Native.SIZE_T_SIZE, value, true) { + // This is needed to fill in the gaps of IntegerType's implementation of Number for Kotlin. + override fun toByte() = toInt().toByte() + // Needed until https://youtrack.jetbrains.com/issue/KT-47902 is fixed. + @Deprecated("`toInt().toChar()` is deprecated") + override fun toChar() = toInt().toChar() + override fun toShort() = toInt().toShort() + + fun writeToBuffer(buf: ByteBuffer) { + // Make sure we always write usize integers using native byte-order, since they may be + // casted to pointer values + buf.order(ByteOrder.nativeOrder()) + try { + when (Native.SIZE_T_SIZE) { + 4 -> buf.putInt(toInt()) + 8 -> buf.putLong(toLong()) + else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}") + } + } finally { + buf.order(ByteOrder.BIG_ENDIAN) + } + } + + companion object { + val size: Int + get() = Native.SIZE_T_SIZE + + fun readFromBuffer(buf: ByteBuffer) : USize { + // Make sure we always read usize integers using native byte-order, since they may be + // casted from pointer values + buf.order(ByteOrder.nativeOrder()) + try { + return when (Native.SIZE_T_SIZE) { + 4 -> USize(buf.getInt().toLong()) + 8 -> USize(buf.getLong()) + else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}") + } + } finally { + buf.order(ByteOrder.BIG_ENDIAN) + } + } + } +} + + +// Map handles to objects +// +// This is used when the Rust code expects an opaque pointer to represent some foreign object. +// Normally we would pass a pointer to the object, but JNA doesn't support getting a pointer from an +// object reference , nor does it support leaking a reference to Rust. +// +// Instead, this class maps USize values to objects so that we can pass a pointer-sized type to +// Rust when it needs an opaque pointer. +// +// TODO: refactor callbacks to use this class +internal class UniFfiHandleMap { + private val map = ConcurrentHashMap() + // Use AtomicInteger for our counter, since we may be on a 32-bit system. 4 billion possible + // values seems like enough. If somehow we generate 4 billion handles, then this will wrap + // around back to zero and we can assume the first handle generated will have been dropped by + // then. + private val counter = java.util.concurrent.atomic.AtomicInteger(0) + + val size: Int + get() = map.size + + fun insert(obj: T): USize { + val handle = USize(counter.getAndAdd(1).toLong()) + map.put(handle, obj) + return handle + } + + fun get(handle: USize): T? { + return map.get(handle) + } + + fun remove(handle: USize): T? { + return map.remove(handle) + } +} + +// FFI type for Rust future continuations +internal interface UniFffiRustFutureContinuationCallbackType : com.sun.jna.Callback { + fun callback(continuationHandle: USize, pollResult: Short); +} + +// Contains loading, initialization code, +// and the FFI Function declarations in a com.sun.jna.Library. +@Synchronized +private fun findLibraryName(componentName: String): String { + val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") + if (libOverride != null) { + return libOverride + } + return "mobile" +} + +private inline fun loadIndirect( + componentName: String +): Lib { + return Native.load(findLibraryName(componentName), Lib::class.java) +} + +// A JNA Library to expose the extern-C FFI definitions. +// This is an implementation detail which will be called internally by the public API. + +internal interface _UniFFILib : Library { + companion object { + internal val INSTANCE: _UniFFILib by lazy { + loadIndirect<_UniFFILib>(componentName = "mobile") + .also { lib: _UniFFILib -> + uniffiCheckContractApiVersion(lib) + uniffiCheckApiChecksums(lib) + uniffiRustFutureContinuationCallback.register(lib) + } + } + } + + fun uniffi_mobile_fn_func_auth(`url`: RustBuffer.ByValue,`secretKey`: RustBuffer.ByValue, + ): Pointer + fun uniffi_mobile_fn_func_myexample(_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue + fun ffi_mobile_rustbuffer_alloc(`size`: Int,_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue + fun ffi_mobile_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue,_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue + fun ffi_mobile_rustbuffer_free(`buf`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, + ): Unit + fun ffi_mobile_rustbuffer_reserve(`buf`: RustBuffer.ByValue,`additional`: Int,_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue + fun ffi_mobile_rust_future_continuation_callback_set(`callback`: UniFffiRustFutureContinuationCallbackType, + ): Unit + fun ffi_mobile_rust_future_poll_u8(`handle`: Pointer,`uniffiCallback`: USize, + ): Unit + fun ffi_mobile_rust_future_cancel_u8(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_free_u8(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_complete_u8(`handle`: Pointer,_uniffi_out_err: RustCallStatus, + ): Byte + fun ffi_mobile_rust_future_poll_i8(`handle`: Pointer,`uniffiCallback`: USize, + ): Unit + fun ffi_mobile_rust_future_cancel_i8(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_free_i8(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_complete_i8(`handle`: Pointer,_uniffi_out_err: RustCallStatus, + ): Byte + fun ffi_mobile_rust_future_poll_u16(`handle`: Pointer,`uniffiCallback`: USize, + ): Unit + fun ffi_mobile_rust_future_cancel_u16(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_free_u16(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_complete_u16(`handle`: Pointer,_uniffi_out_err: RustCallStatus, + ): Short + fun ffi_mobile_rust_future_poll_i16(`handle`: Pointer,`uniffiCallback`: USize, + ): Unit + fun ffi_mobile_rust_future_cancel_i16(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_free_i16(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_complete_i16(`handle`: Pointer,_uniffi_out_err: RustCallStatus, + ): Short + fun ffi_mobile_rust_future_poll_u32(`handle`: Pointer,`uniffiCallback`: USize, + ): Unit + fun ffi_mobile_rust_future_cancel_u32(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_free_u32(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_complete_u32(`handle`: Pointer,_uniffi_out_err: RustCallStatus, + ): Int + fun ffi_mobile_rust_future_poll_i32(`handle`: Pointer,`uniffiCallback`: USize, + ): Unit + fun ffi_mobile_rust_future_cancel_i32(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_free_i32(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_complete_i32(`handle`: Pointer,_uniffi_out_err: RustCallStatus, + ): Int + fun ffi_mobile_rust_future_poll_u64(`handle`: Pointer,`uniffiCallback`: USize, + ): Unit + fun ffi_mobile_rust_future_cancel_u64(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_free_u64(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_complete_u64(`handle`: Pointer,_uniffi_out_err: RustCallStatus, + ): Long + fun ffi_mobile_rust_future_poll_i64(`handle`: Pointer,`uniffiCallback`: USize, + ): Unit + fun ffi_mobile_rust_future_cancel_i64(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_free_i64(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_complete_i64(`handle`: Pointer,_uniffi_out_err: RustCallStatus, + ): Long + fun ffi_mobile_rust_future_poll_f32(`handle`: Pointer,`uniffiCallback`: USize, + ): Unit + fun ffi_mobile_rust_future_cancel_f32(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_free_f32(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_complete_f32(`handle`: Pointer,_uniffi_out_err: RustCallStatus, + ): Float + fun ffi_mobile_rust_future_poll_f64(`handle`: Pointer,`uniffiCallback`: USize, + ): Unit + fun ffi_mobile_rust_future_cancel_f64(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_free_f64(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_complete_f64(`handle`: Pointer,_uniffi_out_err: RustCallStatus, + ): Double + fun ffi_mobile_rust_future_poll_pointer(`handle`: Pointer,`uniffiCallback`: USize, + ): Unit + fun ffi_mobile_rust_future_cancel_pointer(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_free_pointer(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_complete_pointer(`handle`: Pointer,_uniffi_out_err: RustCallStatus, + ): Pointer + fun ffi_mobile_rust_future_poll_rust_buffer(`handle`: Pointer,`uniffiCallback`: USize, + ): Unit + fun ffi_mobile_rust_future_cancel_rust_buffer(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_free_rust_buffer(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_complete_rust_buffer(`handle`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue + fun ffi_mobile_rust_future_poll_void(`handle`: Pointer,`uniffiCallback`: USize, + ): Unit + fun ffi_mobile_rust_future_cancel_void(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_free_void(`handle`: Pointer, + ): Unit + fun ffi_mobile_rust_future_complete_void(`handle`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + fun uniffi_mobile_checksum_func_auth( + ): Short + fun uniffi_mobile_checksum_func_myexample( + ): Short + fun ffi_mobile_uniffi_contract_version( + ): Int + +} + +private fun uniffiCheckContractApiVersion(lib: _UniFFILib) { + // Get the bindings contract version from our ComponentInterface + val bindings_contract_version = 24 + // Get the scaffolding contract version by calling the into the dylib + val scaffolding_contract_version = lib.ffi_mobile_uniffi_contract_version() + if (bindings_contract_version != scaffolding_contract_version) { + throw RuntimeException("UniFFI contract version mismatch: try cleaning and rebuilding your project") + } +} + +@Suppress("UNUSED_PARAMETER") +private fun uniffiCheckApiChecksums(lib: _UniFFILib) { + if (lib.uniffi_mobile_checksum_func_auth() != 55720.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mobile_checksum_func_myexample() != 65225.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } +} + +// Async support +// Async return type handlers + +internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toShort() +internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toShort() + +internal val uniffiContinuationHandleMap = UniFfiHandleMap>() + +// FFI type for Rust future continuations +internal object uniffiRustFutureContinuationCallback: UniFffiRustFutureContinuationCallbackType { + override fun callback(continuationHandle: USize, pollResult: Short) { + uniffiContinuationHandleMap.remove(continuationHandle)?.resume(pollResult) + } + + internal fun register(lib: _UniFFILib) { + lib.ffi_mobile_rust_future_continuation_callback_set(this) + } +} + +internal suspend fun uniffiRustCallAsync( + rustFuture: Pointer, + pollFunc: (Pointer, USize) -> Unit, + completeFunc: (Pointer, RustCallStatus) -> F, + freeFunc: (Pointer) -> Unit, + liftFunc: (F) -> T, + errorHandler: CallStatusErrorHandler +): T { + try { + do { + val pollResult = suspendCancellableCoroutine { continuation -> + pollFunc( + rustFuture, + uniffiContinuationHandleMap.insert(continuation) + ) + } + } while (pollResult != UNIFFI_RUST_FUTURE_POLL_READY); + + return liftFunc( + rustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) }) + ) + } finally { + freeFunc(rustFuture) + } +} + + +// Public interface members begin here. + + +public object FfiConverterString: FfiConverter { + // Note: we don't inherit from FfiConverterRustBuffer, because we use a + // special encoding when lowering/lifting. We can use `RustBuffer.len` to + // store our length and avoid writing it out to the buffer. + override fun lift(value: RustBuffer.ByValue): String { + try { + val byteArr = ByteArray(value.len) + value.asByteBuffer()!!.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } finally { + RustBuffer.free(value) + } + } + + override fun read(buf: ByteBuffer): String { + val len = buf.getInt() + val byteArr = ByteArray(len) + buf.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } + + fun toUtf8(value: String): ByteBuffer { + // Make sure we don't have invalid UTF-16, check for lone surrogates. + return Charsets.UTF_8.newEncoder().run { + onMalformedInput(CodingErrorAction.REPORT) + encode(CharBuffer.wrap(value)) + } + } + + override fun lower(value: String): RustBuffer.ByValue { + val byteBuf = toUtf8(value) + // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us + // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. + val rbuf = RustBuffer.alloc(byteBuf.limit()) + rbuf.asByteBuffer()!!.put(byteBuf) + return rbuf + } + + // We aren't sure exactly how many bytes our string will be once it's UTF-8 + // encoded. Allocate 3 bytes per UTF-16 code unit which will always be + // enough. + override fun allocationSize(value: String): Int { + val sizeForLength = 4 + val sizeForString = value.length * 3 + return sizeForLength + sizeForString + } + + override fun write(value: String, buf: ByteBuffer) { + val byteBuf = toUtf8(value) + buf.putInt(byteBuf.limit()) + buf.put(byteBuf) + } +} + + + + +public object FfiConverterSequenceString: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterString.read(buf) + } + } + + override fun allocationSize(value: List): Int { + val sizeForLength = 4 + val sizeForItems = value.map { FfiConverterString.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.forEach { + FfiConverterString.write(it, buf) + } + } +} + + + + + +@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") +suspend fun `auth`(`url`: String, `secretKey`: String) : List { + return uniffiRustCallAsync( + _UniFFILib.INSTANCE.uniffi_mobile_fn_func_auth(FfiConverterString.lower(`url`),FfiConverterString.lower(`secretKey`),), + { future, continuation -> _UniFFILib.INSTANCE.ffi_mobile_rust_future_poll_rust_buffer(future, continuation) }, + { future, continuation -> _UniFFILib.INSTANCE.ffi_mobile_rust_future_complete_rust_buffer(future, continuation) }, + { future -> _UniFFILib.INSTANCE.ffi_mobile_rust_future_free_rust_buffer(future) }, + // lift function + { FfiConverterSequenceString.lift(it) }, + // Error FFI converter + NullCallStatusErrorHandler, + ) +} + +fun `myexample`(): List { + return FfiConverterSequenceString.lift( + rustCall() { _status -> + _UniFFILib.INSTANCE.uniffi_mobile_fn_func_myexample(_status) +}) +} + + diff --git a/android/src/main/jniLibs/arm64-v8a/libmobile.so b/android/src/main/jniLibs/arm64-v8a/libmobile.so new file mode 100755 index 0000000..8e41248 Binary files /dev/null and b/android/src/main/jniLibs/arm64-v8a/libmobile.so differ diff --git a/android/src/main/jniLibs/armeabi-v7a/libmobile.so b/android/src/main/jniLibs/armeabi-v7a/libmobile.so new file mode 100755 index 0000000..e7cbeba Binary files /dev/null and b/android/src/main/jniLibs/armeabi-v7a/libmobile.so differ diff --git a/android/src/main/jniLibs/x86/libmobile.so b/android/src/main/jniLibs/x86/libmobile.so new file mode 100755 index 0000000..4190e15 Binary files /dev/null and b/android/src/main/jniLibs/x86/libmobile.so differ diff --git a/android/src/main/jniLibs/x86_64/libmobile.so b/android/src/main/jniLibs/x86_64/libmobile.so new file mode 100755 index 0000000..b4448e9 Binary files /dev/null and b/android/src/main/jniLibs/x86_64/libmobile.so differ diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..29f3a60 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,5 @@ +module.exports = { + presets: [ + ['module:react-native-builder-bob/babel-preset', { modules: 'commonjs' }], + ], +}; diff --git a/example/.bundle/config b/example/.bundle/config new file mode 100644 index 0000000..848943b --- /dev/null +++ b/example/.bundle/config @@ -0,0 +1,2 @@ +BUNDLE_PATH: "vendor/bundle" +BUNDLE_FORCE_RUBY_PLATFORM: 1 diff --git a/example/.watchmanconfig b/example/.watchmanconfig new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/example/.watchmanconfig @@ -0,0 +1 @@ +{} diff --git a/example/Gemfile b/example/Gemfile new file mode 100644 index 0000000..2a7ce35 --- /dev/null +++ b/example/Gemfile @@ -0,0 +1,8 @@ +source 'https://rubygems.org' + +# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version +ruby ">= 2.6.10" + +# Exclude problematic versions of cocoapods and activesupport that causes build failures. +gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' +gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..12470c3 --- /dev/null +++ b/example/README.md @@ -0,0 +1,79 @@ +This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli). + +# Getting Started + +>**Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding. + +## Step 1: Start the Metro Server + +First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native. + +To start Metro, run the following command from the _root_ of your React Native project: + +```bash +# using npm +npm start + +# OR using Yarn +yarn start +``` + +## Step 2: Start your Application + +Let Metro Bundler run in its _own_ terminal. Open a _new_ terminal from the _root_ of your React Native project. Run the following command to start your _Android_ or _iOS_ app: + +### For Android + +```bash +# using npm +npm run android + +# OR using Yarn +yarn android +``` + +### For iOS + +```bash +# using npm +npm run ios + +# OR using Yarn +yarn ios +``` + +If everything is set up _correctly_, you should see your new app running in your _Android Emulator_ or _iOS Simulator_ shortly provided you have set up your emulator/simulator correctly. + +This is one way to run your app — you can also run it directly from within Android Studio and Xcode respectively. + +## Step 3: Modifying your App + +Now that you have successfully run the app, let's modify it. + +1. Open `App.tsx` in your text editor of choice and edit some lines. +2. For **Android**: Press the R key twice or select **"Reload"** from the **Developer Menu** (Ctrl + M (on Window and Linux) or Cmd ⌘ + M (on macOS)) to see your changes! + + For **iOS**: Hit Cmd ⌘ + R in your iOS Simulator to reload the app and see your changes! + +## Congratulations! :tada: + +You've successfully run and modified your React Native App. :partying_face: + +### Now what? + +- If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps). +- If you're curious to learn more about React Native, check out the [Introduction to React Native](https://reactnative.dev/docs/getting-started). + +# Troubleshooting + +If you can't get this to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. + +# Learn More + +To learn more about React Native, take a look at the following resources: + +- [React Native Website](https://reactnative.dev) - learn more about React Native. +- [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment. +- [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**. +- [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts. +- [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native. diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle new file mode 100644 index 0000000..cc7c929 --- /dev/null +++ b/example/android/app/build.gradle @@ -0,0 +1,119 @@ +apply plugin: "com.android.application" +apply plugin: "org.jetbrains.kotlin.android" +apply plugin: "com.facebook.react" + +/** + * This is the configuration block to customize your React Native Android app. + * By default you don't need to apply any configuration, just uncomment the lines you need. + */ +react { + /* Folders */ + // The root of your project, i.e. where "package.json" lives. Default is '../..' + // root = file("../../") + // The folder where the react-native NPM package is. Default is ../../node_modules/react-native + // reactNativeDir = file("../../node_modules/react-native") + // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen + // codegenDir = file("../../node_modules/@react-native/codegen") + // The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js + // cliFile = file("../../node_modules/react-native/cli.js") + + /* Variants */ + // The list of variants to that are debuggable. For those we're going to + // skip the bundling of the JS bundle and the assets. By default is just 'debug'. + // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. + // debuggableVariants = ["liteDebug", "prodDebug"] + + /* Bundling */ + // A list containing the node command and its flags. Default is just 'node'. + // nodeExecutableAndArgs = ["node"] + // + // The command to run when bundling. By default is 'bundle' + // bundleCommand = "ram-bundle" + // + // The path to the CLI configuration file. Default is empty. + // bundleConfig = file(../rn-cli.config.js) + // + // The name of the generated asset file containing your JS bundle + // bundleAssetName = "MyApplication.android.bundle" + // + // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' + // entryFile = file("../js/MyApplication.android.js") + // + // A list of extra flags to pass to the 'bundle' commands. + // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle + // extraPackagerArgs = [] + + /* Hermes Commands */ + // The hermes compiler command to run. By default it is 'hermesc' + // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" + // + // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" + // hermesFlags = ["-O", "-output-source-map"] + + /* Autolinking */ + autolinkLibrariesWithApp() +} + +/** + * Set this to true to Run Proguard on Release builds to minify the Java bytecode. + */ +def enableProguardInReleaseBuilds = false + +/** + * The preferred build flavor of JavaScriptCore (JSC) + * + * For example, to use the international variant, you can use: + * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` + * + * The international variant includes ICU i18n library and necessary data + * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that + * give correct results when using with locales other than en-US. Note that + * this variant is about 6MiB larger per architecture than default. + */ +def jscFlavor = 'org.webkit:android-jsc:+' + +android { + ndkVersion rootProject.ext.ndkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + compileSdk rootProject.ext.compileSdkVersion + + namespace "pubky.example" + defaultConfig { + applicationId "pubky.example" + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName "1.0" + } + signingConfigs { + debug { + storeFile file('debug.keystore') + storePassword 'android' + keyAlias 'androiddebugkey' + keyPassword 'android' + } + } + buildTypes { + debug { + signingConfig signingConfigs.debug + } + release { + // Caution! In production, you need to generate your own keystore file. + // see https://reactnative.dev/docs/signed-apk-android. + signingConfig signingConfigs.debug + minifyEnabled enableProguardInReleaseBuilds + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + } + } +} + +dependencies { + // The version of react-native is set by the React Native Gradle Plugin + implementation("com.facebook.react:react-android") + + if (hermesEnabled.toBoolean()) { + implementation("com.facebook.react:hermes-android") + } else { + implementation jscFlavor + } +} diff --git a/example/android/app/debug.keystore b/example/android/app/debug.keystore new file mode 100644 index 0000000..364e105 Binary files /dev/null and b/example/android/app/debug.keystore differ diff --git a/example/android/app/proguard-rules.pro b/example/android/app/proguard-rules.pro new file mode 100644 index 0000000..11b0257 --- /dev/null +++ b/example/android/app/proguard-rules.pro @@ -0,0 +1,10 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..eb98c01 --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e189252 --- /dev/null +++ b/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + diff --git a/example/android/app/src/main/java/pubky/example/MainActivity.kt b/example/android/app/src/main/java/pubky/example/MainActivity.kt new file mode 100644 index 0000000..2767fe9 --- /dev/null +++ b/example/android/app/src/main/java/pubky/example/MainActivity.kt @@ -0,0 +1,22 @@ +package pubky.example + +import com.facebook.react.ReactActivity +import com.facebook.react.ReactActivityDelegate +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled +import com.facebook.react.defaults.DefaultReactActivityDelegate + +class MainActivity : ReactActivity() { + + /** + * Returns the name of the main component registered from JavaScript. This is used to schedule + * rendering of the component. + */ + override fun getMainComponentName(): String = "PubkyExample" + + /** + * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] + * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] + */ + override fun createReactActivityDelegate(): ReactActivityDelegate = + DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) +} diff --git a/example/android/app/src/main/java/pubky/example/MainApplication.kt b/example/android/app/src/main/java/pubky/example/MainApplication.kt new file mode 100644 index 0000000..09089a9 --- /dev/null +++ b/example/android/app/src/main/java/pubky/example/MainApplication.kt @@ -0,0 +1,43 @@ +package pubky.example + +import android.app.Application +import com.facebook.react.PackageList +import com.facebook.react.ReactApplication +import com.facebook.react.ReactHost +import com.facebook.react.ReactNativeHost +import com.facebook.react.ReactPackage +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load +import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost +import com.facebook.react.defaults.DefaultReactNativeHost +import com.facebook.soloader.SoLoader + +class MainApplication : Application(), ReactApplication { + + override val reactNativeHost: ReactNativeHost = + object : DefaultReactNativeHost(this) { + override fun getPackages(): List = + PackageList(this).packages.apply { + // Packages that cannot be autolinked yet can be added manually here, for example: + // add(MyReactNativePackage()) + } + + override fun getJSMainModuleName(): String = "index" + + override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG + + override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED + } + + override val reactHost: ReactHost + get() = getDefaultReactHost(applicationContext, reactNativeHost) + + override fun onCreate() { + super.onCreate() + SoLoader.init(this, false) + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + // If you opted-in for the New Architecture, we load the native entry point for this app. + load() + } + } +} diff --git a/example/android/app/src/main/res/drawable/rn_edit_text_material.xml b/example/android/app/src/main/res/drawable/rn_edit_text_material.xml new file mode 100644 index 0000000..5c25e72 --- /dev/null +++ b/example/android/app/src/main/res/drawable/rn_edit_text_material.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..a2f5908 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..1b52399 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..ff10afd Binary files /dev/null and b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..115a4c7 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..dcd3cd8 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..459ca60 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..8ca12fe Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..8e19b41 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..b824ebd Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..4c19a13 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/example/android/app/src/main/res/values/strings.xml b/example/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..12d70b7 --- /dev/null +++ b/example/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + PubkyExample + diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..7ba83a2 --- /dev/null +++ b/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle new file mode 100644 index 0000000..df1ce4d --- /dev/null +++ b/example/android/build.gradle @@ -0,0 +1,21 @@ +buildscript { + ext { + buildToolsVersion = "34.0.0" + minSdkVersion = 23 + compileSdkVersion = 34 + targetSdkVersion = 34 + ndkVersion = "26.1.10909125" + kotlinVersion = "1.9.24" + } + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle") + classpath("com.facebook.react:react-native-gradle-plugin") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") + } +} + +apply plugin: "com.facebook.react.rootproject" diff --git a/example/android/gradle.properties b/example/android/gradle.properties new file mode 100644 index 0000000..9fb1566 --- /dev/null +++ b/example/android/gradle.properties @@ -0,0 +1,39 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m +org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true + +# Use this property to specify which architecture you want to build. +# You can also override it from the CLI using +# ./gradlew -PreactNativeArchitectures=x86_64 +reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 + +# Use this property to enable support to the new architecture. +# This will allow you to use TurboModules and the Fabric render in +# your application. You should enable this flag either if you want +# to write custom TurboModules/Fabric components OR use libraries that +# are providing them. +newArchEnabled=false + +# Use this property to enable or disable the Hermes JS engine. +# If set to false, you will be using JSC instead. +hermesEnabled=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.jar b/example/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e644113 Binary files /dev/null and b/example/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..6f7a6eb --- /dev/null +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/example/android/gradlew b/example/android/gradlew new file mode 100755 index 0000000..b740cf1 --- /dev/null +++ b/example/android/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/example/android/gradlew.bat b/example/android/gradlew.bat new file mode 100644 index 0000000..7101f8e --- /dev/null +++ b/example/android/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/example/android/settings.gradle b/example/android/settings.gradle new file mode 100644 index 0000000..e464540 --- /dev/null +++ b/example/android/settings.gradle @@ -0,0 +1,6 @@ +pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } +plugins { id("com.facebook.react.settings") } +extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } +rootProject.name = 'pubky.example' +include ':app' +includeBuild('../node_modules/@react-native/gradle-plugin') diff --git a/example/app.json b/example/app.json new file mode 100644 index 0000000..c82a73d --- /dev/null +++ b/example/app.json @@ -0,0 +1,4 @@ +{ + "name": "PubkyExample", + "displayName": "PubkyExample" +} diff --git a/example/babel.config.js b/example/babel.config.js new file mode 100644 index 0000000..486a093 --- /dev/null +++ b/example/babel.config.js @@ -0,0 +1,12 @@ +const path = require('path'); +const { getConfig } = require('react-native-builder-bob/babel-config'); +const pkg = require('../package.json'); + +const root = path.resolve(__dirname, '..'); + +module.exports = getConfig( + { + presets: ['module:@react-native/babel-preset'], + }, + { root, pkg } +); diff --git a/example/index.js b/example/index.js new file mode 100644 index 0000000..117ddca --- /dev/null +++ b/example/index.js @@ -0,0 +1,5 @@ +import { AppRegistry } from 'react-native'; +import App from './src/App'; +import { name as appName } from './app.json'; + +AppRegistry.registerComponent(appName, () => App); diff --git a/example/ios/.xcode.env b/example/ios/.xcode.env new file mode 100644 index 0000000..3d5782c --- /dev/null +++ b/example/ios/.xcode.env @@ -0,0 +1,11 @@ +# This `.xcode.env` file is versioned and is used to source the environment +# used when running script phases inside Xcode. +# To customize your local environment, you can create an `.xcode.env.local` +# file that is not versioned. + +# NODE_BINARY variable contains the PATH to the node executable. +# +# Customize the NODE_BINARY variable here. +# For example, to use nvm with brew, add the following line +# . "$(brew --prefix nvm)/nvm.sh" --no-use +export NODE_BINARY=$(command -v node) diff --git a/example/ios/.xcode.env.local b/example/ios/.xcode.env.local new file mode 100644 index 0000000..29c943b --- /dev/null +++ b/example/ios/.xcode.env.local @@ -0,0 +1 @@ +export NODE_BINARY=/Users/coreyphillips/.nvm/versions/node/v20.14.0/bin/node diff --git a/example/ios/File.swift b/example/ios/File.swift new file mode 100644 index 0000000..7bb2df7 --- /dev/null +++ b/example/ios/File.swift @@ -0,0 +1,6 @@ +// +// File.swift +// PubkyExample +// + +import Foundation diff --git a/example/ios/Podfile b/example/ios/Podfile new file mode 100644 index 0000000..4ca319a --- /dev/null +++ b/example/ios/Podfile @@ -0,0 +1,40 @@ +# Resolve react_native_pods.rb with node to allow for hoisting +require Pod::Executable.execute_command('node', ['-p', + 'require.resolve( + "react-native/scripts/react_native_pods.rb", + {paths: [process.argv[1]]}, + )', __dir__]).strip + +platform :ios, min_ios_version_supported +prepare_react_native_project! + +linkage = ENV['USE_FRAMEWORKS'] +if linkage != nil + Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green + use_frameworks! :linkage => linkage.to_sym +end + +target 'PubkyExample' do + config = use_native_modules! + + use_react_native!( + :path => config[:reactNativePath], + # An absolute path to your application root. + :app_path => "#{Pod::Config.instance.installation_root}/.." + ) + + target 'PubkyExampleTests' do + inherit! :complete + # Pods for testing + end + + post_install do |installer| + # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 + react_native_post_install( + installer, + config[:reactNativePath], + :mac_catalyst_enabled => false, + # :ccache_enabled => true + ) + end +end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock new file mode 100644 index 0000000..86c637d --- /dev/null +++ b/example/ios/Podfile.lock @@ -0,0 +1,1792 @@ +PODS: + - boost (1.84.0) + - DoubleConversion (1.1.6) + - FBLazyVector (0.75.2) + - fmt (9.1.0) + - glog (0.3.5) + - hermes-engine (0.75.2): + - hermes-engine/Pre-built (= 0.75.2) + - hermes-engine/Pre-built (0.75.2) + - RCT-Folly (2024.01.01.00): + - boost + - DoubleConversion + - fmt (= 9.1.0) + - glog + - RCT-Folly/Default (= 2024.01.01.00) + - RCT-Folly/Default (2024.01.01.00): + - boost + - DoubleConversion + - fmt (= 9.1.0) + - glog + - RCT-Folly/Fabric (2024.01.01.00): + - boost + - DoubleConversion + - fmt (= 9.1.0) + - glog + - RCTDeprecation (0.75.2) + - RCTRequired (0.75.2) + - RCTTypeSafety (0.75.2): + - FBLazyVector (= 0.75.2) + - RCTRequired (= 0.75.2) + - React-Core (= 0.75.2) + - React (0.75.2): + - React-Core (= 0.75.2) + - React-Core/DevSupport (= 0.75.2) + - React-Core/RCTWebSocket (= 0.75.2) + - React-RCTActionSheet (= 0.75.2) + - React-RCTAnimation (= 0.75.2) + - React-RCTBlob (= 0.75.2) + - React-RCTImage (= 0.75.2) + - React-RCTLinking (= 0.75.2) + - React-RCTNetwork (= 0.75.2) + - React-RCTSettings (= 0.75.2) + - React-RCTText (= 0.75.2) + - React-RCTVibration (= 0.75.2) + - React-callinvoker (0.75.2) + - React-Core (0.75.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default (= 0.75.2) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/CoreModulesHeaders (0.75.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/Default (0.75.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/DevSupport (0.75.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default (= 0.75.2) + - React-Core/RCTWebSocket (= 0.75.2) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTActionSheetHeaders (0.75.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTAnimationHeaders (0.75.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTBlobHeaders (0.75.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTImageHeaders (0.75.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTLinkingHeaders (0.75.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTNetworkHeaders (0.75.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTSettingsHeaders (0.75.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTTextHeaders (0.75.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTVibrationHeaders (0.75.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTWebSocket (0.75.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default (= 0.75.2) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-CoreModules (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - RCT-Folly (= 2024.01.01.00) + - RCTTypeSafety (= 0.75.2) + - React-Core/CoreModulesHeaders (= 0.75.2) + - React-jsi (= 0.75.2) + - React-jsinspector + - React-NativeModulesApple + - React-RCTBlob + - React-RCTImage (= 0.75.2) + - ReactCodegen + - ReactCommon + - SocketRocket (= 0.7.0) + - React-cxxreact (0.75.2): + - boost + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-callinvoker (= 0.75.2) + - React-debug (= 0.75.2) + - React-jsi (= 0.75.2) + - React-jsinspector + - React-logger (= 0.75.2) + - React-perflogger (= 0.75.2) + - React-runtimeexecutor (= 0.75.2) + - React-debug (0.75.2) + - React-defaultsnativemodule (0.75.2): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-domnativemodule + - React-Fabric + - React-featureflags + - React-featureflagsnativemodule + - React-graphics + - React-idlecallbacksnativemodule + - React-ImageManager + - React-microtasksnativemodule + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - React-domnativemodule (0.75.2): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-FabricComponents + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - React-Fabric (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/animations (= 0.75.2) + - React-Fabric/attributedstring (= 0.75.2) + - React-Fabric/componentregistry (= 0.75.2) + - React-Fabric/componentregistrynative (= 0.75.2) + - React-Fabric/components (= 0.75.2) + - React-Fabric/core (= 0.75.2) + - React-Fabric/dom (= 0.75.2) + - React-Fabric/imagemanager (= 0.75.2) + - React-Fabric/leakchecker (= 0.75.2) + - React-Fabric/mounting (= 0.75.2) + - React-Fabric/observers (= 0.75.2) + - React-Fabric/scheduler (= 0.75.2) + - React-Fabric/telemetry (= 0.75.2) + - React-Fabric/templateprocessor (= 0.75.2) + - React-Fabric/uimanager (= 0.75.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/animations (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/attributedstring (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/componentregistry (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/componentregistrynative (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/components/legacyviewmanagerinterop (= 0.75.2) + - React-Fabric/components/root (= 0.75.2) + - React-Fabric/components/view (= 0.75.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components/legacyviewmanagerinterop (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components/root (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components/view (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - Yoga + - React-Fabric/core (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/dom (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/imagemanager (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/leakchecker (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/mounting (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/observers (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/observers/events (= 0.75.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/observers/events (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/scheduler (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/observers/events + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-performancetimeline + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/telemetry (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/templateprocessor (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/uimanager (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/uimanager/consistency (= 0.75.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererconsistency + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/uimanager/consistency (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererconsistency + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-FabricComponents (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-FabricComponents/components (= 0.75.2) + - React-FabricComponents/textlayoutmanager (= 0.75.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-FabricComponents/components/inputaccessory (= 0.75.2) + - React-FabricComponents/components/iostextinput (= 0.75.2) + - React-FabricComponents/components/modal (= 0.75.2) + - React-FabricComponents/components/rncore (= 0.75.2) + - React-FabricComponents/components/safeareaview (= 0.75.2) + - React-FabricComponents/components/scrollview (= 0.75.2) + - React-FabricComponents/components/text (= 0.75.2) + - React-FabricComponents/components/textinput (= 0.75.2) + - React-FabricComponents/components/unimplementedview (= 0.75.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/inputaccessory (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/iostextinput (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/modal (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/rncore (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/safeareaview (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/scrollview (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/text (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/textinput (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/unimplementedview (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/textlayoutmanager (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - Yoga + - React-FabricImage (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired (= 0.75.2) + - RCTTypeSafety (= 0.75.2) + - React-Fabric + - React-graphics + - React-ImageManager + - React-jsi + - React-jsiexecutor (= 0.75.2) + - React-logger + - React-rendererdebug + - React-utils + - ReactCommon + - Yoga + - React-featureflags (0.75.2) + - React-featureflagsnativemodule (0.75.2): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - React-graphics (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - RCT-Folly/Fabric (= 2024.01.01.00) + - React-jsi + - React-jsiexecutor + - React-utils + - React-hermes (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-cxxreact (= 0.75.2) + - React-jsi + - React-jsiexecutor (= 0.75.2) + - React-jsinspector + - React-perflogger (= 0.75.2) + - React-runtimeexecutor + - React-idlecallbacksnativemodule (0.75.2): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - React-ImageManager (0.75.2): + - glog + - RCT-Folly/Fabric + - React-Core/Default + - React-debug + - React-Fabric + - React-graphics + - React-rendererdebug + - React-utils + - React-jserrorhandler (0.75.2): + - RCT-Folly/Fabric (= 2024.01.01.00) + - React-debug + - React-jsi + - React-jsi (0.75.2): + - boost + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-jsiexecutor (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-cxxreact (= 0.75.2) + - React-jsi (= 0.75.2) + - React-jsinspector + - React-perflogger (= 0.75.2) + - React-jsinspector (0.75.2): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-featureflags + - React-jsi + - React-runtimeexecutor (= 0.75.2) + - React-jsitracing (0.75.2): + - React-jsi + - React-logger (0.75.2): + - glog + - React-Mapbuffer (0.75.2): + - glog + - React-debug + - React-microtasksnativemodule (0.75.2): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - react-native-pubky (0.1.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - React-nativeconfig (0.75.2) + - React-NativeModulesApple (0.75.2): + - glog + - hermes-engine + - React-callinvoker + - React-Core + - React-cxxreact + - React-jsi + - React-jsinspector + - React-runtimeexecutor + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - React-perflogger (0.75.2) + - React-performancetimeline (0.75.2): + - RCT-Folly (= 2024.01.01.00) + - React-cxxreact + - React-RCTActionSheet (0.75.2): + - React-Core/RCTActionSheetHeaders (= 0.75.2) + - React-RCTAnimation (0.75.2): + - RCT-Folly (= 2024.01.01.00) + - RCTTypeSafety + - React-Core/RCTAnimationHeaders + - React-jsi + - React-NativeModulesApple + - ReactCodegen + - ReactCommon + - React-RCTAppDelegate (0.75.2): + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-CoreModules + - React-debug + - React-defaultsnativemodule + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-nativeconfig + - React-NativeModulesApple + - React-RCTFabric + - React-RCTImage + - React-RCTNetwork + - React-rendererdebug + - React-RuntimeApple + - React-RuntimeCore + - React-RuntimeHermes + - React-runtimescheduler + - React-utils + - ReactCodegen + - ReactCommon + - React-RCTBlob (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-Core/RCTBlobHeaders + - React-Core/RCTWebSocket + - React-jsi + - React-jsinspector + - React-NativeModulesApple + - React-RCTNetwork + - ReactCodegen + - ReactCommon + - React-RCTFabric (0.75.2): + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - React-Core + - React-debug + - React-Fabric + - React-FabricComponents + - React-FabricImage + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsinspector + - React-nativeconfig + - React-performancetimeline + - React-RCTImage + - React-RCTText + - React-rendererconsistency + - React-rendererdebug + - React-runtimescheduler + - React-utils + - Yoga + - React-RCTImage (0.75.2): + - RCT-Folly (= 2024.01.01.00) + - RCTTypeSafety + - React-Core/RCTImageHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTNetwork + - ReactCodegen + - ReactCommon + - React-RCTLinking (0.75.2): + - React-Core/RCTLinkingHeaders (= 0.75.2) + - React-jsi (= 0.75.2) + - React-NativeModulesApple + - ReactCodegen + - ReactCommon + - ReactCommon/turbomodule/core (= 0.75.2) + - React-RCTNetwork (0.75.2): + - RCT-Folly (= 2024.01.01.00) + - RCTTypeSafety + - React-Core/RCTNetworkHeaders + - React-jsi + - React-NativeModulesApple + - ReactCodegen + - ReactCommon + - React-RCTSettings (0.75.2): + - RCT-Folly (= 2024.01.01.00) + - RCTTypeSafety + - React-Core/RCTSettingsHeaders + - React-jsi + - React-NativeModulesApple + - ReactCodegen + - ReactCommon + - React-RCTText (0.75.2): + - React-Core/RCTTextHeaders (= 0.75.2) + - Yoga + - React-RCTVibration (0.75.2): + - RCT-Folly (= 2024.01.01.00) + - React-Core/RCTVibrationHeaders + - React-jsi + - React-NativeModulesApple + - ReactCodegen + - ReactCommon + - React-rendererconsistency (0.75.2) + - React-rendererdebug (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - RCT-Folly (= 2024.01.01.00) + - React-debug + - React-rncore (0.75.2) + - React-RuntimeApple (0.75.2): + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - React-callinvoker + - React-Core/Default + - React-CoreModules + - React-cxxreact + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-Mapbuffer + - React-NativeModulesApple + - React-RCTFabric + - React-RuntimeCore + - React-runtimeexecutor + - React-RuntimeHermes + - React-runtimescheduler + - React-utils + - React-RuntimeCore (0.75.2): + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - React-cxxreact + - React-featureflags + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - React-runtimeexecutor (0.75.2): + - React-jsi (= 0.75.2) + - React-RuntimeHermes (0.75.2): + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - React-featureflags + - React-hermes + - React-jsi + - React-jsinspector + - React-jsitracing + - React-nativeconfig + - React-RuntimeCore + - React-utils + - React-runtimescheduler (0.75.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-callinvoker + - React-cxxreact + - React-debug + - React-featureflags + - React-jsi + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-utils + - React-utils (0.75.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-debug + - React-jsi (= 0.75.2) + - ReactCodegen (0.75.2): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-FabricImage + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-NativeModulesApple + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactCommon (0.75.2): + - ReactCommon/turbomodule (= 0.75.2) + - ReactCommon/turbomodule (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-callinvoker (= 0.75.2) + - React-cxxreact (= 0.75.2) + - React-jsi (= 0.75.2) + - React-logger (= 0.75.2) + - React-perflogger (= 0.75.2) + - ReactCommon/turbomodule/bridging (= 0.75.2) + - ReactCommon/turbomodule/core (= 0.75.2) + - ReactCommon/turbomodule/bridging (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-callinvoker (= 0.75.2) + - React-cxxreact (= 0.75.2) + - React-jsi (= 0.75.2) + - React-logger (= 0.75.2) + - React-perflogger (= 0.75.2) + - ReactCommon/turbomodule/core (0.75.2): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-callinvoker (= 0.75.2) + - React-cxxreact (= 0.75.2) + - React-debug (= 0.75.2) + - React-featureflags (= 0.75.2) + - React-jsi (= 0.75.2) + - React-logger (= 0.75.2) + - React-perflogger (= 0.75.2) + - React-utils (= 0.75.2) + - SocketRocket (0.7.0) + - Yoga (0.0.0) + +DEPENDENCIES: + - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) + - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) + - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) + - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) + - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) + - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) + - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) + - RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) + - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) + - RCTRequired (from `../node_modules/react-native/Libraries/Required`) + - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) + - React (from `../node_modules/react-native/`) + - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) + - React-Core (from `../node_modules/react-native/`) + - React-Core/RCTWebSocket (from `../node_modules/react-native/`) + - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) + - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) + - React-debug (from `../node_modules/react-native/ReactCommon/react/debug`) + - React-defaultsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/defaults`) + - React-domnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/dom`) + - React-Fabric (from `../node_modules/react-native/ReactCommon`) + - React-FabricComponents (from `../node_modules/react-native/ReactCommon`) + - React-FabricImage (from `../node_modules/react-native/ReactCommon`) + - React-featureflags (from `../node_modules/react-native/ReactCommon/react/featureflags`) + - React-featureflagsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/featureflags`) + - React-graphics (from `../node_modules/react-native/ReactCommon/react/renderer/graphics`) + - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) + - React-idlecallbacksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`) + - React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) + - React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`) + - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) + - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) + - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`) + - React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`) + - React-logger (from `../node_modules/react-native/ReactCommon/logger`) + - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) + - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - react-native-pubky (from `../..`) + - React-nativeconfig (from `../node_modules/react-native/ReactCommon`) + - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) + - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) + - React-performancetimeline (from `../node_modules/react-native/ReactCommon/react/performance/timeline`) + - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) + - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) + - React-RCTAppDelegate (from `../node_modules/react-native/Libraries/AppDelegate`) + - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) + - React-RCTFabric (from `../node_modules/react-native/React`) + - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) + - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) + - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) + - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) + - React-RCTText (from `../node_modules/react-native/Libraries/Text`) + - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) + - React-rendererconsistency (from `../node_modules/react-native/ReactCommon/react/renderer/consistency`) + - React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`) + - React-rncore (from `../node_modules/react-native/ReactCommon`) + - React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) + - React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) + - React-RuntimeHermes (from `../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) + - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) + - ReactCodegen (from `build/generated/ios`) + - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) + - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) + +SPEC REPOS: + trunk: + - SocketRocket + +EXTERNAL SOURCES: + boost: + :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" + DoubleConversion: + :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + FBLazyVector: + :path: "../node_modules/react-native/Libraries/FBLazyVector" + fmt: + :podspec: "../node_modules/react-native/third-party-podspecs/fmt.podspec" + glog: + :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" + hermes-engine: + :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" + :tag: hermes-2024-08-15-RNv0.75.1-4b3bf912cc0f705b51b71ce1a5b8bd79b93a451b + RCT-Folly: + :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" + RCTDeprecation: + :path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" + RCTRequired: + :path: "../node_modules/react-native/Libraries/Required" + RCTTypeSafety: + :path: "../node_modules/react-native/Libraries/TypeSafety" + React: + :path: "../node_modules/react-native/" + React-callinvoker: + :path: "../node_modules/react-native/ReactCommon/callinvoker" + React-Core: + :path: "../node_modules/react-native/" + React-CoreModules: + :path: "../node_modules/react-native/React/CoreModules" + React-cxxreact: + :path: "../node_modules/react-native/ReactCommon/cxxreact" + React-debug: + :path: "../node_modules/react-native/ReactCommon/react/debug" + React-defaultsnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/defaults" + React-domnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/dom" + React-Fabric: + :path: "../node_modules/react-native/ReactCommon" + React-FabricComponents: + :path: "../node_modules/react-native/ReactCommon" + React-FabricImage: + :path: "../node_modules/react-native/ReactCommon" + React-featureflags: + :path: "../node_modules/react-native/ReactCommon/react/featureflags" + React-featureflagsnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/featureflags" + React-graphics: + :path: "../node_modules/react-native/ReactCommon/react/renderer/graphics" + React-hermes: + :path: "../node_modules/react-native/ReactCommon/hermes" + React-idlecallbacksnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" + React-ImageManager: + :path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" + React-jserrorhandler: + :path: "../node_modules/react-native/ReactCommon/jserrorhandler" + React-jsi: + :path: "../node_modules/react-native/ReactCommon/jsi" + React-jsiexecutor: + :path: "../node_modules/react-native/ReactCommon/jsiexecutor" + React-jsinspector: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern" + React-jsitracing: + :path: "../node_modules/react-native/ReactCommon/hermes/executor/" + React-logger: + :path: "../node_modules/react-native/ReactCommon/logger" + React-Mapbuffer: + :path: "../node_modules/react-native/ReactCommon" + React-microtasksnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + react-native-pubky: + :path: "../.." + React-nativeconfig: + :path: "../node_modules/react-native/ReactCommon" + React-NativeModulesApple: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" + React-perflogger: + :path: "../node_modules/react-native/ReactCommon/reactperflogger" + React-performancetimeline: + :path: "../node_modules/react-native/ReactCommon/react/performance/timeline" + React-RCTActionSheet: + :path: "../node_modules/react-native/Libraries/ActionSheetIOS" + React-RCTAnimation: + :path: "../node_modules/react-native/Libraries/NativeAnimation" + React-RCTAppDelegate: + :path: "../node_modules/react-native/Libraries/AppDelegate" + React-RCTBlob: + :path: "../node_modules/react-native/Libraries/Blob" + React-RCTFabric: + :path: "../node_modules/react-native/React" + React-RCTImage: + :path: "../node_modules/react-native/Libraries/Image" + React-RCTLinking: + :path: "../node_modules/react-native/Libraries/LinkingIOS" + React-RCTNetwork: + :path: "../node_modules/react-native/Libraries/Network" + React-RCTSettings: + :path: "../node_modules/react-native/Libraries/Settings" + React-RCTText: + :path: "../node_modules/react-native/Libraries/Text" + React-RCTVibration: + :path: "../node_modules/react-native/Libraries/Vibration" + React-rendererconsistency: + :path: "../node_modules/react-native/ReactCommon/react/renderer/consistency" + React-rendererdebug: + :path: "../node_modules/react-native/ReactCommon/react/renderer/debug" + React-rncore: + :path: "../node_modules/react-native/ReactCommon" + React-RuntimeApple: + :path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios" + React-RuntimeCore: + :path: "../node_modules/react-native/ReactCommon/react/runtime" + React-runtimeexecutor: + :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" + React-RuntimeHermes: + :path: "../node_modules/react-native/ReactCommon/react/runtime" + React-runtimescheduler: + :path: "../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" + React-utils: + :path: "../node_modules/react-native/ReactCommon/react/utils" + ReactCodegen: + :path: build/generated/ios + ReactCommon: + :path: "../node_modules/react-native/ReactCommon" + Yoga: + :path: "../node_modules/react-native/ReactCommon/yoga" + +SPEC CHECKSUMS: + boost: 4cb898d0bf20404aab1850c656dcea009429d6c1 + DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 + FBLazyVector: 38bb611218305c3bc61803e287b8a81c6f63b619 + fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 + glog: 69ef571f3de08433d766d614c73a9838a06bf7eb + hermes-engine: 3b6e0717ca847e2fc90a201e59db36caf04dee88 + RCT-Folly: 4464f4d875961fce86008d45f4ecf6cef6de0740 + RCTDeprecation: 34cbf122b623037ea9facad2e92e53434c5c7422 + RCTRequired: 24c446d7bcd0f517d516b6265d8df04dc3eb1219 + RCTTypeSafety: ef5e91bd791abd3a99b2c75fd565791102a66352 + React: 643f06bc294806d2db2526b424fdf759e107f514 + React-callinvoker: 34d1fa0c340104f324e2521f546196beb44dfad2 + React-Core: facd883836d8d1cc1949d2053c58eab5fb22eb75 + React-CoreModules: f92a2cb11d22f6066823ca547c61e900325dfe44 + React-cxxreact: f5595a4cbfe5a4e9d401dffa2c1c78bbbbbe75e4 + React-debug: 4a91c177b5b2efcc546fb50bc2f676f3f589efab + React-defaultsnativemodule: bb94c3db425b01c760f41a253de8536b3f5497f0 + React-domnativemodule: 6c581fd39812cafb024171e091c00905b2c3a3e2 + React-Fabric: a33cc1fdc62a3085774783bb30970531589d2028 + React-FabricComponents: 98de5f94cbd35d407f4fc78855298b562d8289cb + React-FabricImage: 0ce8fd83844d9edef5825116d38f0e208b9ad786 + React-featureflags: 37a78859ad71db758e2efdcbdb7384afefa8701e + React-featureflagsnativemodule: 52b46e161a151b4653cf1762285e8e899d534e3f + React-graphics: c16f1bab97a5d473831a79360d84300e93a614e5 + React-hermes: 7801f8c0e12f326524b461dc368d3e74f3d2a385 + React-idlecallbacksnativemodule: 58de2ac968ee80947d19dc8fe20def607e5c2de8 + React-ImageManager: 98a1e5b0b05528dde47ebcd953d916ac66d46c09 + React-jserrorhandler: 08f1c3465a71a6549c27ad82809ce145ad52d4f1 + React-jsi: 161428ab2c706d5fcd9878d260ff1513fdb356ab + React-jsiexecutor: abfdc7526151c6755f836235bbaa53b267a0803c + React-jsinspector: f0786053a1a258a4d8dde859d1a820c26ee686f0 + React-jsitracing: 52b849a77d02e2dc262a3031454c23be8dabb4d9 + React-logger: 8db32983d75dc2ad54f278f344ccb9b256e694fc + React-Mapbuffer: 1c08607305558666fd16678b85ef135e455d5c96 + React-microtasksnativemodule: 87b8de96f937faefece8afd2cb3a518321b2ef99 + react-native-pubky: 245e7fdcf4672621087a6c9f6a528c9e179b2a59 + React-nativeconfig: 57781b79e11d5af7573e6f77cbf1143b71802a6d + React-NativeModulesApple: 7ff2e2cfb2e5fa5bdedcecf28ce37e696c6ef1e1 + React-perflogger: 8a360ccf603de6ddbe9ff8f54383146d26e6c936 + React-performancetimeline: 3cfec915adcb3653a5a633b41e711903844c35d8 + React-RCTActionSheet: 1c0e26a88eec41215089cf4436e38188cfe9f01a + React-RCTAnimation: d87207841b1e2ae1389e684262ea8c73c887cb04 + React-RCTAppDelegate: 4ec7824c0cc9cc4b146ca8ee0fd81b10c316a440 + React-RCTBlob: 79b42cb7db55f34079297687a480dbcf37f023f6 + React-RCTFabric: 1dd1661db93716f8cb116e451bd9c211a8d15716 + React-RCTImage: 0c10a75de59f7384a2a55545d5f36fe783e6ecda + React-RCTLinking: bf08f4f655bf777af292b8d97449072c8bb196ca + React-RCTNetwork: 1b690846b40fc5685af58e088720657db6814637 + React-RCTSettings: 097e420926dd44153fb25174835b572aded224d6 + React-RCTText: d8fe2ae9f95b2ccd03b2f534286e938254791992 + React-RCTVibration: 976466dba32c0981a836e45ce38bcd4c8d6d924e + React-rendererconsistency: ee0d6f1b4420e1ad5bb01c02170e7ecbd278e307 + React-rendererdebug: 7fbf02f30d1e0bb0d96d65cf2548219cb53b29b6 + React-rncore: 7ffc5be03adbf0a5cbf1b654483f487a899cff08 + React-RuntimeApple: e623f002e1871de30a443291171d3f2fb134a6ec + React-RuntimeCore: a67357d4f073b1dbe6fbefc5273072027f201e1c + React-runtimeexecutor: 5bb52479abf8081086afb0397dc33dc97202a439 + React-RuntimeHermes: 860cf64708a12a2fa62366fe51fe000121fa031b + React-runtimescheduler: fff88d51ad2c8815fc75930dbac224d680593e6b + React-utils: 81a715d9c0a2a49047e77a86f3a2247408540deb + ReactCodegen: 60973d382704c793c605b9be0fc7f31cb279442f + ReactCommon: 6ef348087d250257c44c0204461c03f036650e9b + SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d + Yoga: 2a45d7e59592db061217551fd3bbe2dd993817ae + +PODFILE CHECKSUM: 8e3998c3768d2f2ed5f00c381f50bace44e02e3f + +COCOAPODS: 1.15.2 diff --git a/example/ios/PubkyExample-Bridging-Header.h b/example/ios/PubkyExample-Bridging-Header.h new file mode 100644 index 0000000..e11d920 --- /dev/null +++ b/example/ios/PubkyExample-Bridging-Header.h @@ -0,0 +1,3 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// diff --git a/example/ios/PubkyExample.xcodeproj/project.pbxproj b/example/ios/PubkyExample.xcodeproj/project.pbxproj new file mode 100644 index 0000000..299974a --- /dev/null +++ b/example/ios/PubkyExample.xcodeproj/project.pbxproj @@ -0,0 +1,713 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 00E356F31AD99517003FC87E /* PubkyExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* PubkyExampleTests.m */; }; + 0C80B921A6F3F58F76C31292 /* libPods-PubkyExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-PubkyExample.a */; }; + 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 7699B88040F8A987B510C191 /* libPods-PubkyExample-PubkyExampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19F6CBCC0A4E27FBF8BF4A61 /* libPods-PubkyExample-PubkyExampleTests.a */; }; + 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; + B2A00F0E44E958DA013ABFD6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = AF7D486B6E71CFB714642CFB /* PrivacyInfo.xcprivacy */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13B07F861A680F5B00A75B9A; + remoteInfo = PubkyExample; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 00E356EE1AD99517003FC87E /* PubkyExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PubkyExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 00E356F21AD99517003FC87E /* PubkyExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PubkyExampleTests.m; sourceTree = ""; }; + 13B07F961A680F5B00A75B9A /* PubkyExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PubkyExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = PubkyExample/AppDelegate.h; sourceTree = ""; }; + 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = PubkyExample/AppDelegate.mm; sourceTree = ""; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = PubkyExample/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = PubkyExample/Info.plist; sourceTree = ""; }; + 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = PubkyExample/main.m; sourceTree = ""; }; + 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = PubkyExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 19F6CBCC0A4E27FBF8BF4A61 /* libPods-PubkyExample-PubkyExampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PubkyExample-PubkyExampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B4392A12AC88292D35C810B /* Pods-PubkyExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PubkyExample.debug.xcconfig"; path = "Target Support Files/Pods-PubkyExample/Pods-PubkyExample.debug.xcconfig"; sourceTree = ""; }; + 5709B34CF0A7D63546082F79 /* Pods-PubkyExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PubkyExample.release.xcconfig"; path = "Target Support Files/Pods-PubkyExample/Pods-PubkyExample.release.xcconfig"; sourceTree = ""; }; + 5B7EB9410499542E8C5724F5 /* Pods-PubkyExample-PubkyExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PubkyExample-PubkyExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-PubkyExample-PubkyExampleTests/Pods-PubkyExample-PubkyExampleTests.debug.xcconfig"; sourceTree = ""; }; + 5DCACB8F33CDC322A6C60F78 /* libPods-PubkyExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PubkyExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = PubkyExample/LaunchScreen.storyboard; sourceTree = ""; }; + 89C6BE57DB24E9ADA2F236DE /* Pods-PubkyExample-PubkyExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PubkyExample-PubkyExampleTests.release.xcconfig"; path = "Target Support Files/Pods-PubkyExample-PubkyExampleTests/Pods-PubkyExample-PubkyExampleTests.release.xcconfig"; sourceTree = ""; }; + AF7D486B6E71CFB714642CFB /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = PubkyExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; + ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 00E356EB1AD99517003FC87E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7699B88040F8A987B510C191 /* libPods-PubkyExample-PubkyExampleTests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0C80B921A6F3F58F76C31292 /* libPods-PubkyExample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00E356EF1AD99517003FC87E /* PubkyExampleTests */ = { + isa = PBXGroup; + children = ( + 00E356F21AD99517003FC87E /* PubkyExampleTests.m */, + 00E356F01AD99517003FC87E /* Supporting Files */, + ); + path = PubkyExampleTests; + sourceTree = ""; + }; + 00E356F01AD99517003FC87E /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 00E356F11AD99517003FC87E /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 13B07FAE1A68108700A75B9A /* PubkyExample */ = { + isa = PBXGroup; + children = ( + 13B07FAF1A68108700A75B9A /* AppDelegate.h */, + 13B07FB01A68108700A75B9A /* AppDelegate.mm */, + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 13B07FB61A68108700A75B9A /* Info.plist */, + 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, + 13B07FB71A68108700A75B9A /* main.m */, + 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, + AF7D486B6E71CFB714642CFB /* PrivacyInfo.xcprivacy */, + ); + name = PubkyExample; + sourceTree = ""; + }; + 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { + isa = PBXGroup; + children = ( + ED297162215061F000B7C4FE /* JavaScriptCore.framework */, + 5DCACB8F33CDC322A6C60F78 /* libPods-PubkyExample.a */, + 19F6CBCC0A4E27FBF8BF4A61 /* libPods-PubkyExample-PubkyExampleTests.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + ); + name = Libraries; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + 13B07FAE1A68108700A75B9A /* PubkyExample */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 00E356EF1AD99517003FC87E /* PubkyExampleTests */, + 83CBBA001A601CBA00E9B192 /* Products */, + 2D16E6871FA4F8E400B85C8A /* Frameworks */, + BBD78D7AC51CEA395F1C20DB /* Pods */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* PubkyExample.app */, + 00E356EE1AD99517003FC87E /* PubkyExampleTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + BBD78D7AC51CEA395F1C20DB /* Pods */ = { + isa = PBXGroup; + children = ( + 3B4392A12AC88292D35C810B /* Pods-PubkyExample.debug.xcconfig */, + 5709B34CF0A7D63546082F79 /* Pods-PubkyExample.release.xcconfig */, + 5B7EB9410499542E8C5724F5 /* Pods-PubkyExample-PubkyExampleTests.debug.xcconfig */, + 89C6BE57DB24E9ADA2F236DE /* Pods-PubkyExample-PubkyExampleTests.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 00E356ED1AD99517003FC87E /* PubkyExampleTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "PubkyExampleTests" */; + buildPhases = ( + A55EABD7B0C7F3A422A6CC61 /* [CP] Check Pods Manifest.lock */, + 00E356EA1AD99517003FC87E /* Sources */, + 00E356EB1AD99517003FC87E /* Frameworks */, + 00E356EC1AD99517003FC87E /* Resources */, + C59DA0FBD6956966B86A3779 /* [CP] Embed Pods Frameworks */, + F6A41C54EA430FDDC6A6ED99 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 00E356F51AD99517003FC87E /* PBXTargetDependency */, + ); + name = PubkyExampleTests; + productName = PubkyExampleTests; + productReference = 00E356EE1AD99517003FC87E /* PubkyExampleTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 13B07F861A680F5B00A75B9A /* PubkyExample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "PubkyExample" */; + buildPhases = ( + C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */, + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */, + E235C05ADACE081382539298 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = PubkyExample; + productName = PubkyExample; + productReference = 13B07F961A680F5B00A75B9A /* PubkyExample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1210; + TargetAttributes = { + 00E356ED1AD99517003FC87E = { + CreatedOnToolsVersion = 6.2; + TestTargetID = 13B07F861A680F5B00A75B9A; + }; + 13B07F861A680F5B00A75B9A = { + LastSwiftMigration = 1120; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "PubkyExample" */; + compatibilityVersion = "Xcode 12.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* PubkyExample */, + 00E356ED1AD99517003FC87E /* PubkyExampleTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 00E356EC1AD99517003FC87E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */, + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + B2A00F0E44E958DA013ABFD6 /* PrivacyInfo.xcprivacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/.xcode.env.local", + "$(SRCROOT)/.xcode.env", + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; + }; + 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PubkyExample/Pods-PubkyExample-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PubkyExample/Pods-PubkyExample-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PubkyExample/Pods-PubkyExample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + A55EABD7B0C7F3A422A6CC61 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PubkyExample-PubkyExampleTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PubkyExample-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C59DA0FBD6956966B86A3779 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PubkyExample-PubkyExampleTests/Pods-PubkyExample-PubkyExampleTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PubkyExample-PubkyExampleTests/Pods-PubkyExample-PubkyExampleTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PubkyExample-PubkyExampleTests/Pods-PubkyExample-PubkyExampleTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E235C05ADACE081382539298 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PubkyExample/Pods-PubkyExample-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PubkyExample/Pods-PubkyExample-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PubkyExample/Pods-PubkyExample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + F6A41C54EA430FDDC6A6ED99 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PubkyExample-PubkyExampleTests/Pods-PubkyExample-PubkyExampleTests-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PubkyExample-PubkyExampleTests/Pods-PubkyExample-PubkyExampleTests-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PubkyExample-PubkyExampleTests/Pods-PubkyExample-PubkyExampleTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 00E356EA1AD99517003FC87E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 00E356F31AD99517003FC87E /* PubkyExampleTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */, + 13B07FC11A68108700A75B9A /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 00E356F51AD99517003FC87E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13B07F861A680F5B00A75B9A /* PubkyExample */; + targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 00E356F61AD99517003FC87E /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5B7EB9410499542E8C5724F5 /* Pods-PubkyExample-PubkyExampleTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = PubkyExampleTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + "$(inherited)", + ); + PRODUCT_BUNDLE_IDENTIFIER = pubky.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PubkyExample.app/PubkyExample"; + }; + name = Debug; + }; + 00E356F71AD99517003FC87E /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89C6BE57DB24E9ADA2F236DE /* Pods-PubkyExample-PubkyExampleTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + COPY_PHASE_STRIP = NO; + INFOPLIST_FILE = PubkyExampleTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + "$(inherited)", + ); + PRODUCT_BUNDLE_IDENTIFIER = pubky.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PubkyExample.app/PubkyExample"; + }; + name = Release; + }; + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-PubkyExample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 1; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = PubkyExample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = pubky.example; + PRODUCT_NAME = PubkyExample; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-PubkyExample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 1; + INFOPLIST_FILE = PubkyExample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = pubky.example; + PRODUCT_NAME = PubkyExample; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CC = ""; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CXX = ""; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.4; + LD = ""; + LDPLUSPLUS = ""; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = ( + "\"$(SDKROOT)/usr/lib/swift\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-DFOLLY_NO_CONFIG", + "-DFOLLY_MOBILE=1", + "-DFOLLY_USE_LIBCPP=1", + "-DFOLLY_CFG_NO_COROUTINES=1", + "-DFOLLY_HAVE_CLOCK_GETTIME=1", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; + USE_HERMES = true; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CC = ""; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + CXX = ""; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.4; + LD = ""; + LDPLUSPLUS = ""; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = ( + "\"$(SDKROOT)/usr/lib/swift\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-DFOLLY_NO_CONFIG", + "-DFOLLY_MOBILE=1", + "-DFOLLY_USE_LIBCPP=1", + "-DFOLLY_CFG_NO_COROUTINES=1", + "-DFOLLY_HAVE_CLOCK_GETTIME=1", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + USE_HERMES = true; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "PubkyExampleTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 00E356F61AD99517003FC87E /* Debug */, + 00E356F71AD99517003FC87E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "PubkyExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "PubkyExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/example/ios/PubkyExample.xcodeproj/xcshareddata/xcschemes/PubkyExample.xcscheme b/example/ios/PubkyExample.xcodeproj/xcshareddata/xcschemes/PubkyExample.xcscheme new file mode 100644 index 0000000..832b6bc --- /dev/null +++ b/example/ios/PubkyExample.xcodeproj/xcshareddata/xcschemes/PubkyExample.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/PubkyExample.xcworkspace/contents.xcworkspacedata b/example/ios/PubkyExample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..e128c78 --- /dev/null +++ b/example/ios/PubkyExample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/example/ios/PubkyExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/PubkyExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/ios/PubkyExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/PubkyExample/AppDelegate.h b/example/ios/PubkyExample/AppDelegate.h new file mode 100644 index 0000000..5d28082 --- /dev/null +++ b/example/ios/PubkyExample/AppDelegate.h @@ -0,0 +1,6 @@ +#import +#import + +@interface AppDelegate : RCTAppDelegate + +@end diff --git a/example/ios/PubkyExample/AppDelegate.mm b/example/ios/PubkyExample/AppDelegate.mm new file mode 100644 index 0000000..6c5d572 --- /dev/null +++ b/example/ios/PubkyExample/AppDelegate.mm @@ -0,0 +1,31 @@ +#import "AppDelegate.h" + +#import + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + self.moduleName = @"PubkyExample"; + // You can add your custom initial props in the dictionary below. + // They will be passed down to the ViewController used by React Native. + self.initialProps = @{}; + + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge +{ + return [self bundleURL]; +} + +- (NSURL *)bundleURL +{ +#if DEBUG + return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; +#else + return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; +#endif +} + +@end diff --git a/example/ios/PubkyExample/Images.xcassets/AppIcon.appiconset/Contents.json b/example/ios/PubkyExample/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..8121323 --- /dev/null +++ b/example/ios/PubkyExample/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,53 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/example/ios/PubkyExample/Images.xcassets/Contents.json b/example/ios/PubkyExample/Images.xcassets/Contents.json new file mode 100644 index 0000000..2d92bd5 --- /dev/null +++ b/example/ios/PubkyExample/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/ios/PubkyExample/Info.plist b/example/ios/PubkyExample/Info.plist new file mode 100644 index 0000000..592933e --- /dev/null +++ b/example/ios/PubkyExample/Info.plist @@ -0,0 +1,52 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + PubkyExample + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + NSAppTransportSecurity + + + NSAllowsArbitraryLoads + + NSAllowsLocalNetworking + + + NSLocationWhenInUseUsageDescription + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/example/ios/PubkyExample/LaunchScreen.storyboard b/example/ios/PubkyExample/LaunchScreen.storyboard new file mode 100644 index 0000000..b9d5f6e --- /dev/null +++ b/example/ios/PubkyExample/LaunchScreen.storyboard @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/PubkyExample/PrivacyInfo.xcprivacy b/example/ios/PubkyExample/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..41b8317 --- /dev/null +++ b/example/ios/PubkyExample/PrivacyInfo.xcprivacy @@ -0,0 +1,37 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + diff --git a/example/ios/PubkyExample/main.m b/example/ios/PubkyExample/main.m new file mode 100644 index 0000000..d645c72 --- /dev/null +++ b/example/ios/PubkyExample/main.m @@ -0,0 +1,10 @@ +#import + +#import "AppDelegate.h" + +int main(int argc, char *argv[]) +{ + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/example/ios/PubkyExampleTests/Info.plist b/example/ios/PubkyExampleTests/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/example/ios/PubkyExampleTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/example/ios/PubkyExampleTests/PubkyExampleTests.m b/example/ios/PubkyExampleTests/PubkyExampleTests.m new file mode 100644 index 0000000..b95e808 --- /dev/null +++ b/example/ios/PubkyExampleTests/PubkyExampleTests.m @@ -0,0 +1,66 @@ +#import +#import + +#import +#import + +#define TIMEOUT_SECONDS 600 +#define TEXT_TO_LOOK_FOR @"Welcome to React" + +@interface PubkyExampleTests : XCTestCase + +@end + +@implementation PubkyExampleTests + +- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test +{ + if (test(view)) { + return YES; + } + for (UIView *subview in [view subviews]) { + if ([self findSubviewInView:subview matching:test]) { + return YES; + } + } + return NO; +} + +- (void)testRendersWelcomeScreen +{ + UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; + BOOL foundElement = NO; + + __block NSString *redboxError = nil; +#ifdef DEBUG + RCTSetLogFunction( + ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { + if (level >= RCTLogLevelError) { + redboxError = message; + } + }); +#endif + + while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + + foundElement = [self findSubviewInView:vc.view + matching:^BOOL(UIView *view) { + if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { + return YES; + } + return NO; + }]; + } + +#ifdef DEBUG + RCTSetLogFunction(RCTDefaultLogFunction); +#endif + + XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); + XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); +} + +@end diff --git a/example/jest.config.js b/example/jest.config.js new file mode 100644 index 0000000..8eb675e --- /dev/null +++ b/example/jest.config.js @@ -0,0 +1,3 @@ +module.exports = { + preset: 'react-native', +}; diff --git a/example/metro.config.js b/example/metro.config.js new file mode 100644 index 0000000..78e4f81 --- /dev/null +++ b/example/metro.config.js @@ -0,0 +1,18 @@ +const path = require('path'); +const { getDefaultConfig } = require('@react-native/metro-config'); +const { getConfig } = require('react-native-builder-bob/metro-config'); +const pkg = require('../package.json'); + +const root = path.resolve(__dirname, '..'); + +/** + * Metro configuration + * https://facebook.github.io/metro/docs/configuration + * + * @type {import('metro-config').MetroConfig} + */ +module.exports = getConfig(getDefaultConfig(__dirname), { + root, + pkg, + project: __dirname, +}); diff --git a/example/package.json b/example/package.json new file mode 100644 index 0000000..11c5aa2 --- /dev/null +++ b/example/package.json @@ -0,0 +1,29 @@ +{ + "name": "react-native-pubky-example", + "version": "0.0.1", + "private": true, + "scripts": { + "android": "react-native run-android", + "ios": "react-native run-ios", + "start": "react-native start", + "build:android": "react-native build-android --extra-params \"--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a\"", + "build:ios": "cd ios && rm -rf Podfile.lock Pods && pod install && react-native build-ios --scheme PubkyExample --mode Debug --extra-params \"-sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO\"", + "postinstall": "cd ios && pod install && cd .." + }, + "dependencies": { + "react": "18.3.1", + "react-native": "0.75.2" + }, + "devDependencies": { + "@babel/core": "^7.20.0", + "@babel/preset-env": "^7.20.0", + "@babel/runtime": "^7.20.0", + "@react-native/babel-preset": "0.75.2", + "@react-native/metro-config": "0.75.2", + "@react-native/typescript-config": "0.75.2", + "react-native-builder-bob": "^0.30.2" + }, + "engines": { + "node": ">=18" + } +} diff --git a/example/react-native.config.js b/example/react-native.config.js new file mode 100644 index 0000000..0b9606d --- /dev/null +++ b/example/react-native.config.js @@ -0,0 +1,15 @@ +const path = require('path'); +const pkg = require('../package.json'); + +module.exports = { + project: { + ios: { + automaticPodsInstallation: true, + }, + }, + dependencies: { + [pkg.name]: { + root: path.join(__dirname, '..'), + }, + }, +}; diff --git a/example/src/App.tsx b/example/src/App.tsx new file mode 100644 index 0000000..480e4f2 --- /dev/null +++ b/example/src/App.tsx @@ -0,0 +1,37 @@ +import { StyleSheet, View, Button } from 'react-native'; +import { auth } from 'react-native-pubky'; + +export default function App() { + return ( + + +
+
+

Scan or copy Pubky auth URL

+
+ +
+ +
+ + ` + } + + _setQr(canvas) { + QRCode.toCanvas(canvas, this.authUrl, { + margin: 2, + scale: 8, + + color: { + light: '#fff', + dark: '#000', + }, + }); + } + + _switchOpen() { + this.open = !this.open + } + + async _copyToClipboard() { + try { + await navigator.clipboard.writeText(this.authUrl); + this.showCopied = true; + setTimeout(() => { this.showCopied = false }, 1000) + } catch (error) { + console.error('Failed to copy text: ', error); + } + } + + + + render() { + return html` +
+ +
+
+

Scan or copy Pubky auth URL

+
+ +
+ +
+
+ ` + } + + static get styles() { + return css` + * { + box-sizing: border-box; + } + + :host { + --full-width: 22rem; + --full-height: 31rem; + --header-height: 3rem; + --closed-width: 3rem; + } + + a { + text-decoration: none; + } + + button { + padding: 0; + background: none; + border: none; + color: inherit; + cursor: pointer; + } + + p { + margin: 0; + } + + /** End reset */ + + #widget { + color: white; + + position: fixed; + top: 1rem; + right: 1rem; + + background-color:red; + + z-index: 99999; + overflow: hidden; + background: rgba(43, 43, 43, .7372549019607844); + border: 1px solid #3c3c3c; + box-shadow: 0 10px 34px -10px rgba(236, 243, 222, .05); + border-radius: 8px; + -webkit-backdrop-filter: blur(8px); + backdrop-filter: blur(8px); + + width: var(--closed-width); + height: var(--header-height); + + will-change: height,width; + transition-property: height, width; + transition-duration: 80ms; + transition-timing-function: ease-in; + } + + #widget.open{ + width: var(--full-width); + height: var(--full-height); + } + + .header { + height: var(--header-height); + display: flex; + justify-content: center; + align-items: center; + } + + #widget + .header .text { + display: none; + font-weight: bold; + } + #widget.open + .header .text { + display: block + } + + #widget.open + .header { + width: var(--full-width); + justify-content: center; + } + + #pubky-icon { + height: 100%; + width: 100%; + } + + #widget.open + #pubky-icon { + width: var(--header-height); + height: 74%; + } + + #widget-content{ + width: var(--full-width); + padding: 0 1rem + } + + #widget p { + font-size: .87rem; + line-height: 1rem; + text-align: center; + color: #fff; + opacity: .5; + + /* Fix flash wrap in open animation */ + text-wrap: nowrap; + } + + #qr { + width: 18em !important; + height: 18em !important; + } + + .card { + position: relative; + background: #3b3b3b; + border-radius: 5px; + padding: 1rem; + margin-top: 1rem; + display: flex; + justify-content: center; + align-items: center; + } + + .card.url { + padding: .625rem; + justify-content: space-between; + max-width:100%; + } + + .url p { + display: flex; + align-items: center; + + line-height: 1!important; + width: 93%; + overflow: hidden; + text-overflow: ellipsis; + text-wrap: nowrap; + } + + .line { + height: 1px; + background-color: #3b3b3b; + flex: 1 1; + margin-bottom: 1rem; + } + + .copied { + will-change: opacity; + transition-property: opacity; + transition-duration: 80ms; + transition-timing-function: ease-in; + + opacity: 0; + + position: absolute; + right: 0; + top: -1.6rem; + font-size: 0.9em; + background: rgb(43 43 43 / 98%); + padding: .5rem; + border-radius: .3rem; + color: #ddd; + } + + .copied.show { + opacity:1 + } + ` + } +} + +window.customElements.define('pubky-auth-widget', PubkyAuthWidget) diff --git a/rust/pubky/examples/authz/README.md b/rust/pubky/examples/authz/README.md new file mode 100644 index 0000000..905bda6 --- /dev/null +++ b/rust/pubky/examples/authz/README.md @@ -0,0 +1,29 @@ +# Pubky Auth Example + +This example shows 3rd party authorization in Pubky. + +It consists of 2 parts: + +1. [3rd party app](./3rd-party-app): A web component showing the how to implement a Pubky Auth widget. +2. [Authenticator CLI](./authenticator): A CLI showing the authenticator (key chain) asking user for consent and generating the AuthToken. + +## Usage + +First you need to be running a local testnet Homeserver, in the root of this repo run + +```bash +cargo run --bin pubky_homeserver -- --testnet +``` + +Run the frontend of the 3rd party app + +```bash +cd ./3rd-party-app +npm start +``` + +Copy the Pubky Auth URL from the frontend. + +Finally run the CLI to paste the Pubky Auth in. + +You should see the frontend reacting by showing the success of authorization and session details. diff --git a/rust/pubky/examples/authz/authenticator/Cargo.lock b/rust/pubky/examples/authz/authenticator/Cargo.lock new file mode 100644 index 0000000..f2fe8b2 --- /dev/null +++ b/rust/pubky/examples/authz/authenticator/Cargo.lock @@ -0,0 +1,1906 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + +[[package]] +name = "arrayref" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "authenticator" +version = "0.1.0" +dependencies = [ + "anyhow", + "keyring", + "pubky", + "pubky-common", + "rpassword", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base32" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "blake3" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cc" +version = "1.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4934e6b7e8419148b6ef56950d277af8561060b56afd59e2aadf98b59fce6baa" +dependencies = [ + "cookie", + "idna 0.5.0", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "critical-section" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64009896348fc5af4222e9cf7d7d82a95a256c634ebcf61c53e4ea461422242" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "crypto_secretbox" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" +dependencies = [ + "aead", + "cipher", + "generic-array", + "poly1305", + "salsa20", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "serde", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "keyring" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b9af47ded4df3067484d7d45758ca2b36bd083bf6d024c2952bbd8af1cdaa4" +dependencies = [ + "byteorder", + "linux-keyutils", + "security-framework", + "windows-sys 0.59.0", +] + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "linux-keyutils" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761e49ec5fd8a5a463f9b84e877c373d888935b71c6be78f3767fe2ae6bed18e" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lru" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" + +[[package]] +name = "mainline" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b751ffb57303217bcae8f490eee6044a5b40eadf6ca05ff476cad37e7b7970d" +dependencies = [ + "bytes", + "crc", + "ed25519-dalek", + "flume", + "lru", + "rand", + "serde", + "serde_bencode", + "serde_bytes", + "sha1_smol", + "thiserror", + "tracing", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "object" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkarr" +version = "2.2.0" +source = "git+https://github.com/Pubky/pkarr?branch=v3#17975121c809d97dcad907fbb2ffc782e994d270" +dependencies = [ + "base32", + "bytes", + "document-features", + "dyn-clone", + "ed25519-dalek", + "flume", + "futures", + "js-sys", + "lru", + "mainline", + "rand", + "self_cell", + "serde", + "simple-dns", + "thiserror", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "postcard" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7f0a8d620d71c457dd1d47df76bb18960378da56af4527aaa10f515eee732e" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "pubky" +version = "0.1.0" +dependencies = [ + "argon2", + "bytes", + "js-sys", + "pkarr", + "pubky-common", + "reqwest", + "thiserror", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "pubky-common" +version = "0.1.0" +dependencies = [ + "base32", + "blake3", + "crypto_secretbox", + "ed25519-dalek", + "js-sys", + "once_cell", + "pkarr", + "postcard", + "rand", + "serde", + "thiserror", +] + +[[package]] +name = "publicsuffix" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" +dependencies = [ + "idna 0.3.0", + "psl-types", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "reqwest" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +dependencies = [ + "base64", + "bytes", + "cookie", + "cookie_store", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "rpassword" +version = "7.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" +dependencies = [ + "libc", + "rtoolbox", + "windows-sys 0.48.0", +] + +[[package]] +name = "rtoolbox" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "self_cell" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bencode" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a70dfc7b7438b99896e7f8992363ab8e2c4ba26aa5ec675d32d1c3c2c33d413e" +dependencies = [ + "serde", + "serde_bytes", +] + +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + +[[package]] +name = "simple-dns" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01607fe2e61894468c6dc0b26103abb073fb08b79a3d9e4b6d76a1a341549958" +dependencies = [ + "bitflags", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna 0.5.0", + "percent-encoding", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/rust/pubky/examples/authz/authenticator/Cargo.toml b/rust/pubky/examples/authz/authenticator/Cargo.toml new file mode 100644 index 0000000..932701b --- /dev/null +++ b/rust/pubky/examples/authz/authenticator/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "authenticator" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.86" +base64 = "0.22.1" +clap = { version = "4.5.16", features = ["derive"] } +pubky = { version = "0.1.0", path = "../../../pubky" } +pubky-common = { version = "0.1.0", path = "../../../pubky-common" } +rpassword = "7.3.1" +tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] } +url = "2.5.2" diff --git a/rust/pubky/examples/authz/authenticator/src/main.rs b/rust/pubky/examples/authz/authenticator/src/main.rs new file mode 100644 index 0000000..410b8f5 --- /dev/null +++ b/rust/pubky/examples/authz/authenticator/src/main.rs @@ -0,0 +1,80 @@ +use anyhow::Result; +use clap::Parser; +use pubky::PubkyClient; +use std::path::PathBuf; +use url::Url; + +use pubky_common::{capabilities::Capability, crypto::PublicKey}; + +/// local testnet HOMESERVER +const HOMESERVER: &str = "8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo"; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Cli { + /// Path to a recovery_file of the Pubky you want to sign in with + recovery_file: PathBuf, + + /// Pubky Auth url + url: Url, +} + +#[tokio::main] +async fn main() -> Result<()> { + let cli = Cli::parse(); + + let recovery_file = std::fs::read(&cli.recovery_file)?; + println!("\nSuccessfully opened recovery file"); + + let url = cli.url; + + let caps = url + .query_pairs() + .filter_map(|(key, value)| { + if key == "caps" { + return Some( + value + .split(',') + .filter_map(|cap| Capability::try_from(cap).ok()) + .collect::>(), + ); + }; + None + }) + .next() + .unwrap_or_default(); + + if !caps.is_empty() { + println!("\nRequired Capabilities:"); + } + + for cap in &caps { + println!(" {} : {:?}", cap.scope, cap.actions); + } + + // === Consent form === + + println!("\nEnter your recovery_file's passphrase to confirm:"); + let passphrase = rpassword::read_password()?; + + let keypair = pubky_common::recovery_file::decrypt_recovery_file(&recovery_file, &passphrase)?; + + println!("Successfully decrypted recovery file..."); + println!("PublicKey: {}", keypair.public_key()); + + let client = PubkyClient::testnet(); + + // For the purposes of this demo, we need to make sure + // the user has an account on the local homeserver. + if client.signin(&keypair).await.is_err() { + client + .signup(&keypair, &PublicKey::try_from(HOMESERVER).unwrap()) + .await?; + }; + + println!("Sending AuthToken to the 3rd party app..."); + + client.send_auth_token(&keypair, url).await?; + + Ok(()) +} diff --git a/rust/pubky/pubky-common/Cargo.toml b/rust/pubky/pubky-common/Cargo.toml new file mode 100644 index 0000000..9676fba --- /dev/null +++ b/rust/pubky/pubky-common/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "pubky-common" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +base32 = "0.5.0" +blake3 = "1.5.1" +ed25519-dalek = "2.1.1" +once_cell = "1.19.0" +pkarr = { workspace = true } +rand = "0.8.5" +thiserror = "1.0.60" +postcard = { version = "1.0.8", features = ["alloc"] } +crypto_secretbox = { version = "0.1.1", features = ["std"] } +argon2 = { version = "0.5.3", features = ["std"] } + +serde = { workspace = true, optional = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +js-sys = "0.3.69" + +[dev-dependencies] +postcard = "1.0.8" + +[features] + +serde = ["dep:serde", "ed25519-dalek/serde", "pkarr/serde"] +full = ['serde'] + +default = ['full'] diff --git a/rust/pubky/pubky-common/src/auth.rs b/rust/pubky/pubky-common/src/auth.rs new file mode 100644 index 0000000..866fe5e --- /dev/null +++ b/rust/pubky/pubky-common/src/auth.rs @@ -0,0 +1,279 @@ +//! Client-server Authentication using signed timesteps + +use std::sync::{Arc, Mutex}; + +use serde::{Deserialize, Serialize}; + +use crate::{ + capabilities::{Capabilities, Capability}, + crypto::{Keypair, PublicKey, Signature}, + namespaces::PUBKY_AUTH, + timestamp::Timestamp, +}; + +// 30 seconds +const TIME_INTERVAL: u64 = 30 * 1_000_000; + +const CURRENT_VERSION: u8 = 0; +// 45 seconds in the past or the future +const TIMESTAMP_WINDOW: i64 = 45 * 1_000_000; + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct AuthToken { + /// Signature over the token. + signature: Signature, + /// A namespace to ensure this signature can't be used for any + /// other purposes that share the same message structurea by accident. + namespace: [u8; 10], + /// Version of the [AuthToken], in case we need to upgrade it to support unforseen usecases. + /// + /// Version 0: + /// - Signer is implicitly the same as the root keypair for + /// the [AuthToken::pubky], without any delegation. + /// - Capabilities are only meant for resoucres on the homeserver. + version: u8, + /// Timestamp + timestamp: Timestamp, + /// The [PublicKey] of the owner of the resources being accessed by this token. + pubky: PublicKey, + // Variable length capabilities + capabilities: Capabilities, +} + +impl AuthToken { + pub fn sign(keypair: &Keypair, capabilities: impl Into) -> Self { + let timestamp = Timestamp::now(); + + let mut token = Self { + signature: Signature::from_bytes(&[0; 64]), + namespace: *PUBKY_AUTH, + version: 0, + timestamp, + pubky: keypair.public_key(), + capabilities: capabilities.into(), + }; + + let serialized = token.serialize(); + + token.signature = keypair.sign(&serialized[65..]); + + token + } + + pub fn capabilities(&self) -> &[Capability] { + &self.capabilities.0 + } + + pub fn verify(bytes: &[u8]) -> Result { + if bytes[75] > CURRENT_VERSION { + return Err(Error::UnknownVersion); + } + + let token = AuthToken::deserialize(bytes)?; + + match token.version { + 0 => { + let now = Timestamp::now(); + + // Chcek timestamp; + let diff = token.timestamp.difference(&now); + if diff > TIMESTAMP_WINDOW { + return Err(Error::TooFarInTheFuture); + } + if diff < -TIMESTAMP_WINDOW { + return Err(Error::Expired); + } + + token + .pubky + .verify(AuthToken::signable(token.version, bytes), &token.signature) + .map_err(|_| Error::InvalidSignature)?; + + Ok(token) + } + _ => unreachable!(), + } + } + + pub fn serialize(&self) -> Vec { + postcard::to_allocvec(self).unwrap() + } + + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(postcard::from_bytes(bytes)?) + } + + pub fn pubky(&self) -> &PublicKey { + &self.pubky + } + + /// A unique ID for this [AuthToken], which is a concatenation of + /// [AuthToken::pubky] and [AuthToken::timestamp]. + /// + /// Assuming that [AuthToken::timestamp] is unique for every [AuthToken::pubky]. + fn id(version: u8, bytes: &[u8]) -> Box<[u8]> { + match version { + 0 => bytes[75..115].into(), + _ => unreachable!(), + } + } + + fn signable(version: u8, bytes: &[u8]) -> &[u8] { + match version { + 0 => bytes[65..].into(), + _ => unreachable!(), + } + } +} + +#[derive(Debug, Clone, Default)] +/// Keeps track of used AuthToken until they expire. +pub struct AuthVerifier { + seen: Arc>>>, +} + +impl AuthVerifier { + pub fn verify(&self, bytes: &[u8]) -> Result { + self.gc(); + + let token = AuthToken::verify(bytes)?; + + let mut seen = self.seen.lock().unwrap(); + + let id = AuthToken::id(token.version, bytes); + + match seen.binary_search_by(|element| element.cmp(&id)) { + Ok(_) => Err(Error::AlreadyUsed), + Err(index) => { + seen.insert(index, id); + Ok(token) + } + } + } + + // === Private Methods === + + /// Remove all tokens older than two time intervals in the past. + fn gc(&self) { + let threshold = ((Timestamp::now().into_inner() / TIME_INTERVAL) - 2).to_be_bytes(); + + let mut inner = self.seen.lock().unwrap(); + + match inner.binary_search_by(|element| element[0..8].cmp(&threshold)) { + Ok(index) | Err(index) => { + inner.drain(0..index); + } + } + } +} + +#[derive(thiserror::Error, Debug, PartialEq, Eq)] +pub enum Error { + #[error("Unknown version")] + UnknownVersion, + #[error("AuthToken has a timestamp that is more than 45 seconds in the future")] + TooFarInTheFuture, + #[error("AuthToken has a timestamp that is more than 45 seconds in the past")] + Expired, + #[error("Invalid Signature")] + InvalidSignature, + #[error(transparent)] + Postcard(#[from] postcard::Error), + #[error("AuthToken already used")] + AlreadyUsed, +} + +#[cfg(test)] +mod tests { + use crate::{ + auth::TIMESTAMP_WINDOW, capabilities::Capability, crypto::Keypair, timestamp::Timestamp, + }; + + use super::*; + + #[test] + fn v0_id_signable() { + let signer = Keypair::random(); + let capabilities = vec![Capability::root()]; + + let token = AuthToken::sign(&signer, capabilities.clone()); + + let serialized = &token.serialize(); + + let mut id = vec![]; + id.extend_from_slice(&token.timestamp.to_bytes()); + id.extend_from_slice(signer.public_key().as_bytes()); + + assert_eq!(AuthToken::id(token.version, serialized), id.into()); + + assert_eq!( + AuthToken::signable(token.version, serialized), + &serialized[65..] + ) + } + + #[test] + fn sign_verify() { + let signer = Keypair::random(); + let capabilities = vec![Capability::root()]; + + let verifier = AuthVerifier::default(); + + let token = AuthToken::sign(&signer, capabilities.clone()); + + let serialized = &token.serialize(); + + verifier.verify(serialized).unwrap(); + + assert_eq!(token.capabilities, capabilities.into()); + } + + #[test] + fn expired() { + let signer = Keypair::random(); + let capabilities = Capabilities(vec![Capability::root()]); + + let verifier = AuthVerifier::default(); + + let timestamp = (&Timestamp::now()) - (TIMESTAMP_WINDOW as u64); + + let mut signable = vec![]; + signable.extend_from_slice(signer.public_key().as_bytes()); + signable.extend_from_slice(&postcard::to_allocvec(&capabilities).unwrap()); + + let signature = signer.sign(&signable); + + let token = AuthToken { + signature, + namespace: *PUBKY_AUTH, + version: 0, + timestamp, + pubky: signer.public_key(), + capabilities, + }; + + let serialized = token.serialize(); + + let result = verifier.verify(&serialized); + + assert_eq!(result, Err(Error::Expired)); + } + + #[test] + fn already_used() { + let signer = Keypair::random(); + let capabilities = vec![Capability::root()]; + + let verifier = AuthVerifier::default(); + + let token = AuthToken::sign(&signer, capabilities.clone()); + + let serialized = &token.serialize(); + + verifier.verify(serialized).unwrap(); + + assert_eq!(token.capabilities, capabilities.into()); + + assert_eq!(verifier.verify(serialized), Err(Error::AlreadyUsed)); + } +} diff --git a/rust/pubky/pubky-common/src/capabilities.rs b/rust/pubky/pubky-common/src/capabilities.rs new file mode 100644 index 0000000..7929860 --- /dev/null +++ b/rust/pubky/pubky-common/src/capabilities.rs @@ -0,0 +1,237 @@ +use std::fmt::Display; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Capability { + pub scope: String, + pub actions: Vec, +} + +impl Capability { + /// Create a root [Capability] at the `/` path with all the available [PubkyAbility] + pub fn root() -> Self { + Capability { + scope: "/".to_string(), + actions: vec![Action::Read, Action::Write], + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Action { + /// Can read the scope at the specified path (GET requests). + Read, + /// Can write to the scope at the specified path (PUT/POST/DELETE requests). + Write, + /// Unknown ability + Unknown(char), +} + +impl From<&Action> for char { + fn from(value: &Action) -> Self { + match value { + Action::Read => 'r', + Action::Write => 'w', + Action::Unknown(char) => char.to_owned(), + } + } +} + +impl TryFrom for Action { + type Error = Error; + + fn try_from(value: char) -> Result { + match value { + 'r' => Ok(Self::Read), + 'w' => Ok(Self::Write), + _ => Err(Error::InvalidAction), + } + } +} + +impl Display for Capability { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}:{}", + self.scope, + self.actions.iter().map(char::from).collect::() + ) + } +} + +impl TryFrom for Capability { + type Error = Error; + + fn try_from(value: String) -> Result { + value.as_str().try_into() + } +} + +impl TryFrom<&str> for Capability { + type Error = Error; + + fn try_from(value: &str) -> Result { + if value.matches(':').count() != 1 { + return Err(Error::InvalidFormat); + } + + if !value.starts_with('/') { + return Err(Error::InvalidScope); + } + + let actions_str = value.rsplit(':').next().unwrap_or(""); + + let mut actions = Vec::new(); + + for char in actions_str.chars() { + let ability = Action::try_from(char)?; + + match actions.binary_search_by(|element| char::from(element).cmp(&char)) { + Ok(_) => {} + Err(index) => { + actions.insert(index, ability); + } + } + } + + let scope = value[0..value.len() - actions_str.len() - 1].to_string(); + + Ok(Capability { scope, actions }) + } +} + +impl Serialize for Capability { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let string = self.to_string(); + + string.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Capability { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let string: String = Deserialize::deserialize(deserializer)?; + + string.try_into().map_err(serde::de::Error::custom) + } +} + +#[derive(thiserror::Error, Debug, PartialEq, Eq)] +pub enum Error { + #[error("Capability: Invalid scope: does not start with `/`")] + InvalidScope, + #[error("Capability: Invalid format should be :")] + InvalidFormat, + #[error("Capability: Invalid Action")] + InvalidAction, + #[error("Capabilities: Invalid capabilities format")] + InvalidCapabilities, +} + +#[derive(Clone, Default, Debug, PartialEq, Eq)] +/// A wrapper around `Vec` to enable serialization without +/// a varint. Useful when [Capabilities] are at the end of a struct. +pub struct Capabilities(pub Vec); + +impl Capabilities { + pub fn contains(&self, capability: &Capability) -> bool { + self.0.contains(capability) + } +} + +impl From> for Capabilities { + fn from(value: Vec) -> Self { + Self(value) + } +} + +impl From for Vec { + fn from(value: Capabilities) -> Self { + value.0 + } +} + +impl TryFrom<&str> for Capabilities { + type Error = Error; + + fn try_from(value: &str) -> Result { + let mut caps = vec![]; + + for s in value.split(',') { + if let Ok(cap) = Capability::try_from(s) { + caps.push(cap); + }; + } + + Ok(Capabilities(caps)) + } +} + +impl Display for Capabilities { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let string = self + .0 + .iter() + .map(|c| c.to_string()) + .collect::>() + .join(","); + + write!(f, "{}", string) + } +} + +impl Serialize for Capabilities { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Capabilities { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let string: String = Deserialize::deserialize(deserializer)?; + + let mut caps = vec![]; + + for s in string.split(',') { + if let Ok(cap) = Capability::try_from(s) { + caps.push(cap); + }; + } + + Ok(Capabilities(caps)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn pubky_caps() { + let cap = Capability { + scope: "/pub/pubky.app/".to_string(), + actions: vec![Action::Read, Action::Write], + }; + + // Read and write withing directory `/pub/pubky.app/`. + let expected_string = "/pub/pubky.app/:rw"; + + assert_eq!(cap.to_string(), expected_string); + + assert_eq!(Capability::try_from(expected_string), Ok(cap)) + } +} diff --git a/rust/pubky/pubky-common/src/crypto.rs b/rust/pubky/pubky-common/src/crypto.rs new file mode 100644 index 0000000..a7adea5 --- /dev/null +++ b/rust/pubky/pubky-common/src/crypto.rs @@ -0,0 +1,71 @@ +use crypto_secretbox::{ + aead::{Aead, AeadCore, KeyInit, OsRng}, + XSalsa20Poly1305, +}; +use rand::prelude::Rng; + +pub use pkarr::{Keypair, PublicKey}; + +pub use ed25519_dalek::Signature; + +pub type Hash = blake3::Hash; + +pub use blake3::hash; + +pub use blake3::Hasher; + +pub fn random_hash() -> Hash { + let mut rng = rand::thread_rng(); + Hash::from_bytes(rng.gen()) +} + +pub fn random_bytes() -> [u8; N] { + let mut rng = rand::thread_rng(); + let mut arr = [0u8; N]; + + #[allow(clippy::needless_range_loop)] + for i in 0..N { + arr[i] = rng.gen(); + } + arr +} + +pub fn encrypt(plain_text: &[u8], encryption_key: &[u8; 32]) -> Result, Error> { + let cipher = XSalsa20Poly1305::new(encryption_key.into()); + let nonce = XSalsa20Poly1305::generate_nonce(&mut OsRng); // unique per message + let ciphertext = cipher.encrypt(&nonce, plain_text)?; + + let mut out: Vec = Vec::with_capacity(nonce.len() + ciphertext.len()); + out.extend_from_slice(nonce.as_slice()); + out.extend_from_slice(&ciphertext); + + Ok(out) +} + +pub fn decrypt(bytes: &[u8], encryption_key: &[u8; 32]) -> Result, Error> { + let cipher = XSalsa20Poly1305::new(encryption_key.into()); + + Ok(cipher.decrypt(bytes[..24].into(), &bytes[24..])?) +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + SecretBox(#[from] crypto_secretbox::Error), +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn encrypt_decrypt() { + let plain_text = "Plain text!"; + let encryption_key = [0; 32]; + + let encrypted = encrypt(plain_text.as_bytes(), &encryption_key).unwrap(); + let decrypted = decrypt(&encrypted, &encryption_key).unwrap(); + + assert_eq!(decrypted, plain_text.as_bytes()) + } +} diff --git a/rust/pubky/pubky-common/src/lib.rs b/rust/pubky/pubky-common/src/lib.rs new file mode 100644 index 0000000..cfb56f2 --- /dev/null +++ b/rust/pubky/pubky-common/src/lib.rs @@ -0,0 +1,7 @@ +pub mod auth; +pub mod capabilities; +pub mod crypto; +pub mod namespaces; +pub mod recovery_file; +pub mod session; +pub mod timestamp; diff --git a/rust/pubky/pubky-common/src/namespaces.rs b/rust/pubky/pubky-common/src/namespaces.rs new file mode 100644 index 0000000..6aa37cd --- /dev/null +++ b/rust/pubky/pubky-common/src/namespaces.rs @@ -0,0 +1 @@ +pub const PUBKY_AUTH: &[u8; 10] = b"PUBKY:AUTH"; diff --git a/rust/pubky/pubky-common/src/recovery_file.rs b/rust/pubky/pubky-common/src/recovery_file.rs new file mode 100644 index 0000000..0a2f9b4 --- /dev/null +++ b/rust/pubky/pubky-common/src/recovery_file.rs @@ -0,0 +1,102 @@ +use argon2::Argon2; +use pkarr::Keypair; + +use crate::crypto::{decrypt, encrypt}; + +static SPEC_NAME: &str = "recovery"; +static SPEC_LINE: &str = "pubky.org/recovery"; + +pub fn decrypt_recovery_file(recovery_file: &[u8], passphrase: &str) -> Result { + let encryption_key = recovery_file_encryption_key_from_passphrase(passphrase)?; + + let newline_index = recovery_file + .iter() + .position(|&r| r == 10) + .ok_or(()) + .map_err(|_| Error::RecoveryFileMissingSpecLine)?; + + let spec_line = &recovery_file[..newline_index]; + + if !(spec_line.starts_with(SPEC_LINE.as_bytes()) + || spec_line.starts_with(b"pkarr.org/recovery")) + { + return Err(Error::RecoveryFileVersionNotSupported); + } + + let encrypted = &recovery_file[newline_index + 1..]; + + if encrypted.is_empty() { + return Err(Error::RecoverFileMissingEncryptedSecretKey); + }; + + let decrypted = decrypt(encrypted, &encryption_key)?; + let length = decrypted.len(); + let secret_key: [u8; 32] = decrypted + .try_into() + .map_err(|_| Error::RecoverFileInvalidSecretKeyLength(length))?; + + Ok(Keypair::from_secret_key(&secret_key)) +} + +pub fn create_recovery_file(keypair: &Keypair, passphrase: &str) -> Result, Error> { + let encryption_key = recovery_file_encryption_key_from_passphrase(passphrase)?; + let secret_key = keypair.secret_key(); + + let encrypted_secret_key = encrypt(&secret_key, &encryption_key)?; + + let mut out = Vec::with_capacity(SPEC_LINE.len() + 1 + encrypted_secret_key.len()); + + out.extend_from_slice(SPEC_LINE.as_bytes()); + out.extend_from_slice(b"\n"); + out.extend_from_slice(&encrypted_secret_key); + + Ok(out) +} + +fn recovery_file_encryption_key_from_passphrase(passphrase: &str) -> Result<[u8; 32], Error> { + let argon2id = Argon2::default(); + + let mut out = [0; 32]; + + argon2id.hash_password_into(passphrase.as_bytes(), SPEC_NAME.as_bytes(), &mut out)?; + + Ok(out) +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + // === Recovery file == + #[error("Recovery file should start with a spec line, followed by a new line character")] + RecoveryFileMissingSpecLine, + + #[error("Recovery file should start with a spec line, followed by a new line character")] + RecoveryFileVersionNotSupported, + + #[error("Recovery file should contain an encrypted secret key after the new line character")] + RecoverFileMissingEncryptedSecretKey, + + #[error("Recovery file encrypted secret key should be 32 bytes, got {0}")] + RecoverFileInvalidSecretKeyLength(usize), + + #[error(transparent)] + Argon(#[from] argon2::Error), + + #[error(transparent)] + Crypto(#[from] crate::crypto::Error), +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn encrypt_decrypt_recovery_file() { + let passphrase = "very secure password"; + let keypair = Keypair::random(); + + let recovery_file = create_recovery_file(&keypair, passphrase).unwrap(); + let recovered = decrypt_recovery_file(&recovery_file, passphrase).unwrap(); + + assert_eq!(recovered.public_key(), keypair.public_key()); + } +} diff --git a/rust/pubky/pubky-common/src/session.rs b/rust/pubky/pubky-common/src/session.rs new file mode 100644 index 0000000..5ce64d0 --- /dev/null +++ b/rust/pubky/pubky-common/src/session.rs @@ -0,0 +1,126 @@ +use pkarr::PublicKey; +use postcard::{from_bytes, to_allocvec}; +use serde::{Deserialize, Serialize}; + +extern crate alloc; +use alloc::vec::Vec; + +use crate::{auth::AuthToken, capabilities::Capability, timestamp::Timestamp}; + +// TODO: add IP address? +// TODO: use https://crates.io/crates/user-agent-parser to parse the session +// and get more informations from the user-agent. +#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct Session { + version: usize, + pubky: PublicKey, + created_at: u64, + /// User specified name, defaults to the user-agent. + name: String, + user_agent: String, + capabilities: Vec, +} + +impl Session { + pub fn new(token: &AuthToken, user_agent: Option) -> Self { + Self { + version: 0, + pubky: token.pubky().to_owned(), + created_at: Timestamp::now().into_inner(), + capabilities: token.capabilities().to_vec(), + user_agent: user_agent.as_deref().unwrap_or("").to_string(), + name: user_agent.as_deref().unwrap_or("").to_string(), + } + } + + // === Getters === + + pub fn pubky(&self) -> &PublicKey { + &self.pubky + } + + pub fn capabilities(&self) -> &Vec { + &self.capabilities + } + + // === Setters === + + pub fn set_user_agent(&mut self, user_agent: String) -> &mut Self { + self.user_agent = user_agent; + + if self.name.is_empty() { + self.name.clone_from(&self.user_agent) + } + + self + } + + pub fn set_capabilities(&mut self, capabilities: Vec) -> &mut Self { + self.capabilities = capabilities; + + self + } + + // === Public Methods === + + pub fn serialize(&self) -> Vec { + to_allocvec(self).expect("Session::serialize") + } + + pub fn deserialize(bytes: &[u8]) -> Result { + if bytes[0] > 0 { + return Err(Error::UnknownVersion); + } + + Ok(from_bytes(bytes)?) + } + + // TODO: add `can_read()`, `can_write()` and `is_root()` methods +} + +pub type Result = core::result::Result; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Unknown version")] + UnknownVersion, + #[error(transparent)] + Postcard(#[from] postcard::Error), +} + +#[cfg(test)] +mod tests { + use crate::crypto::Keypair; + + use super::*; + + #[test] + fn serialize() { + let keypair = Keypair::from_secret_key(&[0; 32]); + let pubky = keypair.public_key(); + + let session = Session { + user_agent: "foo".to_string(), + capabilities: vec![Capability::root()], + created_at: 0, + pubky, + version: 0, + name: "".to_string(), + }; + + let serialized = session.serialize(); + + assert_eq!( + serialized, + [ + 0, 59, 106, 39, 188, 206, 182, 164, 45, 98, 163, 168, 208, 42, 111, 13, 115, 101, + 50, 21, 119, 29, 226, 67, 166, 58, 192, 72, 161, 139, 89, 218, 41, 0, 0, 3, 102, + 111, 111, 1, 4, 47, 58, 114, 119 + ] + ); + + let deseiralized = Session::deserialize(&serialized).unwrap(); + + assert_eq!(deseiralized, session) + } +} diff --git a/rust/pubky/pubky-common/src/timestamp.rs b/rust/pubky/pubky-common/src/timestamp.rs new file mode 100644 index 0000000..848f894 --- /dev/null +++ b/rust/pubky/pubky-common/src/timestamp.rs @@ -0,0 +1,280 @@ +//! Monotonic unix timestamp in microseconds + +use serde::{Deserialize, Serialize}; +use std::fmt::Display; +use std::{ + ops::{Add, Sub}, + sync::Mutex, +}; + +use once_cell::sync::Lazy; +use rand::Rng; + +#[cfg(not(target_arch = "wasm32"))] +use std::time::SystemTime; + +/// ~4% chance of none of 10 clocks have matching id. +const CLOCK_MASK: u64 = (1 << 8) - 1; +const TIME_MASK: u64 = !0 >> 8; + +pub struct TimestampFactory { + clock_id: u64, + last_time: u64, +} + +impl TimestampFactory { + pub fn new() -> Self { + Self { + clock_id: rand::thread_rng().gen::() & CLOCK_MASK, + last_time: system_time() & TIME_MASK, + } + } + + pub fn now(&mut self) -> Timestamp { + // Ensure monotonicity. + self.last_time = (system_time() & TIME_MASK).max(self.last_time + CLOCK_MASK + 1); + + // Add clock_id to the end of the timestamp + Timestamp(self.last_time | self.clock_id) + } +} + +impl Default for TimestampFactory { + fn default() -> Self { + Self::new() + } +} + +static DEFAULT_FACTORY: Lazy> = + Lazy::new(|| Mutex::new(TimestampFactory::default())); + +/// Monotonic timestamp since [SystemTime::UNIX_EPOCH] in microseconds as u64. +/// +/// The purpose of this timestamp is to unique per "user", not globally, +/// it achieves this by: +/// 1. Override the last byte with a random `clock_id`, reducing the probability +/// of two matching timestamps across multiple machines/threads. +/// 2. Gurantee that the remaining 3 bytes are ever increasing (monotonic) within +/// the same thread regardless of the wall clock value +/// +/// This timestamp is also serialized as BE bytes to remain sortable. +/// If a `utf-8` encoding is necessary, it is encoded as [base32::Alphabet::Crockford] +/// to act as a sortable Id. +/// +/// U64 of microseconds is valid for the next 500 thousand years! +#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)] +pub struct Timestamp(u64); + +impl Timestamp { + pub fn now() -> Self { + DEFAULT_FACTORY.lock().unwrap().now() + } + + /// Return big endian bytes + pub fn to_bytes(&self) -> [u8; 8] { + self.0.to_be_bytes() + } + + pub fn difference(&self, rhs: &Timestamp) -> i64 { + (self.0 as i64) - (rhs.0 as i64) + } + + pub fn into_inner(&self) -> u64 { + self.0 + } +} + +impl Default for Timestamp { + fn default() -> Self { + Timestamp::now() + } +} + +impl Display for Timestamp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let bytes: [u8; 8] = self.into(); + f.write_str(&base32::encode(base32::Alphabet::Crockford, &bytes)) + } +} + +impl TryFrom for Timestamp { + type Error = TimestampError; + + fn try_from(value: String) -> Result { + match base32::decode(base32::Alphabet::Crockford, &value) { + Some(vec) => { + let bytes: [u8; 8] = vec + .try_into() + .map_err(|_| TimestampError::InvalidEncoding)?; + + Ok(bytes.into()) + } + None => Err(TimestampError::InvalidEncoding), + } + } +} + +impl TryFrom<&[u8]> for Timestamp { + type Error = TimestampError; + + fn try_from(bytes: &[u8]) -> Result { + let bytes: [u8; 8] = bytes + .try_into() + .map_err(|_| TimestampError::InvalidBytesLength(bytes.len()))?; + + Ok(bytes.into()) + } +} + +impl From<&Timestamp> for [u8; 8] { + fn from(timestamp: &Timestamp) -> Self { + timestamp.0.to_be_bytes() + } +} + +impl From<[u8; 8]> for Timestamp { + fn from(bytes: [u8; 8]) -> Self { + Self(u64::from_be_bytes(bytes)) + } +} + +// === U64 conversion === + +impl From for u64 { + fn from(value: Timestamp) -> Self { + value.into_inner() + } +} + +impl Add for &Timestamp { + type Output = Timestamp; + + fn add(self, rhs: u64) -> Self::Output { + Timestamp(self.0 + rhs) + } +} + +impl Sub for &Timestamp { + type Output = Timestamp; + + fn sub(self, rhs: u64) -> Self::Output { + Timestamp(self.0 - rhs) + } +} + +impl Serialize for Timestamp { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let bytes = self.to_bytes(); + bytes.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Timestamp { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let bytes: [u8; 8] = Deserialize::deserialize(deserializer)?; + Ok(Timestamp(u64::from_be_bytes(bytes))) + } +} + +#[cfg(not(target_arch = "wasm32"))] +/// Return the number of microseconds since [SystemTime::UNIX_EPOCH] +fn system_time() -> u64 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("time drift") + .as_micros() as u64 +} + +#[cfg(target_arch = "wasm32")] +/// Return the number of microseconds since [SystemTime::UNIX_EPOCH] +pub fn system_time() -> u64 { + // Won't be an issue for more than 5000 years! + (js_sys::Date::now() as u64 ) + // Turn miliseconds to microseconds + * 1000 +} + +#[derive(thiserror::Error, Debug)] +pub enum TimestampError { + #[error("Invalid bytes length, Timestamp should be encoded as 8 bytes, got {0}")] + InvalidBytesLength(usize), + #[error("Invalid timestamp encoding")] + InvalidEncoding, +} + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use super::*; + + #[test] + fn monotonic() { + const COUNT: usize = 100; + + let mut set = HashSet::with_capacity(COUNT); + let mut vec = Vec::with_capacity(COUNT); + + for _ in 0..COUNT { + let timestamp = Timestamp::now(); + + set.insert(timestamp.clone()); + vec.push(timestamp); + } + + let mut ordered = vec.clone(); + ordered.sort(); + + assert_eq!(set.len(), COUNT, "unique"); + assert_eq!(ordered, vec, "ordered"); + } + + #[test] + fn strings() { + const COUNT: usize = 100; + + let mut set = HashSet::with_capacity(COUNT); + let mut vec = Vec::with_capacity(COUNT); + + for _ in 0..COUNT { + let string = Timestamp::now().to_string(); + + set.insert(string.clone()); + vec.push(string) + } + + let mut ordered = vec.clone(); + ordered.sort(); + + assert_eq!(set.len(), COUNT, "unique"); + assert_eq!(ordered, vec, "ordered"); + } + + #[test] + fn to_from_string() { + let timestamp = Timestamp::now(); + let string = timestamp.to_string(); + let decoded: Timestamp = string.try_into().unwrap(); + + assert_eq!(decoded, timestamp) + } + + #[test] + fn serde() { + let timestamp = Timestamp::now(); + + let serialized = postcard::to_allocvec(×tamp).unwrap(); + + assert_eq!(serialized, timestamp.to_bytes()); + + let deserialized: Timestamp = postcard::from_bytes(&serialized).unwrap(); + + assert_eq!(deserialized, timestamp); + } +} diff --git a/rust/pubky/pubky-homeserver/Cargo.toml b/rust/pubky/pubky-homeserver/Cargo.toml new file mode 100644 index 0000000..c8abfd5 --- /dev/null +++ b/rust/pubky/pubky-homeserver/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "pubky_homeserver" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.82" +axum = { version = "0.7.5", features = ["macros"] } +axum-extra = { version = "0.9.3", features = ["typed-header", "async-read-body"] } +base32 = "0.5.1" +bytes = "^1.7.1" +clap = { version = "4.5.11", features = ["derive"] } +dirs-next = "2.0.0" +flume = "0.11.0" +futures-util = "0.3.30" +heed = "0.20.3" +hex = "0.4.3" +pkarr = { workspace = true } +postcard = { version = "1.0.8", features = ["alloc"] } +pubky-common = { version = "0.1.0", path = "../pubky-common" } +serde = { workspace = true } +tokio = { version = "1.37.0", features = ["full"] } +toml = "0.8.19" +tower-cookies = "0.10.0" +tower-http = { version = "0.5.2", features = ["cors", "trace"] } +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } diff --git a/rust/pubky/pubky-homeserver/README.md b/rust/pubky/pubky-homeserver/README.md new file mode 100644 index 0000000..d1799a2 --- /dev/null +++ b/rust/pubky/pubky-homeserver/README.md @@ -0,0 +1,23 @@ +# Pubky Homeserver + +## Usage + +Use `cargo run` + +```bash +cargo run -- --config=./src/config.toml +``` + +Or Build first then run from target. + +Build + +```bash +cargo build --release +``` + +Run with an optional config file + +```bash +../target/release/pubky-homeserver --config=./src/config.toml +``` diff --git a/rust/pubky/pubky-homeserver/src/config.rs b/rust/pubky/pubky-homeserver/src/config.rs new file mode 100644 index 0000000..55f015c --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/config.rs @@ -0,0 +1,173 @@ +//! Configuration for the server + +use anyhow::{anyhow, Context, Result}; +use pkarr::Keypair; +use serde::{Deserialize, Deserializer, Serialize}; +use std::{ + fmt::Debug, + path::{Path, PathBuf}, + time::Duration, +}; +use tracing::info; + +use pubky_common::timestamp::Timestamp; + +const DEFAULT_HOMESERVER_PORT: u16 = 6287; +const DEFAULT_STORAGE_DIR: &str = "pubky"; + +/// Server configuration +#[derive(Serialize, Deserialize, Clone)] +pub struct Config { + testnet: bool, + port: Option, + bootstrap: Option>, + domain: String, + /// Path to the storage directory + /// + /// Defaults to a directory in the OS data directory + storage: Option, + #[serde(deserialize_with = "secret_key_deserialize")] + secret_key: Option<[u8; 32]>, + + dht_request_timeout: Option, +} + +impl Config { + /// Load the config from a file. + pub async fn load(path: impl AsRef) -> Result { + let s = tokio::fs::read_to_string(path.as_ref()) + .await + .with_context(|| format!("failed to read {}", path.as_ref().to_string_lossy()))?; + + let config: Config = toml::from_str(&s)?; + + if config.testnet { + let testnet_config = Config::testnet(); + + return Ok(Config { + bootstrap: testnet_config.bootstrap, + ..config + }); + } + + Ok(config) + } + + /// Testnet configurations + pub fn testnet() -> Self { + let testnet = pkarr::mainline::Testnet::new(10); + info!(?testnet.bootstrap, "Testnet bootstrap nodes"); + + let bootstrap = Some(testnet.bootstrap.to_owned()); + let storage = Some( + std::env::temp_dir() + .join(Timestamp::now().to_string()) + .join(DEFAULT_STORAGE_DIR), + ); + + Self { + bootstrap, + storage, + port: Some(15411), + dht_request_timeout: Some(Duration::from_millis(10)), + ..Default::default() + } + } + + /// Test configurations + pub fn test(testnet: &pkarr::mainline::Testnet) -> Self { + let bootstrap = Some(testnet.bootstrap.to_owned()); + let storage = Some( + std::env::temp_dir() + .join(Timestamp::now().to_string()) + .join(DEFAULT_STORAGE_DIR), + ); + + Self { + bootstrap, + storage, + ..Default::default() + } + } + + pub fn port(&self) -> u16 { + self.port.unwrap_or(DEFAULT_HOMESERVER_PORT) + } + + pub fn bootstsrap(&self) -> Option> { + self.bootstrap.to_owned() + } + + pub fn domain(&self) -> &str { + &self.domain + } + + /// Get the path to the storage directory + pub fn storage(&self) -> Result { + let dir = if let Some(storage) = &self.storage { + PathBuf::from(storage) + } else { + let path = dirs_next::data_dir().ok_or_else(|| { + anyhow!("operating environment provides no directory for application data") + })?; + path.join(DEFAULT_STORAGE_DIR) + }; + + Ok(dir.join("homeserver")) + } + + pub fn keypair(&self) -> Keypair { + Keypair::from_secret_key(&self.secret_key.unwrap_or_default()) + } + + pub(crate) fn dht_request_timeout(&self) -> Option { + self.dht_request_timeout + } +} + +impl Default for Config { + fn default() -> Self { + Self { + testnet: false, + port: Some(0), + bootstrap: None, + domain: "localhost".to_string(), + storage: None, + secret_key: None, + dht_request_timeout: None, + } + } +} + +fn secret_key_deserialize<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let opt: Option = Option::deserialize(deserializer)?; + + match opt { + Some(s) => { + let bytes = hex::decode(s).map_err(serde::de::Error::custom)?; + + if bytes.len() != 32 { + return Err(serde::de::Error::custom("Expected a 32-byte array")); + } + + let mut arr = [0u8; 32]; + arr.copy_from_slice(&bytes); + Ok(Some(arr)) + } + None => Ok(None), + } +} + +impl Debug for Config { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_map() + .entry(&"testnet", &self.testnet) + .entry(&"port", &self.port()) + .entry(&"storage", &self.storage()) + .entry(&"public_key", &self.keypair().public_key()) + .finish() + } +} diff --git a/rust/pubky/pubky-homeserver/src/config.toml b/rust/pubky/pubky-homeserver/src/config.toml new file mode 100644 index 0000000..cb65622 --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/config.toml @@ -0,0 +1,10 @@ +# Use testnet network (local DHT) for testing. +testnet = true +# Secret key (in hex) to generate the Homeserver's Keypair +secret_key = "0000000000000000000000000000000000000000000000000000000000000000" +# Domain to be published in Pkarr records for this server to be accessible by. +domain = "localhost" +# Port for the Homeserver to listen on. +port = 6287 +# Storage directory Defaults to +# storage = "" diff --git a/rust/pubky/pubky-homeserver/src/database.rs b/rust/pubky/pubky-homeserver/src/database.rs new file mode 100644 index 0000000..4adc73d --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/database.rs @@ -0,0 +1,73 @@ +use std::fs; + +use std::path::Path; + +use heed::{Env, EnvOpenOptions}; + +mod migrations; +pub mod tables; + +use tables::{Tables, TABLES_COUNT}; + +pub const MAX_LIST_LIMIT: u16 = 100; + +#[derive(Debug, Clone)] +pub struct DB { + pub(crate) env: Env, + pub(crate) tables: Tables, +} + +impl DB { + pub fn open(storage: &Path) -> anyhow::Result { + fs::create_dir_all(storage).unwrap(); + + let env = unsafe { EnvOpenOptions::new().max_dbs(TABLES_COUNT).open(storage) }?; + + let tables = migrations::run(&env)?; + + let db = DB { env, tables }; + + Ok(db) + } +} + +#[cfg(test)] +mod tests { + use bytes::Bytes; + use pkarr::Keypair; + use pubky_common::timestamp::Timestamp; + + use super::DB; + + #[tokio::test] + async fn entries() { + let storage = std::env::temp_dir() + .join(Timestamp::now().to_string()) + .join("pubky"); + + let db = DB::open(&storage).unwrap(); + + let keypair = Keypair::random(); + let path = "/pub/foo.txt"; + + let (tx, rx) = flume::bounded::(0); + + let mut cloned = db.clone(); + let cloned_keypair = keypair.clone(); + + let done = tokio::task::spawn_blocking(move || { + cloned + .put_entry(&cloned_keypair.public_key(), path, rx) + .unwrap(); + }); + + tx.send(vec![1, 2, 3, 4, 5].into()).unwrap(); + drop(tx); + + done.await.unwrap(); + + let blob = db.get_blob(&keypair.public_key(), path).unwrap().unwrap(); + + assert_eq!(blob, Bytes::from(vec![1, 2, 3, 4, 5])); + } +} diff --git a/rust/pubky/pubky-homeserver/src/database/migrations.rs b/rust/pubky/pubky-homeserver/src/database/migrations.rs new file mode 100644 index 0000000..eb5a5f8 --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/database/migrations.rs @@ -0,0 +1,17 @@ +use heed::Env; + +mod m0; + +use super::tables::Tables; + +pub fn run(env: &Env) -> anyhow::Result { + let mut wtxn = env.write_txn()?; + + m0::run(env, &mut wtxn)?; + + let tables = Tables::new(env, &mut wtxn)?; + + wtxn.commit()?; + + Ok(tables) +} diff --git a/rust/pubky/pubky-homeserver/src/database/migrations/m0.rs b/rust/pubky/pubky-homeserver/src/database/migrations/m0.rs new file mode 100644 index 0000000..11c0e1a --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/database/migrations/m0.rs @@ -0,0 +1,17 @@ +use heed::{Env, RwTxn}; + +use crate::database::tables::{blobs, entries, events, sessions, users}; + +pub fn run(env: &Env, wtxn: &mut RwTxn) -> anyhow::Result<()> { + let _: users::UsersTable = env.create_database(wtxn, Some(users::USERS_TABLE))?; + + let _: sessions::SessionsTable = env.create_database(wtxn, Some(sessions::SESSIONS_TABLE))?; + + let _: blobs::BlobsTable = env.create_database(wtxn, Some(blobs::BLOBS_TABLE))?; + + let _: entries::EntriesTable = env.create_database(wtxn, Some(entries::ENTRIES_TABLE))?; + + let _: events::EventsTable = env.create_database(wtxn, Some(events::EVENTS_TABLE))?; + + Ok(()) +} diff --git a/rust/pubky/pubky-homeserver/src/database/tables.rs b/rust/pubky/pubky-homeserver/src/database/tables.rs new file mode 100644 index 0000000..e879bd0 --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/database/tables.rs @@ -0,0 +1,49 @@ +pub mod blobs; +pub mod entries; +pub mod events; +pub mod sessions; +pub mod users; + +use heed::{Env, RwTxn}; + +use blobs::{BlobsTable, BLOBS_TABLE}; +use entries::{EntriesTable, ENTRIES_TABLE}; + +use self::{ + events::{EventsTable, EVENTS_TABLE}, + sessions::{SessionsTable, SESSIONS_TABLE}, + users::{UsersTable, USERS_TABLE}, +}; + +pub const TABLES_COUNT: u32 = 5; + +#[derive(Debug, Clone)] +pub struct Tables { + pub users: UsersTable, + pub sessions: SessionsTable, + pub blobs: BlobsTable, + pub entries: EntriesTable, + pub events: EventsTable, +} + +impl Tables { + pub fn new(env: &Env, wtxn: &mut RwTxn) -> anyhow::Result { + Ok(Self { + users: env + .open_database(wtxn, Some(USERS_TABLE))? + .expect("Users table already created"), + sessions: env + .open_database(wtxn, Some(SESSIONS_TABLE))? + .expect("Sessions table already created"), + blobs: env + .open_database(wtxn, Some(BLOBS_TABLE))? + .expect("Blobs table already created"), + entries: env + .open_database(wtxn, Some(ENTRIES_TABLE))? + .expect("Entries table already created"), + events: env + .open_database(wtxn, Some(EVENTS_TABLE))? + .expect("Events table already created"), + }) + } +} diff --git a/rust/pubky/pubky-homeserver/src/database/tables/blobs.rs b/rust/pubky/pubky-homeserver/src/database/tables/blobs.rs new file mode 100644 index 0000000..25f57c0 --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/database/tables/blobs.rs @@ -0,0 +1,38 @@ +use heed::{types::Bytes, Database}; +use pkarr::PublicKey; + +use crate::database::DB; + +use super::entries::Entry; + +/// hash of the blob => bytes. +pub type BlobsTable = Database; + +pub const BLOBS_TABLE: &str = "blobs"; + +impl DB { + pub fn get_blob( + &self, + public_key: &PublicKey, + path: &str, + ) -> anyhow::Result> { + let rtxn = self.env.read_txn()?; + + let key = format!("{public_key}/{path}"); + + let result = if let Some(bytes) = self.tables.entries.get(&rtxn, &key)? { + let entry = Entry::deserialize(bytes)?; + + self.tables + .blobs + .get(&rtxn, entry.content_hash())? + .map(|blob| bytes::Bytes::from(blob.to_vec())) + } else { + None + }; + + rtxn.commit()?; + + Ok(result) + } +} diff --git a/rust/pubky/pubky-homeserver/src/database/tables/entries.rs b/rust/pubky/pubky-homeserver/src/database/tables/entries.rs new file mode 100644 index 0000000..e41a5df --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/database/tables/entries.rs @@ -0,0 +1,274 @@ +use pkarr::PublicKey; +use postcard::{from_bytes, to_allocvec}; +use serde::{Deserialize, Serialize}; +use tracing::instrument; + +use heed::{ + types::{Bytes, Str}, + Database, RoTxn, +}; + +use pubky_common::{ + crypto::{Hash, Hasher}, + timestamp::Timestamp, +}; + +use crate::database::{DB, MAX_LIST_LIMIT}; + +use super::events::Event; + +/// full_path(pubky/*path) => Entry. +pub type EntriesTable = Database; + +pub const ENTRIES_TABLE: &str = "entries"; + +impl DB { + pub fn put_entry( + &mut self, + public_key: &PublicKey, + path: &str, + rx: flume::Receiver, + ) -> anyhow::Result<()> { + let mut wtxn = self.env.write_txn()?; + + let mut hasher = Hasher::new(); + let mut bytes = vec![]; + let mut length = 0; + + while let Ok(chunk) = rx.recv() { + hasher.update(&chunk); + bytes.extend_from_slice(&chunk); + length += chunk.len(); + } + + let hash = hasher.finalize(); + + self.tables.blobs.put(&mut wtxn, hash.as_bytes(), &bytes)?; + + let mut entry = Entry::new(); + + entry.set_content_hash(hash); + entry.set_content_length(length); + + let key = format!("{public_key}/{path}"); + + self.tables + .entries + .put(&mut wtxn, &key, &entry.serialize())?; + + if path.starts_with("pub/") { + let url = format!("pubky://{key}"); + let event = Event::put(&url); + let value = event.serialize(); + + let key = entry.timestamp.to_string(); + + self.tables.events.put(&mut wtxn, &key, &value)?; + + // TODO: delete older events. + // TODO: move to events.rs + } + + wtxn.commit()?; + + Ok(()) + } + + pub fn delete_entry(&mut self, public_key: &PublicKey, path: &str) -> anyhow::Result { + let mut wtxn = self.env.write_txn()?; + + let key = format!("{public_key}/{path}"); + + let deleted = if let Some(bytes) = self.tables.entries.get(&wtxn, &key)? { + let entry = Entry::deserialize(bytes)?; + + // TODO: reference counting of blobs + let deleted_blobs = self.tables.blobs.delete(&mut wtxn, entry.content_hash())?; + + let deleted_entry = self.tables.entries.delete(&mut wtxn, &key)?; + + // create DELETE event + if path.starts_with("pub/") { + let url = format!("pubky://{key}"); + + let event = Event::delete(&url); + let value = event.serialize(); + + let key = Timestamp::now().to_string(); + + self.tables.events.put(&mut wtxn, &key, &value)?; + + // TODO: delete older events. + // TODO: move to events.rs + } + + deleted_entry & deleted_blobs + } else { + false + }; + + wtxn.commit()?; + + Ok(deleted) + } + + pub fn contains_directory(&self, txn: &RoTxn, path: &str) -> anyhow::Result { + Ok(self.tables.entries.get_greater_than(txn, path)?.is_some()) + } + + /// Return a list of pubky urls. + /// + /// - limit defaults to and capped by [MAX_LIST_LIMIT] + pub fn list( + &self, + txn: &RoTxn, + path: &str, + reverse: bool, + limit: Option, + cursor: Option, + shallow: bool, + ) -> anyhow::Result> { + // Vector to store results + let mut results = Vec::new(); + + let limit = limit.unwrap_or(MAX_LIST_LIMIT).min(MAX_LIST_LIMIT); + + // TODO: make this more performant than split and allocations? + + let mut threshold = cursor + .map(|cursor| { + // Removing leading forward slashes + let mut file_or_directory = cursor.trim_start_matches('/'); + + if cursor.starts_with("pubky://") { + file_or_directory = cursor.split(path).last().expect("should not be reachable") + }; + + next_threshold( + path, + file_or_directory, + file_or_directory.ends_with('/'), + reverse, + shallow, + ) + }) + .unwrap_or(next_threshold(path, "", false, reverse, shallow)); + + for _ in 0..limit { + if let Some((key, _)) = if reverse { + self.tables.entries.get_lower_than(txn, &threshold)? + } else { + self.tables.entries.get_greater_than(txn, &threshold)? + } { + if !key.starts_with(path) { + break; + } + + if shallow { + let mut split = key[path.len()..].split('/'); + let file_or_directory = split.next().expect("should not be reachable"); + + let is_directory = split.next().is_some(); + + threshold = + next_threshold(path, file_or_directory, is_directory, reverse, shallow); + + results.push(format!( + "pubky://{path}{file_or_directory}{}", + if is_directory { "/" } else { "" } + )); + } else { + threshold = key.to_string(); + results.push(format!("pubky://{}", key)) + } + }; + } + + Ok(results) + } +} + +/// Calculate the next threshold +#[instrument] +fn next_threshold( + path: &str, + file_or_directory: &str, + is_directory: bool, + reverse: bool, + shallow: bool, +) -> String { + format!( + "{path}{file_or_directory}{}", + if file_or_directory.is_empty() { + // No file_or_directory, early return + if reverse { + // `path/to/dir/\x7f` to catch all paths than `path/to/dir/` + "\x7f" + } else { + "" + } + } else if shallow & is_directory { + if reverse { + // threshold = `path/to/dir\x2e`, since `\x2e` is lower than `/` + "\x2e" + } else { + //threshold = `path/to/dir\x7f`, since `\x7f` is greater than `/` + "\x7f" + } + } else { + "" + } + ) +} + +#[derive(Clone, Default, Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct Entry { + /// Encoding version + version: usize, + /// Modified at + timestamp: Timestamp, + content_hash: [u8; 32], + content_length: usize, + content_type: String, + // user_metadata: ? +} + +// TODO: get headers like Etag + +impl Entry { + pub fn new() -> Self { + Default::default() + } + + // === Setters === + + pub fn set_content_hash(&mut self, content_hash: Hash) -> &mut Self { + content_hash.as_bytes().clone_into(&mut self.content_hash); + self + } + + pub fn set_content_length(&mut self, content_length: usize) -> &mut Self { + self.content_length = content_length; + self + } + + // === Getters === + + pub fn content_hash(&self) -> &[u8; 32] { + &self.content_hash + } + + // === Public Method === + + pub fn serialize(&self) -> Vec { + to_allocvec(self).expect("Session::serialize") + } + + pub fn deserialize(bytes: &[u8]) -> core::result::Result { + if bytes[0] > 0 { + panic!("Unknown Entry version"); + } + + from_bytes(bytes) + } +} diff --git a/rust/pubky/pubky-homeserver/src/database/tables/events.rs b/rust/pubky/pubky-homeserver/src/database/tables/events.rs new file mode 100644 index 0000000..cf82e18 --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/database/tables/events.rs @@ -0,0 +1,58 @@ +//! Server events (Put and Delete entries) +//! +//! Useful as a realtime sync with Indexers until +//! we implement more self-authenticated merkle data. + +use heed::{ + types::{Bytes, Str}, + Database, +}; +use postcard::{from_bytes, to_allocvec}; +use serde::{Deserialize, Serialize}; + +/// Event [Timestamp] base32 => Encoded event. +pub type EventsTable = Database; + +pub const EVENTS_TABLE: &str = "events"; + +#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)] +pub enum Event { + Put(String), + Delete(String), +} + +impl Event { + pub fn put(url: &str) -> Self { + Self::Put(url.to_string()) + } + + pub fn delete(url: &str) -> Self { + Self::Delete(url.to_string()) + } + + pub fn serialize(&self) -> Vec { + to_allocvec(self).expect("Session::serialize") + } + + pub fn deserialize(bytes: &[u8]) -> core::result::Result { + if bytes[0] > 1 { + panic!("Unknown Event version"); + } + + from_bytes(bytes) + } + + pub fn url(&self) -> &str { + match self { + Event::Put(url) => url, + Event::Delete(url) => url, + } + } + + pub fn operation(&self) -> &str { + match self { + Event::Put(_) => "PUT", + Event::Delete(_) => "DEL", + } + } +} diff --git a/rust/pubky/pubky-homeserver/src/database/tables/sessions.rs b/rust/pubky/pubky-homeserver/src/database/tables/sessions.rs new file mode 100644 index 0000000..4ecd228 --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/database/tables/sessions.rs @@ -0,0 +1,51 @@ +use heed::{ + types::{Bytes, Str}, + Database, +}; +use pkarr::PublicKey; +use pubky_common::session::Session; +use tower_cookies::Cookies; + +use crate::database::DB; + +/// session secret => Session. +pub type SessionsTable = Database; + +pub const SESSIONS_TABLE: &str = "sessions"; + +impl DB { + pub fn get_session( + &mut self, + cookies: Cookies, + public_key: &PublicKey, + ) -> anyhow::Result> { + if let Some(bytes) = self.get_session_bytes(cookies, public_key)? { + return Ok(Some(Session::deserialize(&bytes)?)); + }; + + Ok(None) + } + + pub fn get_session_bytes( + &mut self, + cookies: Cookies, + public_key: &PublicKey, + ) -> anyhow::Result>> { + if let Some(cookie) = cookies.get(&public_key.to_string()) { + let rtxn = self.env.read_txn()?; + + let sessions: SessionsTable = self + .env + .open_database(&rtxn, Some(SESSIONS_TABLE))? + .expect("Session table already created"); + + let session = sessions.get(&rtxn, cookie.value())?.map(|s| s.to_vec()); + + rtxn.commit()?; + + return Ok(session); + }; + + Ok(None) + } +} diff --git a/rust/pubky/pubky-homeserver/src/database/tables/users.rs b/rust/pubky/pubky-homeserver/src/database/tables/users.rs new file mode 100644 index 0000000..cf9b44e --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/database/tables/users.rs @@ -0,0 +1,58 @@ +use std::borrow::Cow; + +use postcard::{from_bytes, to_allocvec}; +use serde::{Deserialize, Serialize}; + +use heed::{BoxedError, BytesDecode, BytesEncode, Database}; +use pkarr::PublicKey; + +extern crate alloc; + +/// PublicKey => User. +pub type UsersTable = Database; + +pub const USERS_TABLE: &str = "users"; + +// TODO: add more adminstration metadata like quota, invitation links, etc.. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct User { + pub created_at: u64, +} + +impl<'a> BytesEncode<'a> for User { + type EItem = Self; + + fn bytes_encode(user: &Self::EItem) -> Result, BoxedError> { + let vec = to_allocvec(user).unwrap(); + + Ok(Cow::Owned(vec)) + } +} + +impl<'a> BytesDecode<'a> for User { + type DItem = Self; + + fn bytes_decode(bytes: &'a [u8]) -> Result { + let user: User = from_bytes(bytes).unwrap(); + + Ok(user) + } +} + +pub struct PublicKeyCodec {} + +impl<'a> BytesEncode<'a> for PublicKeyCodec { + type EItem = PublicKey; + + fn bytes_encode(pubky: &Self::EItem) -> Result, BoxedError> { + Ok(Cow::Borrowed(pubky.as_bytes())) + } +} + +impl<'a> BytesDecode<'a> for PublicKeyCodec { + type DItem = PublicKey; + + fn bytes_decode(bytes: &'a [u8]) -> Result { + Ok(PublicKey::try_from(bytes)?) + } +} diff --git a/rust/pubky/pubky-homeserver/src/error.rs b/rust/pubky/pubky-homeserver/src/error.rs new file mode 100644 index 0000000..b6e5a14 --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/error.rs @@ -0,0 +1,121 @@ +//! Server error + +use axum::{ + extract::rejection::{ExtensionRejection, PathRejection, QueryRejection}, + http::StatusCode, + response::IntoResponse, +}; + +pub type Result = core::result::Result; + +#[derive(Debug, Clone)] +pub struct Error { + // #[serde(with = "serde_status_code")] + status: StatusCode, + detail: Option, +} + +impl Default for Error { + fn default() -> Self { + Self { + status: StatusCode::INTERNAL_SERVER_ERROR, + detail: None, + } + } +} + +impl Error { + pub fn with_status(status: StatusCode) -> Error { + Self { + status, + detail: None, + } + } + + /// Create a new [`Error`]. + pub fn new(status_code: StatusCode, message: Option) -> Error { + Self { + status: status_code, + detail: message.map(|m| m.to_string()), + } + } +} + +impl IntoResponse for Error { + fn into_response(self) -> axum::response::Response { + match self.detail { + Some(detail) => (self.status, detail).into_response(), + _ => (self.status,).into_response(), + } + } +} + +impl From for Error { + fn from(error: QueryRejection) -> Self { + Self::new(StatusCode::BAD_REQUEST, error.into()) + } +} + +impl From for Error { + fn from(error: ExtensionRejection) -> Self { + Self::new(StatusCode::BAD_REQUEST, error.into()) + } +} + +impl From for Error { + fn from(error: PathRejection) -> Self { + Self::new(StatusCode::BAD_REQUEST, error.into()) + } +} + +// === Pubky specific errors === + +impl From for Error { + fn from(error: pubky_common::auth::Error) -> Self { + Self::new(StatusCode::BAD_REQUEST, Some(error)) + } +} + +impl From for Error { + fn from(error: pkarr::Error) -> Self { + Self::new(StatusCode::BAD_REQUEST, Some(error)) + } +} + +// === INTERNAL_SERVER_ERROR === + +impl From for Error { + fn from(error: std::io::Error) -> Self { + Self::new(StatusCode::INTERNAL_SERVER_ERROR, error.into()) + } +} + +impl From for Error { + fn from(error: heed::Error) -> Self { + Self::new(StatusCode::INTERNAL_SERVER_ERROR, error.into()) + } +} + +impl From for Error { + fn from(error: anyhow::Error) -> Self { + Self::new(StatusCode::INTERNAL_SERVER_ERROR, error.into()) + } +} + +impl From for Error { + fn from(error: postcard::Error) -> Self { + Self::new(StatusCode::INTERNAL_SERVER_ERROR, error.into()) + } +} + +impl From for Error { + fn from(error: axum::Error) -> Self { + Self::new(StatusCode::INTERNAL_SERVER_ERROR, error.into()) + } +} + +impl From> for Error { + fn from(error: flume::SendError) -> Self { + Self::new(StatusCode::INTERNAL_SERVER_ERROR, error.into()) + } +} diff --git a/rust/pubky/pubky-homeserver/src/extractors.rs b/rust/pubky/pubky-homeserver/src/extractors.rs new file mode 100644 index 0000000..567ca6b --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/extractors.rs @@ -0,0 +1,76 @@ +use std::collections::HashMap; + +use axum::{ + async_trait, + extract::{FromRequestParts, Path}, + http::{request::Parts, StatusCode}, + response::{IntoResponse, Response}, + RequestPartsExt, +}; + +use pkarr::PublicKey; + +use crate::error::{Error, Result}; + +#[derive(Debug)] +pub struct Pubky(PublicKey); + +impl Pubky { + pub fn public_key(&self) -> &PublicKey { + &self.0 + } +} + +#[async_trait] +impl FromRequestParts for Pubky +where + S: Send + Sync, +{ + type Rejection = Response; + + async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { + let params: Path> = + parts.extract().await.map_err(IntoResponse::into_response)?; + + let pubky_id = params + .get("pubky") + .ok_or_else(|| (StatusCode::NOT_FOUND, "pubky param missing").into_response())?; + + let public_key = PublicKey::try_from(pubky_id.to_string()) + .map_err(Error::try_from) + .map_err(IntoResponse::into_response)?; + + // TODO: return 404 if the user doesn't exist, but exclude signups. + + Ok(Pubky(public_key)) + } +} + +pub struct EntryPath(pub(crate) String); + +impl EntryPath { + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +#[async_trait] +impl FromRequestParts for EntryPath +where + S: Send + Sync, +{ + type Rejection = Response; + + async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { + let params: Path> = + parts.extract().await.map_err(IntoResponse::into_response)?; + + // TODO: enforce path limits like no trailing '/' + + let path = params + .get("path") + .ok_or_else(|| (StatusCode::NOT_FOUND, "entry path missing").into_response())?; + + Ok(EntryPath(path.to_string())) + } +} diff --git a/rust/pubky/pubky-homeserver/src/lib.rs b/rust/pubky/pubky-homeserver/src/lib.rs new file mode 100644 index 0000000..4a1253b --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/lib.rs @@ -0,0 +1,9 @@ +pub mod config; +mod database; +mod error; +mod extractors; +mod pkarr; +mod routes; +mod server; + +pub use server::Homeserver; diff --git a/rust/pubky/pubky-homeserver/src/main.rs b/rust/pubky/pubky-homeserver/src/main.rs new file mode 100644 index 0000000..dad25df --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/main.rs @@ -0,0 +1,46 @@ +use std::path::PathBuf; + +use anyhow::Result; +use pubky_homeserver::{config::Config, Homeserver}; + +use clap::Parser; + +#[derive(Parser, Debug)] +struct Cli { + /// [tracing_subscriber::EnvFilter] + #[clap(short, long)] + tracing_env_filter: Option, + + /// Run Homeserver in a local testnet + #[clap(long)] + testnet: bool, + + /// Optional Path to config file. + #[clap(short, long)] + config: Option, +} + +#[tokio::main] +async fn main() -> Result<()> { + let args = Cli::parse(); + + tracing_subscriber::fmt() + .with_env_filter( + args.tracing_env_filter + .unwrap_or("pubky_homeserver=debug,tower_http=debug".to_string()), + ) + .init(); + + let server = Homeserver::start(if args.testnet { + Config::testnet() + } else if let Some(config_path) = args.config { + Config::load(config_path).await? + } else { + Config::default() + }) + .await?; + + server.run_until_done().await?; + + Ok(()) +} diff --git a/rust/pubky/pubky-homeserver/src/pkarr.rs b/rust/pubky/pubky-homeserver/src/pkarr.rs new file mode 100644 index 0000000..cf4d7b7 --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/pkarr.rs @@ -0,0 +1,46 @@ +//! Pkarr related task + +use pkarr::{ + dns::{rdata::SVCB, Packet}, + Keypair, PkarrClientAsync, SignedPacket, +}; + +pub async fn publish_server_packet( + pkarr_client: PkarrClientAsync, + keypair: &Keypair, + domain: &str, + port: u16, +) -> anyhow::Result<()> { + // TODO: Try to resolve first before publishing. + + let mut packet = Packet::new_reply(0); + + let mut svcb = SVCB::new(0, domain.try_into()?); + + // Publishing port only for localhost domain, + // assuming any other domain will point to a reverse proxy + // at the conventional ports. + if domain == "localhost" { + svcb.priority = 1; + svcb.set_port(port); + + // TODO: Add more parameteres like the signer key! + // svcb.set_param(key, value) + }; + + // TODO: announce A/AAAA records as well for Noise connections? + // Or maybe Iroh's magic socket + + packet.answers.push(pkarr::dns::ResourceRecord::new( + "@".try_into().unwrap(), + pkarr::dns::CLASS::IN, + 60 * 60, + pkarr::dns::rdata::RData::SVCB(svcb), + )); + + let signed_packet = SignedPacket::from_packet(keypair, &packet)?; + + pkarr_client.publish(&signed_packet).await?; + + Ok(()) +} diff --git a/rust/pubky/pubky-homeserver/src/routes.rs b/rust/pubky/pubky-homeserver/src/routes.rs new file mode 100644 index 0000000..7422f20 --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/routes.rs @@ -0,0 +1,43 @@ +use axum::{ + extract::DefaultBodyLimit, + routing::{delete, get, post, put}, + Router, +}; +use tower_cookies::CookieManagerLayer; +use tower_http::{cors::CorsLayer, trace::TraceLayer}; + +use crate::server::AppState; + +use self::pkarr::pkarr_router; + +mod auth; +mod feed; +mod pkarr; +mod public; +mod root; + +fn base(state: AppState) -> Router { + Router::new() + .route("/", get(root::handler)) + .route("/signup", post(auth::signup)) + .route("/session", post(auth::signin)) + .route("/:pubky/session", get(auth::session)) + .route("/:pubky/session", delete(auth::signout)) + .route("/:pubky/*path", put(public::put)) + .route("/:pubky/*path", get(public::get)) + .route("/:pubky/*path", delete(public::delete)) + .route("/events/", get(feed::feed)) + .layer(CookieManagerLayer::new()) + // TODO: revisit if we enable streaming big payloads + // TODO: maybe add to a separate router (drive router?). + .layer(DefaultBodyLimit::max(16 * 1024)) + .with_state(state) +} + +pub fn create_app(state: AppState) -> Router { + base(state.clone()) + // TODO: Only enable this for test environments? + .nest("/pkarr", pkarr_router(state)) + .layer(CorsLayer::very_permissive()) + .layer(TraceLayer::new_for_http()) +} diff --git a/rust/pubky/pubky-homeserver/src/routes/auth.rs b/rust/pubky/pubky-homeserver/src/routes/auth.rs new file mode 100644 index 0000000..dbcffe4 --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/routes/auth.rs @@ -0,0 +1,138 @@ +use axum::{ + debug_handler, + extract::State, + http::{uri::Scheme, StatusCode, Uri}, + response::IntoResponse, +}; +use axum_extra::{headers::UserAgent, TypedHeader}; +use bytes::Bytes; +use tower_cookies::{cookie::SameSite, Cookie, Cookies}; + +use pubky_common::{crypto::random_bytes, session::Session, timestamp::Timestamp}; + +use crate::{ + database::tables::{ + sessions::{SessionsTable, SESSIONS_TABLE}, + users::User, + }, + error::{Error, Result}, + extractors::Pubky, + server::AppState, +}; + +#[debug_handler] +pub async fn signup( + State(state): State, + user_agent: Option>, + cookies: Cookies, + uri: Uri, + body: Bytes, +) -> Result { + // TODO: Verify invitation link. + // TODO: add errors in case of already axisting user. + signin(State(state), user_agent, cookies, uri, body).await +} + +pub async fn session( + State(state): State, + cookies: Cookies, + pubky: Pubky, +) -> Result { + if let Some(cookie) = cookies.get(&pubky.public_key().to_string()) { + let rtxn = state.db.env.read_txn()?; + + let sessions: SessionsTable = state + .db + .env + .open_database(&rtxn, Some(SESSIONS_TABLE))? + .expect("Session table already created"); + + if let Some(session) = sessions.get(&rtxn, cookie.value())? { + let session = session.to_owned(); + rtxn.commit()?; + + // TODO: add content-type + return Ok(session); + }; + + rtxn.commit()?; + }; + + Err(Error::with_status(StatusCode::NOT_FOUND)) +} + +pub async fn signout( + State(state): State, + cookies: Cookies, + pubky: Pubky, +) -> Result { + if let Some(cookie) = cookies.get(&pubky.public_key().to_string()) { + let mut wtxn = state.db.env.write_txn()?; + + let sessions: SessionsTable = state + .db + .env + .open_database(&wtxn, Some(SESSIONS_TABLE))? + .expect("Session table already created"); + + let _ = sessions.delete(&mut wtxn, cookie.value()); + + wtxn.commit()?; + + return Ok(()); + }; + + Err(Error::with_status(StatusCode::UNAUTHORIZED)) +} + +pub async fn signin( + State(state): State, + user_agent: Option>, + cookies: Cookies, + uri: Uri, + body: Bytes, +) -> Result { + let token = state.verifier.verify(&body)?; + + let public_key = token.pubky(); + + let mut wtxn = state.db.env.write_txn()?; + + let users = state.db.tables.users; + if let Some(existing) = users.get(&wtxn, public_key)? { + users.put(&mut wtxn, public_key, &existing)?; + } else { + users.put( + &mut wtxn, + public_key, + &User { + created_at: Timestamp::now().into_inner(), + }, + )?; + } + + let session_secret = base32::encode(base32::Alphabet::Crockford, &random_bytes::<16>()); + + let session = Session::new(&token, user_agent.map(|ua| ua.to_string())).serialize(); + + state + .db + .tables + .sessions + .put(&mut wtxn, &session_secret, &session)?; + + let mut cookie = Cookie::new(public_key.to_string(), session_secret); + + cookie.set_path("/"); + if *uri.scheme().unwrap_or(&Scheme::HTTP) == Scheme::HTTPS { + cookie.set_secure(true); + cookie.set_same_site(SameSite::None); + } + cookie.set_http_only(true); + + cookies.add(cookie); + + wtxn.commit()?; + + Ok(session) +} diff --git a/rust/pubky/pubky-homeserver/src/routes/feed.rs b/rust/pubky/pubky-homeserver/src/routes/feed.rs new file mode 100644 index 0000000..bd426f3 --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/routes/feed.rs @@ -0,0 +1,71 @@ +use std::collections::HashMap; + +use axum::{ + body::Body, + extract::{Query, State}, + http::{header, Response, StatusCode}, + response::IntoResponse, +}; + +use crate::{ + database::{tables::events::Event, MAX_LIST_LIMIT}, + error::Result, + server::AppState, +}; + +pub async fn feed( + State(state): State, + Query(params): Query>, +) -> Result { + let txn = state.db.env.read_txn()?; + + let limit = params + .get("limit") + .and_then(|l| l.parse::().ok()) + .unwrap_or(MAX_LIST_LIMIT) + .min(MAX_LIST_LIMIT); + + let mut cursor = params + .get("cursor") + .map(|c| c.as_str()) + .unwrap_or("0000000000000"); + + // Guard against bad cursor + if cursor.len() < 13 { + cursor = "0000000000000" + } + + let mut result: Vec = vec![]; + let mut next_cursor = cursor.to_string(); + + for _ in 0..limit { + match state + .db + .tables + .events + .get_greater_than(&txn, &next_cursor)? + { + Some((timestamp, event_bytes)) => { + let event = Event::deserialize(event_bytes)?; + + let line = format!("{} {}", event.operation(), event.url()); + next_cursor = timestamp.to_string(); + + result.push(line); + } + None => break, + }; + } + + if !result.is_empty() { + result.push(format!("cursor: {next_cursor}")) + } + + txn.commit()?; + + Ok(Response::builder() + .status(StatusCode::OK) + .header(header::CONTENT_TYPE, "text/plain") + .body(Body::from(result.join("\n"))) + .unwrap()) +} diff --git a/rust/pubky/pubky-homeserver/src/routes/pkarr.rs b/rust/pubky/pubky-homeserver/src/routes/pkarr.rs new file mode 100644 index 0000000..9e40230 --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/routes/pkarr.rs @@ -0,0 +1,58 @@ +use axum::{ + body::{Body, Bytes}, + extract::State, + http::StatusCode, + response::IntoResponse, + routing::{get, put}, + Router, +}; +use futures_util::stream::StreamExt; + +use pkarr::SignedPacket; + +use crate::{ + error::{Error, Result}, + extractors::Pubky, + server::AppState, +}; + +/// Pkarr relay, helpful for testing. +/// +/// For real productioin, you should use a [production ready +/// relay](https://github.com/pubky/pkarr/server). +pub fn pkarr_router(state: AppState) -> Router { + Router::new() + .route("/:pubky", put(pkarr_put)) + .route("/:pubky", get(pkarr_get)) + .with_state(state) +} + +pub async fn pkarr_put( + State(state): State, + pubky: Pubky, + body: Body, +) -> Result { + let mut bytes = Vec::with_capacity(1104); + + let mut stream = body.into_data_stream(); + + while let Some(chunk) = stream.next().await { + bytes.extend_from_slice(&chunk?) + } + + let public_key = pubky.public_key().to_owned(); + + let signed_packet = SignedPacket::from_relay_payload(&public_key, &Bytes::from(bytes))?; + + state.pkarr_client.publish(&signed_packet).await?; + + Ok(()) +} + +pub async fn pkarr_get(State(state): State, pubky: Pubky) -> Result { + if let Some(signed_packet) = state.pkarr_client.resolve(pubky.public_key()).await? { + return Ok(signed_packet.to_relay_payload()); + } + + Err(Error::with_status(StatusCode::NOT_FOUND)) +} diff --git a/rust/pubky/pubky-homeserver/src/routes/public.rs b/rust/pubky/pubky-homeserver/src/routes/public.rs new file mode 100644 index 0000000..4cf2eed --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/routes/public.rs @@ -0,0 +1,176 @@ +use std::collections::HashMap; + +use axum::{ + body::{Body, Bytes}, + extract::{Query, State}, + http::{header, Response, StatusCode}, + response::IntoResponse, +}; +use futures_util::stream::StreamExt; +use pkarr::PublicKey; +use tower_cookies::Cookies; + +use crate::{ + error::{Error, Result}, + extractors::{EntryPath, Pubky}, + server::AppState, +}; + +pub async fn put( + State(mut state): State, + pubky: Pubky, + path: EntryPath, + cookies: Cookies, + body: Body, +) -> Result { + let public_key = pubky.public_key().clone(); + let path = path.as_str(); + + verify(path)?; + authorize(&mut state, cookies, &public_key, path)?; + + let mut stream = body.into_data_stream(); + + let (tx, rx) = flume::bounded::(1); + + let path = path.to_string(); + + // TODO: refactor Database to clean up this scope. + let done = tokio::task::spawn_blocking(move || -> Result<()> { + // TODO: this is a blocking operation, which is ok for small + // payloads (we have 16 kb limit for now) but later we need + // to stream this to filesystem, and keep track of any failed + // writes to GC these files later. + + state.db.put_entry(&public_key, &path, rx)?; + + Ok(()) + }); + + while let Some(next) = stream.next().await { + let chunk = next?; + + tx.send(chunk)?; + } + + drop(tx); + done.await.expect("join error")?; + + // TODO: return relevant headers, like Etag? + + Ok(()) +} + +pub async fn get( + State(state): State, + pubky: Pubky, + path: EntryPath, + Query(params): Query>, +) -> Result { + verify(path.as_str())?; + let public_key = pubky.public_key(); + + let path = path.as_str(); + + if path.ends_with('/') { + let txn = state.db.env.read_txn()?; + + let path = format!("{public_key}/{path}"); + + if !state.db.contains_directory(&txn, &path)? { + return Err(Error::new( + StatusCode::NOT_FOUND, + "Directory Not Found".into(), + )); + } + + // Handle listing + let vec = state.db.list( + &txn, + &path, + params.contains_key("reverse"), + params.get("limit").and_then(|l| l.parse::().ok()), + params.get("cursor").map(|cursor| cursor.into()), + params.contains_key("shallow"), + )?; + + return Ok(Response::builder() + .status(StatusCode::OK) + .header(header::CONTENT_TYPE, "text/plain") + .body(Body::from(vec.join("\n"))) + .unwrap()); + } + + // TODO: Enable streaming + + match state.db.get_blob(public_key, path) { + Err(error) => Err(error)?, + Ok(Some(bytes)) => Ok(Response::builder().body(Body::from(bytes)).unwrap()), + Ok(None) => Err(Error::new(StatusCode::NOT_FOUND, "File Not Found".into())), + } +} + +pub async fn delete( + State(mut state): State, + pubky: Pubky, + path: EntryPath, + cookies: Cookies, +) -> Result { + let public_key = pubky.public_key().clone(); + let path = path.as_str(); + + authorize(&mut state, cookies, &public_key, path)?; + verify(path)?; + + let deleted = state.db.delete_entry(&public_key, path)?; + + if !deleted { + // TODO: if the path ends with `/` return a `CONFLICT` error? + return Err(Error::with_status(StatusCode::NOT_FOUND)); + } + + // TODO: return relevant headers, like Etag? + + Ok(()) +} + +/// Authorize write (PUT or DELETE) for Public paths. +fn authorize( + state: &mut AppState, + cookies: Cookies, + public_key: &PublicKey, + path: &str, +) -> Result<()> { + // TODO: can we move this logic to the extractor or a layer + // to perform this validation? + let session = state + .db + .get_session(cookies, public_key)? + .ok_or(Error::with_status(StatusCode::UNAUTHORIZED))?; + + if session.pubky() == public_key + && session.capabilities().iter().any(|cap| { + path.starts_with(&cap.scope[1..]) + && cap + .actions + .contains(&pubky_common::capabilities::Action::Write) + }) + { + return Ok(()); + } + + Err(Error::with_status(StatusCode::FORBIDDEN)) +} + +fn verify(path: &str) -> Result<()> { + if !path.starts_with("pub/") { + return Err(Error::new( + StatusCode::FORBIDDEN, + "Writing to directories other than '/pub/' is forbidden".into(), + )); + } + + // TODO: should we forbid paths ending with `/`? + + Ok(()) +} diff --git a/rust/pubky/pubky-homeserver/src/routes/root.rs b/rust/pubky/pubky-homeserver/src/routes/root.rs new file mode 100644 index 0000000..35a9482 --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/routes/root.rs @@ -0,0 +1,5 @@ +use axum::response::IntoResponse; + +pub async fn handler() -> Result { + Ok("This a Pubky homeserver.".to_string()) +} diff --git a/rust/pubky/pubky-homeserver/src/server.rs b/rust/pubky/pubky-homeserver/src/server.rs new file mode 100644 index 0000000..c94a803 --- /dev/null +++ b/rust/pubky/pubky-homeserver/src/server.rs @@ -0,0 +1,161 @@ +use std::{future::IntoFuture, net::SocketAddr}; + +use anyhow::{Error, Result}; +use pubky_common::auth::AuthVerifier; +use tokio::{net::TcpListener, signal, task::JoinSet}; +use tracing::{debug, info, warn}; + +use pkarr::{ + mainline::dht::{DhtSettings, Testnet}, + PkarrClient, PkarrClientAsync, PublicKey, Settings, +}; + +use crate::{config::Config, database::DB, pkarr::publish_server_packet}; + +#[derive(Debug)] +pub struct Homeserver { + port: u16, + config: Config, + tasks: JoinSet>, +} + +#[derive(Clone, Debug)] +pub(crate) struct AppState { + pub verifier: AuthVerifier, + pub db: DB, + pub pkarr_client: PkarrClientAsync, +} + +impl Homeserver { + pub async fn start(config: Config) -> Result { + debug!(?config); + + let keypair = config.keypair(); + + let db = DB::open(&config.storage()?)?; + + let pkarr_client = PkarrClient::new(Settings { + dht: DhtSettings { + bootstrap: config.bootstsrap(), + request_timeout: config.dht_request_timeout(), + ..Default::default() + }, + ..Default::default() + })? + .as_async(); + + let state = AppState { + verifier: AuthVerifier::default(), + db, + pkarr_client: pkarr_client.clone(), + }; + + let app = crate::routes::create_app(state); + + let mut tasks = JoinSet::new(); + + let app = app.clone(); + + let listener = TcpListener::bind(SocketAddr::from(([0, 0, 0, 0], config.port()))).await?; + + let port = listener.local_addr()?.port(); + + // Spawn http server task + tasks.spawn( + axum::serve( + listener, + app.into_make_service_with_connect_info::(), + ) + .with_graceful_shutdown(shutdown_signal()) + .into_future(), + ); + + info!("Homeserver listening on http://localhost:{port}"); + + publish_server_packet(pkarr_client, &keypair, config.domain(), port).await?; + + info!("Homeserver listening on pubky://{}", keypair.public_key()); + + Ok(Self { + tasks, + config, + port, + }) + } + + /// Test version of [Homeserver::start], using mainline Testnet, and a temporary storage. + pub async fn start_test(testnet: &Testnet) -> Result { + info!("Running testnet.."); + + Homeserver::start(Config::test(testnet)).await + } + + // === Getters === + + pub fn port(&self) -> u16 { + self.port + } + + pub fn public_key(&self) -> PublicKey { + self.config.keypair().public_key() + } + + // === Public Methods === + + /// Shutdown the server and wait for all tasks to complete. + pub async fn shutdown(mut self) -> Result<()> { + self.tasks.abort_all(); + self.run_until_done().await?; + Ok(()) + } + + /// Wait for all tasks to complete. + /// + /// Runs forever unless tasks fail. + pub async fn run_until_done(mut self) -> Result<()> { + let mut final_res: Result<()> = Ok(()); + while let Some(res) = self.tasks.join_next().await { + match res { + Ok(Ok(())) => {} + Err(err) if err.is_cancelled() => {} + Ok(Err(err)) => { + warn!(?err, "task failed"); + final_res = Err(Error::from(err)); + } + Err(err) => { + warn!(?err, "task panicked"); + final_res = Err(err.into()); + } + } + } + final_res + } +} + +async fn shutdown_signal() { + let ctrl_c = async { + signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + fn graceful_shutdown() { + info!("Gracefully Shutting down.."); + } + + tokio::select! { + _ = ctrl_c => graceful_shutdown(), + _ = terminate => graceful_shutdown(), + } +} diff --git a/rust/pubky/pubky/Cargo.toml b/rust/pubky/pubky/Cargo.toml new file mode 100644 index 0000000..6871377 --- /dev/null +++ b/rust/pubky/pubky/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "pubky" +version = "0.1.0" +edition = "2021" +description = "Pubky client" +license = "MIT" +repository = "https://github.com/pubky/pubky" +keywords = ["web", "dht", "dns", "decentralized", "identity"] + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +thiserror = "1.0.62" +wasm-bindgen = "0.2.92" +url = "2.5.2" +bytes = "^1.7.1" +base64 = "0.22.1" + +pubky-common = { version = "0.1.0", path = "../pubky-common" } +pkarr = { workspace = true, features = ["async"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +reqwest = { version = "0.12.5", features = ["cookies", "rustls-tls"], default-features = false } +tokio = { version = "1.37.0", features = ["full"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +reqwest = { version = "0.12.5", default-features = false } + +js-sys = "0.3.69" +wasm-bindgen = "0.2.92" +wasm-bindgen-futures = "0.4.42" + +[dev-dependencies] +pubky_homeserver = { path = "../pubky-homeserver" } +tokio = "1.37.0" + +[features] + +[package.metadata.docs.rs] +all-features = true + +[package.metadata.wasm-pack.profile.release] +wasm-opt = ['-g', '-O'] diff --git a/rust/pubky/pubky/pkg/.gitignore b/rust/pubky/pubky/pkg/.gitignore new file mode 100644 index 0000000..7355b75 --- /dev/null +++ b/rust/pubky/pubky/pkg/.gitignore @@ -0,0 +1,6 @@ +index.cjs +browser.js +coverage +node_modules +package-lock.json +pubky* diff --git a/rust/pubky/pubky/pkg/LICENSE b/rust/pubky/pubky/pkg/LICENSE new file mode 100644 index 0000000..a0e67c5 --- /dev/null +++ b/rust/pubky/pubky/pkg/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2023 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/rust/pubky/pubky/pkg/README.md b/rust/pubky/pubky/pkg/README.md new file mode 100644 index 0000000..2228266 --- /dev/null +++ b/rust/pubky/pubky/pkg/README.md @@ -0,0 +1,266 @@ +# Pubky + +JavaScript implementation of [Pubky](https://github.com/pubky/pubky). + +## Table of Contents +- [Install](#install) +- [Getting Started](#getting-started) +- [API](#api) +- [Test and Development](#test-and-development) + +## Install + +```bash +npm install @synonymdev/pubky +``` + +### Prerequisites + +For Nodejs, you need Node v20 or later. + +## Getting started + +```js +import { PubkyClient, Keypair, PublicKey } from '../index.js' + +// Initialize PubkyClient with Pkarr relay(s). +let client = new PubkyClient(); + +// Generate a keypair +let keypair = Keypair.random(); + +// Create a new account +let homeserver = PublicKey.from("8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo"); + +await client.signup(keypair, homeserver) + +const publicKey = keypair.publicKey(); + +// Pubky URL +let url = `pubky://${publicKey.z32()}/pub/example.com/arbitrary`; + +// Verify that you are signed in. +const session = await client.session(publicKey) + +const body = Buffer.from(JSON.stringify({ foo: 'bar' })) + +// PUT public data, by authorized client +await client.put(url, body); + +// GET public data without signup or signin +{ + const client = new PubkyClient(); + + let response = await client.get(url); +} + +// Delete public data, by authorized client +await client.delete(url); +``` + +## API + +### PubkyClient + +#### constructor +```js +let client = new PubkyClient() +``` + +#### signup +```js +await client.signup(keypair, homeserver) +``` +- keypair: An instance of [Keypair](#keypair). +- homeserver: An instance of [PublicKey](#publickey) representing the homeserver. + +Returns: +- session: An instance of [Session](#session). + +#### signin +```js +let session = await client.signin(keypair) +``` +- keypair: An instance of [Keypair](#keypair). + +Returns: +- An instance of [Session](#session). + +#### signout +```js +await client.signout(publicKey) +``` +- publicKey: An instance of [PublicKey](#publicKey). + +#### authRequest +```js +let [pubkyauthUrl, sessionPromise] = client.authRequest(relay, capabilities); + +showQr(pubkyauthUrl); + +let session = await sessionPromise; +``` + +Sign in to a user's Homeserver, without access to their [Keypair](#keypair), nor even [PublicKey](#publickey), +instead request permissions (showing the user pubkyauthUrl), and await a Session after the user consenting to that request. + +- relay: A URL to an [HTTP relay](https://httprelay.io/features/link/) endpoint. +- capabilities: A list of capabilities required for the app for example `/pub/pubky.app/:rw,/pub/example.com/:r`. + +Returns: +- pubkyauthUrl: A url to show to the user to scan or paste into an Authenticator app holding the user [Keypair](#keypair) +- sessionPromise: A promise that resolves into a [Session](#session) on success. + +#### sendAuthToken +```js +await client.sendAuthToken(keypair, pubkyauthUrl); +``` +Consenting to authentication or authorization according to the required capabilities in the `pubkyauthUrl` , and sign and send an auth token to the requester. + +- keypair: An instance of [KeyPair](#keypair) +- pubkyauthUrl: A string `pubkyauth://` url + +#### session {#session-method} +```js +let session = await client.session(publicKey) +``` +- publicKey: An instance of [PublicKey](#publickey). +- Returns: A [Session](#session) object if signed in, or undefined if not. + +#### put +```js +let response = await client.put(url, body); +``` +- url: A string representing the Pubky URL. +- body: A Buffer containing the data to be stored. + +### get +```js +let response = await client.get(url) +``` +- url: A string representing the Pubky URL. +- Returns: A response object containing the requested data. + +### delete + +```js +let response = await client.delete(url); +``` +- url: A string representing the Pubky URL. + +### list +```js +let response = await client.list(url, cursor, reverse, limit) +``` +- url: A string representing the Pubky URL. The path in that url is the prefix that you want to list files within. +- cursor: Usually the last URL from previous calls. List urls after/before (depending on `reverse`) the cursor. +- reverse: Whether or not return urls in reverse order. +- limit: Number of urls to return. +- Returns: A list of URLs of the files in the `url` you passed. + +### Keypair + +#### random +```js +let keypair = Keypair.random() +``` +- Returns: A new random Keypair. + +#### fromSecretKey +```js +let keypair = Keypair.fromSecretKey(secretKey) +``` +- secretKey: A 32 bytes Uint8array. +- Returns: A new Keypair. + + +#### publicKey {#publickey-method} +```js +let publicKey = keypair.publicKey() +``` +- Returns: The [PublicKey](#publickey) associated with the Keypair. + +#### secretKey +```js +let secretKey = keypair.secretKey() +``` +- Returns: The Uint8array secret key associated with the Keypair. + +### PublicKey + +#### from + +```js +let publicKey = PublicKey.from(string); +``` +- string: A string representing the public key. +- Returns: A new PublicKey instance. + +#### z32 +```js +let pubky = publicKey.z32(); +``` +Returns: The z-base-32 encoded string representation of the PublicKey. + +### Session + +#### pubky +```js +let pubky = session.pubky(); +``` +Returns an instance of [PublicKey](#publickey) + +#### capabilities +```js +let capabilities = session.capabilities(); +``` +Returns an array of capabilities, for example `["/pub/pubky.app/:rw"]` + +### Helper functions + +#### createRecoveryFile +```js +let recoveryFile = createRecoveryFile(keypair, passphrase) +``` +- keypair: An instance of [Keypair](#keypair). +- passphrase: A utf-8 string [passphrase](https://www.useapassphrase.com/). +- Returns: A recovery file with a spec line and an encrypted secret key. + +#### createRecoveryFile +```js +let keypair = decryptRecoveryfile(recoveryFile, passphrase) +``` +- recoveryFile: An instance of Uint8Array containing the recovery file blob. +- passphrase: A utf-8 string [passphrase](https://www.useapassphrase.com/). +- Returns: An instance of [Keypair](#keypair). + +## Test and Development + +For test and development, you can run a local homeserver in a test network. + +If you don't have Cargo Installed, start by installing it: + +```bash +curl https://sh.rustup.rs -sSf | sh +``` + +Clone the Pubky repository: + +```bash +git clone https://github.com/pubky/pubky +cd pubky/pkg +``` + +Run the local testnet server + +```bash +npm run testnet +``` + +Use the logged addresses as inputs to `PubkyClient` + +```js +import { PubkyClient } from '../index.js' + +const client = PubkyClient().testnet(); +``` diff --git a/rust/pubky/pubky/pkg/package.json b/rust/pubky/pubky/pkg/package.json new file mode 100644 index 0000000..c612adf --- /dev/null +++ b/rust/pubky/pubky/pkg/package.json @@ -0,0 +1,41 @@ +{ + "name": "@synonymdev/pubky", + "type": "module", + "description": "Pubky client", + "version": "0.1.15", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pubky/pubky" + }, + "scripts": { + "testnet": "cargo run -p pubky_homeserver -- --testnet", + "test": "npm run test-nodejs && npm run test-browser", + "test-nodejs": "tape test/*.js -cov", + "test-browser": "browserify test/*.js -p esmify | npx tape-run", + "build": "cargo run --bin bundle_pubky_npm", + "prepublishOnly": "npm run build && npm run test" + }, + "files": [ + "index.cjs", + "browser.js", + "pubky.d.ts", + "pubky_bg.wasm" + ], + "main": "index.cjs", + "browser": "browser.js", + "types": "pubky.d.ts", + "keywords": [ + "web", + "dht", + "dns", + "decentralized", + "identity" + ], + "devDependencies": { + "browser-resolve": "^2.0.0", + "esmify": "^2.1.1", + "tape": "^5.8.1", + "tape-run": "^11.0.0" + } +} diff --git a/rust/pubky/pubky/pkg/test/auth.js b/rust/pubky/pubky/pkg/test/auth.js new file mode 100644 index 0000000..2207946 --- /dev/null +++ b/rust/pubky/pubky/pkg/test/auth.js @@ -0,0 +1,63 @@ +import test from 'tape' + +import { PubkyClient, Keypair, PublicKey } from '../index.cjs' + +const Homeserver = PublicKey.from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo') + +test('auth', async (t) => { + const client = PubkyClient.testnet(); + + const keypair = Keypair.random() + const publicKey = keypair.publicKey() + + await client.signup(keypair, Homeserver) + + const session = await client.session(publicKey) + t.ok(session, "signup") + + { + await client.signout(publicKey) + + const session = await client.session(publicKey) + t.notOk(session, "singout") + } + + { + await client.signin(keypair) + + const session = await client.session(publicKey) + t.ok(session, "signin") + } +}) + +test("3rd party signin", async (t) => { + let keypair = Keypair.random(); + let pubky = keypair.publicKey().z32(); + + // Third party app side + let capabilities = "/pub/pubky.app/:rw,/pub/foo.bar/file:r"; + let client = PubkyClient.testnet(); + let [pubkyauth_url, pubkyauthResponse] = client + .authRequest("https://demo.httprelay.io/link", capabilities); + + if (globalThis.document) { + // Skip `sendAuthToken` in browser + // TODO: figure out why does it fail in browser unit tests + // but not in real browser (check pubky-auth-widget.js commented part) + return + } + + // Authenticator side + { + let client = PubkyClient.testnet(); + + await client.signup(keypair, Homeserver); + + await client.sendAuthToken(keypair, pubkyauth_url) + } + + let session = await pubkyauthResponse; + + t.is(session.pubky().z32(), pubky) + t.deepEqual(session.capabilities(), capabilities.split(',')) +}) diff --git a/rust/pubky/pubky/pkg/test/keys.js b/rust/pubky/pubky/pkg/test/keys.js new file mode 100644 index 0000000..d036862 --- /dev/null +++ b/rust/pubky/pubky/pkg/test/keys.js @@ -0,0 +1,21 @@ +import test from 'tape' + +import { Keypair } from '../index.cjs' + +test('generate keys from a seed', async (t) => { + const secretkey = Buffer.from('5aa93b299a343aa2691739771f2b5b85e740ca14c685793d67870f88fa89dc51', 'hex') + + const keypair = Keypair.fromSecretKey(secretkey) + + const publicKey = keypair.publicKey() + + t.is(publicKey.z32(), 'gcumbhd7sqit6nn457jxmrwqx9pyymqwamnarekgo3xppqo6a19o') +}) + +test('fromSecretKey error', async (t) => { + const secretkey = Buffer.from('5aa93b299a343aa2691739771f2b5b', 'hex') + + + t.throws(() => Keypair.fromSecretKey(null), /Expected secret_key to be an instance of Uint8Array/) + t.throws(() => Keypair.fromSecretKey(secretkey), /Expected secret_key to be 32 bytes, got 15/) +}) diff --git a/rust/pubky/pubky/pkg/test/public.js b/rust/pubky/pubky/pkg/test/public.js new file mode 100644 index 0000000..ec30bb2 --- /dev/null +++ b/rust/pubky/pubky/pkg/test/public.js @@ -0,0 +1,351 @@ +import test from 'tape' + +import { PubkyClient, Keypair, PublicKey } from '../index.cjs' + +const Homeserver = PublicKey.from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo'); + +test('public: put/get', async (t) => { + const client = PubkyClient.testnet(); + + const keypair = Keypair.random(); + + await client.signup(keypair, Homeserver); + + const publicKey = keypair.publicKey(); + + let url = `pubky://${publicKey.z32()}/pub/example.com/arbitrary`; + + const body = Buffer.from(JSON.stringify({ foo: 'bar' })) + + // PUT public data, by authorized client + await client.put(url, body); + + const otherClient = PubkyClient.testnet(); + + // GET public data without signup or signin + { + let response = await otherClient.get(url); + + t.ok(Buffer.from(response).equals(body)) + } + + // DELETE public data, by authorized client + await client.delete(url); + + + // GET public data without signup or signin + { + let response = await otherClient.get(url); + + t.notOk(response) + } +}) + +test("not found", async (t) => { + const client = PubkyClient.testnet(); + + + const keypair = Keypair.random(); + + await client.signup(keypair, Homeserver); + + const publicKey = keypair.publicKey(); + + let url = `pubky://${publicKey.z32()}/pub/example.com/arbitrary`; + + let result = await client.get(url).catch(e => e); + + t.notOk(result); +}) + +test("unauthorized", async (t) => { + const client = PubkyClient.testnet(); + + const keypair = Keypair.random() + const publicKey = keypair.publicKey() + + await client.signup(keypair, Homeserver) + + const session = await client.session(publicKey) + t.ok(session, "signup") + + await client.signout(publicKey) + + const body = Buffer.from(JSON.stringify({ foo: 'bar' })) + + let url = `pubky://${publicKey.z32()}/pub/example.com/arbitrary`; + + // PUT public data, by authorized client + let result = await client.put(url, body).catch(e => e); + + t.ok(result instanceof Error); + t.is( + result.message, + `HTTP status client error (401 Unauthorized) for url (http://localhost:15411/${publicKey.z32()}/pub/example.com/arbitrary)` + ) +}) + +test("forbidden", async (t) => { + const client = PubkyClient.testnet(); + + const keypair = Keypair.random() + const publicKey = keypair.publicKey() + + await client.signup(keypair, Homeserver) + + const session = await client.session(publicKey) + t.ok(session, "signup") + + const body = Buffer.from(JSON.stringify({ foo: 'bar' })) + + let url = `pubky://${publicKey.z32()}/priv/example.com/arbitrary`; + + // PUT public data, by authorized client + let result = await client.put(url, body).catch(e => e); + + t.ok(result instanceof Error); + t.is( + result.message, + `HTTP status client error (403 Forbidden) for url (http://localhost:15411/${publicKey.z32()}/priv/example.com/arbitrary)` + ) +}) + +test("list", async (t) => { + const client = PubkyClient.testnet(); + + const keypair = Keypair.random() + const publicKey = keypair.publicKey() + const pubky = publicKey.z32() + + await client.signup(keypair, Homeserver) + + + + let urls = [ + `pubky://${pubky}/pub/a.wrong/a.txt`, + `pubky://${pubky}/pub/example.com/a.txt`, + `pubky://${pubky}/pub/example.com/b.txt`, + `pubky://${pubky}/pub/example.wrong/a.txt`, + `pubky://${pubky}/pub/example.com/c.txt`, + `pubky://${pubky}/pub/example.com/d.txt`, + `pubky://${pubky}/pub/z.wrong/a.txt`, + ] + + for (let url of urls) { + await client.put(url, Buffer.from("")); + } + + let url = `pubky://${pubky}/pub/example.com/`; + + { + let list = await client.list(url); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/example.com/a.txt`, + `pubky://${pubky}/pub/example.com/b.txt`, + `pubky://${pubky}/pub/example.com/c.txt`, + `pubky://${pubky}/pub/example.com/d.txt`, + + ], + "normal list with no limit or cursor" + ); + } + + { + let list = await client.list(url, null, null, 2); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/example.com/a.txt`, + `pubky://${pubky}/pub/example.com/b.txt`, + + ], + "normal list with limit but no cursor" + ); + } + + { + let list = await client.list(url, "a.txt", null, 2); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/example.com/b.txt`, + `pubky://${pubky}/pub/example.com/c.txt`, + + ], + "normal list with limit and a suffix cursor" + ); + } + + { + let list = await client.list(url, `pubky://${pubky}/pub/example.com/a.txt`, null, 2); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/example.com/b.txt`, + `pubky://${pubky}/pub/example.com/c.txt`, + + ], + "normal list with limit and a full url cursor" + ); + } + + + { + let list = await client.list(url, null, true); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/example.com/d.txt`, + `pubky://${pubky}/pub/example.com/c.txt`, + `pubky://${pubky}/pub/example.com/b.txt`, + `pubky://${pubky}/pub/example.com/a.txt`, + + ], + "reverse list with no limit or cursor" + ); + } + + { + let list = await client.list(url, null, true, 2); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/example.com/d.txt`, + `pubky://${pubky}/pub/example.com/c.txt`, + + ], + "reverse list with limit but no cursor" + ); + } + + { + let list = await client.list(url, "d.txt", true, 2); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/example.com/c.txt`, + `pubky://${pubky}/pub/example.com/b.txt`, + + ], + "reverse list with limit and a suffix cursor" + ); + } +}) + +test('list shallow', async (t) => { + const client = PubkyClient.testnet(); + + const keypair = Keypair.random() + const publicKey = keypair.publicKey() + const pubky = publicKey.z32() + + await client.signup(keypair, Homeserver) + + let urls = [ + `pubky://${pubky}/pub/a.com/a.txt`, + `pubky://${pubky}/pub/example.com/a.txt`, + `pubky://${pubky}/pub/example.com/b.txt`, + `pubky://${pubky}/pub/example.com/c.txt`, + `pubky://${pubky}/pub/example.com/d.txt`, + `pubky://${pubky}/pub/example.con/d.txt`, + `pubky://${pubky}/pub/example.con`, + `pubky://${pubky}/pub/file`, + `pubky://${pubky}/pub/file2`, + `pubky://${pubky}/pub/z.com/a.txt`, + ] + + for (let url of urls) { + await client.put(url, Buffer.from("")); + } + + let url = `pubky://${pubky}/pub/`; + + { + let list = await client.list(url, null, false, null, true); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/a.com/`, + `pubky://${pubky}/pub/example.com/`, + `pubky://${pubky}/pub/example.con`, + `pubky://${pubky}/pub/example.con/`, + `pubky://${pubky}/pub/file`, + `pubky://${pubky}/pub/file2`, + `pubky://${pubky}/pub/z.com/`, + ], + "normal list shallow" + ); + } + + { + let list = await client.list(url, null, false, 3, true); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/a.com/`, + `pubky://${pubky}/pub/example.com/`, + `pubky://${pubky}/pub/example.con`, + ], + "normal list shallow with limit" + ); + } + + { + let list = await client.list(url, `example.com/`, false, null, true); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/example.con`, + `pubky://${pubky}/pub/example.con/`, + `pubky://${pubky}/pub/file`, + `pubky://${pubky}/pub/file2`, + `pubky://${pubky}/pub/z.com/`, + ], + "normal list shallow with cursor" + ); + } + + { + let list = await client.list(url, null, true, null, true); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/z.com/`, + `pubky://${pubky}/pub/file2`, + `pubky://${pubky}/pub/file`, + `pubky://${pubky}/pub/example.con/`, + `pubky://${pubky}/pub/example.con`, + `pubky://${pubky}/pub/example.com/`, + `pubky://${pubky}/pub/a.com/`, + ], + "normal list shallow" + ); + } + + { + let list = await client.list(url, null, true, 3, true); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/z.com/`, + `pubky://${pubky}/pub/file2`, + `pubky://${pubky}/pub/file`, + ], + "normal list shallow with limit" + ); + } +}) diff --git a/rust/pubky/pubky/pkg/test/recovery.js b/rust/pubky/pubky/pkg/test/recovery.js new file mode 100644 index 0000000..0c033e5 --- /dev/null +++ b/rust/pubky/pubky/pkg/test/recovery.js @@ -0,0 +1,19 @@ +import test from 'tape' + +import { Keypair, createRecoveryFile, decryptRecoveryFile } from '../index.cjs' + +test('recovery', async (t) => { + const keypair = Keypair.random(); + + const recoveryFile = createRecoveryFile(keypair, 'very secure password'); + + t.is(recoveryFile.length, 91) + t.deepEqual( + Array.from(recoveryFile.slice(0, 19)), + [112, 117, 98, 107, 121, 46, 111, 114, 103, 47, 114, 101, 99, 111, 118, 101, 114, 121, 10] + ) + + const recovered = decryptRecoveryFile(recoveryFile, 'very secure password') + + t.is(recovered.publicKey().z32(), keypair.publicKey().z32()) +}) diff --git a/rust/pubky/pubky/src/bin/bundle_pubky_npm.rs b/rust/pubky/pubky/src/bin/bundle_pubky_npm.rs new file mode 100644 index 0000000..40e9b90 --- /dev/null +++ b/rust/pubky/pubky/src/bin/bundle_pubky_npm.rs @@ -0,0 +1,65 @@ +use std::env; +use std::io; +use std::process::{Command, ExitStatus}; + +// If the process hangs, try `cargo clean` to remove all locks. + +fn main() { + println!("Building wasm for pubky..."); + + build_wasm("nodejs").unwrap(); + patch().unwrap(); +} + +fn build_wasm(target: &str) -> io::Result { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + + let output = Command::new("wasm-pack") + .args([ + "build", + &manifest_dir, + "--release", + "--target", + target, + "--out-dir", + &format!("pkg/{}", target), + ]) + .output()?; + + println!( + "wasm-pack {target} output: {}", + String::from_utf8_lossy(&output.stdout) + ); + + if !output.status.success() { + eprintln!( + "wasm-pack failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + + Ok(output.status) +} + +fn patch() -> io::Result { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + + println!("{manifest_dir}/src/bin/patch.mjs"); + let output = Command::new("node") + .args([format!("{manifest_dir}/src/bin/patch.mjs")]) + .output()?; + + println!( + "patch.mjs output: {}", + String::from_utf8_lossy(&output.stdout) + ); + + if !output.status.success() { + eprintln!( + "patch.mjs failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + + Ok(output.status) +} diff --git a/rust/pubky/pubky/src/bin/patch.mjs b/rust/pubky/pubky/src/bin/patch.mjs new file mode 100644 index 0000000..a8ed503 --- /dev/null +++ b/rust/pubky/pubky/src/bin/patch.mjs @@ -0,0 +1,66 @@ +// This script is used to generate isomorphic code for web and nodejs +// +// Based on hacks from [this issue](https://github.com/rustwasm/wasm-pack/issues/1334) + +import { readFile, writeFile, rename } from "node:fs/promises"; +import { fileURLToPath } from 'node:url'; +import path, { dirname } from 'node:path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const cargoTomlContent = await readFile(path.join(__dirname, "../../Cargo.toml"), "utf8"); +const cargoPackageName = /\[package\]\nname = "(.*?)"/.exec(cargoTomlContent)[1] +const name = cargoPackageName.replace(/-/g, '_') + +const content = await readFile(path.join(__dirname, `../../pkg/nodejs/${name}.js`), "utf8"); + +const patched = content + // use global TextDecoder TextEncoder + .replace("require(`util`)", "globalThis") + // attach to `imports` instead of module.exports + .replace("= module.exports", "= imports") + // Export classes + .replace(/\nclass (.*?) \{/g, "\n export class $1 {") + // Export functions + .replace(/\nmodule.exports.(.*?) = function/g, "\nimports.$1 = $1;\nexport function $1") + // Add exports to 'imports' + .replace(/\nmodule\.exports\.(.*?)\s+/g, "\nimports.$1") + // Export default + .replace(/$/, 'export default imports') + // inline wasm bytes + .replace( + /\nconst path.*\nconst bytes.*\n/, + ` +var __toBinary = /* @__PURE__ */ (() => { + var table = new Uint8Array(128); + for (var i = 0; i < 64; i++) + table[i < 26 ? i + 65 : i < 52 ? i + 71 : i < 62 ? i - 4 : i * 4 - 205] = i; + return (base64) => { + var n = base64.length, bytes = new Uint8Array((n - (base64[n - 1] == "=") - (base64[n - 2] == "=")) * 3 / 4 | 0); + for (var i2 = 0, j = 0; i2 < n; ) { + var c0 = table[base64.charCodeAt(i2++)], c1 = table[base64.charCodeAt(i2++)]; + var c2 = table[base64.charCodeAt(i2++)], c3 = table[base64.charCodeAt(i2++)]; + bytes[j++] = c0 << 2 | c1 >> 4; + bytes[j++] = c1 << 4 | c2 >> 2; + bytes[j++] = c2 << 6 | c3; + } + return bytes; + }; +})(); + +const bytes = __toBinary(${JSON.stringify(await readFile(path.join(__dirname, `../../pkg/nodejs/${name}_bg.wasm`), "base64")) + }); +`, + ); + +await writeFile(path.join(__dirname, `../../pkg/browser.js`), patched + "\nglobalThis['pubky'] = imports"); + +// Move outside of nodejs + +await Promise.all([".js", ".d.ts", "_bg.wasm"].map(suffix => + rename( + path.join(__dirname, `../../pkg/nodejs/${name}${suffix}`), + path.join(__dirname, `../../pkg/${suffix === '.js' ? "index.cjs" : (name + suffix)}`), + )) +) diff --git a/rust/pubky/pubky/src/error.rs b/rust/pubky/pubky/src/error.rs new file mode 100644 index 0000000..c8d80e1 --- /dev/null +++ b/rust/pubky/pubky/src/error.rs @@ -0,0 +1,56 @@ +//! Main Crate Error + +use pkarr::dns::SimpleDnsError; + +// Alias Result to be the crate Result. +pub type Result = core::result::Result; + +#[derive(thiserror::Error, Debug)] +/// Pk common Error +pub enum Error { + /// For starter, to remove as code matures. + #[error("Generic error: {0}")] + Generic(String), + + #[error("Could not resolve endpoint for {0}")] + ResolveEndpoint(String), + + #[error("Could not convert the passed type into a Url")] + InvalidUrl, + + // === Transparent === + #[error(transparent)] + Dns(#[from] SimpleDnsError), + + #[error(transparent)] + Pkarr(#[from] pkarr::Error), + + #[error(transparent)] + Url(#[from] url::ParseError), + + #[error(transparent)] + Reqwest(#[from] reqwest::Error), + + #[error(transparent)] + Session(#[from] pubky_common::session::Error), + + #[error(transparent)] + Crypto(#[from] pubky_common::crypto::Error), + + #[error(transparent)] + RecoveryFile(#[from] pubky_common::recovery_file::Error), + + #[error(transparent)] + AuthToken(#[from] pubky_common::auth::Error), +} + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsValue; + +#[cfg(target_arch = "wasm32")] +impl From for JsValue { + fn from(error: Error) -> JsValue { + let error_message = error.to_string(); + js_sys::Error::new(&error_message).into() + } +} diff --git a/rust/pubky/pubky/src/lib.rs b/rust/pubky/pubky/src/lib.rs new file mode 100644 index 0000000..2b6cf42 --- /dev/null +++ b/rust/pubky/pubky/src/lib.rs @@ -0,0 +1,36 @@ +mod error; +mod shared; + +#[cfg(not(target_arch = "wasm32"))] +mod native; + +#[cfg(target_arch = "wasm32")] +mod wasm; +#[cfg(target_arch = "wasm32")] +use std::{ + collections::HashSet, + sync::{Arc, RwLock}, +}; + +use wasm_bindgen::prelude::*; + +#[cfg(not(target_arch = "wasm32"))] +use ::pkarr::PkarrClientAsync; + +pub use error::Error; + +#[cfg(not(target_arch = "wasm32"))] +pub use crate::shared::list_builder::ListBuilder; + +#[derive(Debug, Clone)] +#[wasm_bindgen] +pub struct PubkyClient { + http: reqwest::Client, + #[cfg(not(target_arch = "wasm32"))] + pub(crate) pkarr: PkarrClientAsync, + /// A cookie jar for nodejs fetch. + #[cfg(target_arch = "wasm32")] + pub(crate) session_cookies: Arc>>, + #[cfg(target_arch = "wasm32")] + pub(crate) pkarr_relays: Vec, +} diff --git a/rust/pubky/pubky/src/native.rs b/rust/pubky/pubky/src/native.rs new file mode 100644 index 0000000..ba0f086 --- /dev/null +++ b/rust/pubky/pubky/src/native.rs @@ -0,0 +1,251 @@ +use std::net::ToSocketAddrs; +use std::time::Duration; + +use bytes::Bytes; +use pubky_common::{ + capabilities::Capabilities, + recovery_file::{create_recovery_file, decrypt_recovery_file}, + session::Session, +}; +use reqwest::{RequestBuilder, Response}; +use tokio::sync::oneshot; +use url::Url; + +use pkarr::Keypair; + +use ::pkarr::{mainline::dht::Testnet, PkarrClient, PublicKey, SignedPacket}; + +use crate::{ + error::{Error, Result}, + shared::list_builder::ListBuilder, + PubkyClient, +}; + +static DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),); + +#[derive(Debug, Default)] +pub struct PubkyClientBuilder { + pkarr_settings: pkarr::Settings, +} + +impl PubkyClientBuilder { + /// Set Pkarr client [pkarr::Settings]. + pub fn pkarr_settings(mut self, settings: pkarr::Settings) -> Self { + self.pkarr_settings = settings; + self + } + + /// Use the bootstrap nodes of a testnet, as the bootstrap nodes and + /// resolvers in the internal Pkarr client. + pub fn testnet(mut self, testnet: &Testnet) -> Self { + self.pkarr_settings.dht.bootstrap = testnet.bootstrap.to_vec().into(); + + self.pkarr_settings.resolvers = testnet + .bootstrap + .iter() + .flat_map(|resolver| resolver.to_socket_addrs()) + .flatten() + .collect::>() + .into(); + + self + } + + /// Set the request_timeout of the UDP socket in the Mainline DHT client in + /// the internal Pkarr client. + /// + /// Useful to speed unit tests. + /// Defaults to 2 seconds. + pub fn dht_request_timeout(mut self, timeout: Duration) -> Self { + self.pkarr_settings.dht.request_timeout = timeout.into(); + self + } + + /// Build [PubkyClient] + pub fn build(self) -> PubkyClient { + PubkyClient { + http: reqwest::Client::builder() + .cookie_store(true) + .user_agent(DEFAULT_USER_AGENT) + .build() + .unwrap(), + pkarr: PkarrClient::new(self.pkarr_settings).unwrap().as_async(), + } + } +} + +impl Default for PubkyClient { + fn default() -> Self { + PubkyClient::builder().build() + } +} + +// === Public API === + +impl PubkyClient { + /// Returns a builder to edit settings before creating [PubkyClient]. + pub fn builder() -> PubkyClientBuilder { + PubkyClientBuilder::default() + } + + /// Create a client connected to the local network + /// with the bootstrapping node: `localhost:6881` + pub fn testnet() -> Self { + Self::test(&Testnet { + bootstrap: vec!["localhost:6881".to_string()], + nodes: vec![], + }) + } + + /// Creates a [PubkyClient] with: + /// - DHT bootstrap nodes set to the `testnet` bootstrap nodes. + /// - DHT request timout set to 500 milliseconds. (unless in CI, then it is left as default 2000) + /// + /// For more control, you can use [PubkyClientBuilder::testnet] + pub fn test(testnet: &Testnet) -> PubkyClient { + let mut builder = PubkyClient::builder().testnet(testnet); + + if std::env::var("CI").is_err() { + builder = builder.dht_request_timeout(Duration::from_millis(500)); + } + + builder.build() + } + + // === Auth === + + /// Signup to a homeserver and update Pkarr accordingly. + /// + /// The homeserver is a Pkarr domain name, where the TLD is a Pkarr public key + /// for example "pubky.o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy" + pub async fn signup(&self, keypair: &Keypair, homeserver: &PublicKey) -> Result { + self.inner_signup(keypair, homeserver).await + } + + /// Check the current sesison for a given Pubky in its homeserver. + /// + /// Returns [Session] or `None` (if recieved `404 NOT_FOUND`), + /// or [reqwest::Error] if the response has any other `>=400` status code. + pub async fn session(&self, pubky: &PublicKey) -> Result> { + self.inner_session(pubky).await + } + + /// Signout from a homeserver. + pub async fn signout(&self, pubky: &PublicKey) -> Result<()> { + self.inner_signout(pubky).await + } + + /// Signin to a homeserver. + pub async fn signin(&self, keypair: &Keypair) -> Result { + self.inner_signin(keypair).await + } + + // === Public data === + + /// Upload a small payload to a given path. + pub async fn put>(&self, url: T, content: &[u8]) -> Result<()> { + self.inner_put(url, content).await + } + + /// Download a small payload from a given path relative to a pubky author. + pub async fn get>(&self, url: T) -> Result> { + self.inner_get(url).await + } + + /// Delete a file at a path relative to a pubky author. + pub async fn delete>(&self, url: T) -> Result<()> { + self.inner_delete(url).await + } + + /// Returns a [ListBuilder] to help pass options before calling [ListBuilder::send]. + /// + /// `url` sets the path you want to lest within. + pub fn list>(&self, url: T) -> Result { + self.inner_list(url) + } + + // === Helpers === + + /// Create a recovery file of the `keypair`, containing the secret key encrypted + /// using the `passphrase`. + pub fn create_recovery_file(keypair: &Keypair, passphrase: &str) -> Result> { + Ok(create_recovery_file(keypair, passphrase)?) + } + + /// Recover a keypair from a recovery file by decrypting the secret key using `passphrase`. + pub fn decrypt_recovery_file(recovery_file: &[u8], passphrase: &str) -> Result { + Ok(decrypt_recovery_file(recovery_file, passphrase)?) + } + + /// Return `pubkyauth://` url and wait for the incoming [AuthToken] + /// verifying that AuthToken, and if capabilities were requested, signing in to + /// the Pubky's homeserver and returning the [Session] information. + pub fn auth_request( + &self, + relay: impl TryInto, + capabilities: &Capabilities, + ) -> Result<(Url, tokio::sync::oneshot::Receiver>)> { + let mut relay: Url = relay + .try_into() + .map_err(|_| Error::Generic("Invalid relay Url".into()))?; + + let (pubkyauth_url, client_secret) = self.create_auth_request(&mut relay, capabilities)?; + + let (tx, rx) = oneshot::channel::>(); + + let this = self.clone(); + + tokio::spawn(async move { + let to_send = this + .subscribe_to_auth_response(relay, &client_secret) + .await?; + + tx.send(to_send) + .map_err(|_| Error::Generic("Failed to send the session after signing in with token, since the receiver is dropped".into()))?; + + Ok::<(), Error>(()) + }); + + Ok((pubkyauth_url, rx)) + } + + /// Sign an [pubky_common::auth::AuthToken], encrypt it and send it to the + /// source of the pubkyauth request url. + pub async fn send_auth_token>( + &self, + keypair: &Keypair, + pubkyauth_url: T, + ) -> Result<()> { + let url: Url = pubkyauth_url.try_into().map_err(|_| Error::InvalidUrl)?; + + self.inner_send_auth_token(keypair, url).await?; + + Ok(()) + } +} + +// === Internals === + +impl PubkyClient { + // === Pkarr === + + pub(crate) async fn pkarr_resolve( + &self, + public_key: &PublicKey, + ) -> Result> { + Ok(self.pkarr.resolve(public_key).await?) + } + + pub(crate) async fn pkarr_publish(&self, signed_packet: &SignedPacket) -> Result<()> { + Ok(self.pkarr.publish(signed_packet).await?) + } + + // === HTTP === + + pub(crate) fn request(&self, method: reqwest::Method, url: Url) -> RequestBuilder { + self.http.request(method, url) + } + + pub(crate) fn store_session(&self, _: &Response) {} + pub(crate) fn remove_session(&self, _: &PublicKey) {} +} diff --git a/rust/pubky/pubky/src/shared/auth.rs b/rust/pubky/pubky/src/shared/auth.rs new file mode 100644 index 0000000..88c4259 --- /dev/null +++ b/rust/pubky/pubky/src/shared/auth.rs @@ -0,0 +1,343 @@ +use std::collections::HashMap; + +use base64::{alphabet::URL_SAFE, engine::general_purpose::NO_PAD, Engine}; +use reqwest::{Method, StatusCode}; +use url::Url; + +use pkarr::{Keypair, PublicKey}; +use pubky_common::{ + auth::AuthToken, + capabilities::{Capabilities, Capability}, + crypto::{decrypt, encrypt, hash, random_bytes}, + session::Session, +}; + +use crate::{ + error::{Error, Result}, + PubkyClient, +}; + +use super::pkarr::Endpoint; + +impl PubkyClient { + /// Signup to a homeserver and update Pkarr accordingly. + /// + /// The homeserver is a Pkarr domain name, where the TLD is a Pkarr public key + /// for example "pubky.o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy" + pub(crate) async fn inner_signup( + &self, + keypair: &Keypair, + homeserver: &PublicKey, + ) -> Result { + let homeserver = homeserver.to_string(); + + let Endpoint { mut url, .. } = self.resolve_endpoint(&homeserver).await?; + + url.set_path("/signup"); + + let body = AuthToken::sign(keypair, vec![Capability::root()]).serialize(); + + let response = self + .request(Method::POST, url.clone()) + .body(body) + .send() + .await?; + + self.store_session(&response); + + self.publish_pubky_homeserver(keypair, &homeserver).await?; + + let bytes = response.bytes().await?; + + Ok(Session::deserialize(&bytes)?) + } + + /// Check the current sesison for a given Pubky in its homeserver. + /// + /// Returns None if not signed in, or [reqwest::Error] + /// if the response has any other `>=404` status code. + pub(crate) async fn inner_session(&self, pubky: &PublicKey) -> Result> { + let Endpoint { mut url, .. } = self.resolve_pubky_homeserver(pubky).await?; + + url.set_path(&format!("/{}/session", pubky)); + + let res = self.request(Method::GET, url).send().await?; + + if res.status() == StatusCode::NOT_FOUND { + return Ok(None); + } + + if !res.status().is_success() { + res.error_for_status_ref()?; + }; + + let bytes = res.bytes().await?; + + Ok(Some(Session::deserialize(&bytes)?)) + } + + /// Signout from a homeserver. + pub(crate) async fn inner_signout(&self, pubky: &PublicKey) -> Result<()> { + let Endpoint { mut url, .. } = self.resolve_pubky_homeserver(pubky).await?; + + url.set_path(&format!("/{}/session", pubky)); + + self.request(Method::DELETE, url).send().await?; + + self.remove_session(pubky); + + Ok(()) + } + + /// Signin to a homeserver. + pub(crate) async fn inner_signin(&self, keypair: &Keypair) -> Result { + let token = AuthToken::sign(keypair, vec![Capability::root()]); + + self.signin_with_authtoken(&token).await + } + + pub(crate) async fn inner_send_auth_token( + &self, + keypair: &Keypair, + pubkyauth_url: Url, + ) -> Result<()> { + let query_params: HashMap = + pubkyauth_url.query_pairs().into_owned().collect(); + + let relay = query_params + .get("relay") + .map(|r| url::Url::parse(r).expect("Relay query param to be valid URL")) + .expect("Missing relay query param"); + + let client_secret = query_params + .get("secret") + .map(|s| { + let engine = base64::engine::GeneralPurpose::new(&URL_SAFE, NO_PAD); + let bytes = engine.decode(s).expect("invalid client_secret"); + let arr: [u8; 32] = bytes.try_into().expect("invalid client_secret"); + + arr + }) + .expect("Missing client secret"); + + let capabilities = query_params + .get("caps") + .map(|caps_string| { + caps_string + .split(',') + .filter_map(|cap| Capability::try_from(cap).ok()) + .collect::>() + }) + .unwrap_or_default(); + + let token = AuthToken::sign(keypair, capabilities); + + let encrypted_token = encrypt(&token.serialize(), &client_secret)?; + + let engine = base64::engine::GeneralPurpose::new(&URL_SAFE, NO_PAD); + + let mut callback = relay.clone(); + let mut path_segments = callback.path_segments_mut().unwrap(); + path_segments.pop_if_empty(); + let channel_id = engine.encode(hash(&client_secret).as_bytes()); + path_segments.push(&channel_id); + drop(path_segments); + + self.request(Method::POST, callback) + .body(encrypted_token) + .send() + .await?; + + Ok(()) + } + + pub async fn inner_third_party_signin( + &self, + encrypted_token: &[u8], + client_secret: &[u8; 32], + ) -> Result { + let decrypted = decrypt(encrypted_token, client_secret)?; + let token = AuthToken::deserialize(&decrypted)?; + + self.signin_with_authtoken(&token).await?; + + Ok(token.pubky().to_owned()) + } + + pub async fn signin_with_authtoken(&self, token: &AuthToken) -> Result { + let mut url = Url::parse(&format!("https://{}/session", token.pubky()))?; + + self.resolve_url(&mut url).await?; + + let response = self + .request(Method::POST, url) + .body(token.serialize()) + .send() + .await?; + + self.store_session(&response); + + let bytes = response.bytes().await?; + + Ok(Session::deserialize(&bytes)?) + } + + pub(crate) fn create_auth_request( + &self, + relay: &mut Url, + capabilities: &Capabilities, + ) -> Result<(Url, [u8; 32])> { + let engine = base64::engine::GeneralPurpose::new(&URL_SAFE, NO_PAD); + + let client_secret: [u8; 32] = random_bytes::<32>(); + + let pubkyauth_url = Url::parse(&format!( + "pubkyauth:///?caps={capabilities}&secret={}&relay={relay}", + engine.encode(client_secret) + ))?; + + let mut segments = relay + .path_segments_mut() + .map_err(|_| Error::Generic("Invalid relay".into()))?; + // remove trailing slash if any. + segments.pop_if_empty(); + let channel_id = &engine.encode(hash(&client_secret).as_bytes()); + segments.push(channel_id); + drop(segments); + + Ok((pubkyauth_url, client_secret)) + } + + pub(crate) async fn subscribe_to_auth_response( + &self, + relay: Url, + client_secret: &[u8; 32], + ) -> Result> { + let response = self.http.request(Method::GET, relay).send().await?; + let encrypted_token = response.bytes().await?; + let token_bytes = decrypt(&encrypted_token, client_secret)?; + let token = AuthToken::verify(&token_bytes)?; + + if token.capabilities().is_empty() { + Ok(None) + } else { + let session = self.signin_with_authtoken(&token).await?; + Ok(Some(session)) + } + } +} + +#[cfg(test)] +mod tests { + + use crate::*; + + use pkarr::{mainline::Testnet, Keypair}; + use pubky_common::capabilities::{Capabilities, Capability}; + use pubky_homeserver::Homeserver; + use reqwest::StatusCode; + + #[tokio::test] + async fn basic_authn() { + let testnet = Testnet::new(10); + let server = Homeserver::start_test(&testnet).await.unwrap(); + + let client = PubkyClient::test(&testnet); + + let keypair = Keypair::random(); + + client.signup(&keypair, &server.public_key()).await.unwrap(); + + let session = client + .session(&keypair.public_key()) + .await + .unwrap() + .unwrap(); + + assert!(session.capabilities().contains(&Capability::root())); + + client.signout(&keypair.public_key()).await.unwrap(); + + { + let session = client.session(&keypair.public_key()).await.unwrap(); + + assert!(session.is_none()); + } + + client.signin(&keypair).await.unwrap(); + + { + let session = client + .session(&keypair.public_key()) + .await + .unwrap() + .unwrap(); + + assert_eq!(session.pubky(), &keypair.public_key()); + assert!(session.capabilities().contains(&Capability::root())); + } + } + + #[tokio::test] + async fn authz() { + let testnet = Testnet::new(10); + let server = Homeserver::start_test(&testnet).await.unwrap(); + + let keypair = Keypair::random(); + let pubky = keypair.public_key(); + + // Third party app side + let capabilities: Capabilities = + "/pub/pubky.app/:rw,/pub/foo.bar/file:r".try_into().unwrap(); + let client = PubkyClient::test(&testnet); + let (pubkyauth_url, pubkyauth_response) = client + .auth_request("https://demo.httprelay.io/link", &capabilities) + .unwrap(); + + // Authenticator side + { + let client = PubkyClient::test(&testnet); + + client.signup(&keypair, &server.public_key()).await.unwrap(); + + client + .send_auth_token(&keypair, pubkyauth_url) + .await + .unwrap(); + } + + let session = pubkyauth_response.await.unwrap().unwrap(); + + assert_eq!(session.pubky(), &pubky); + assert_eq!(session.capabilities(), &capabilities.0); + + // Test access control enforcement + + client + .put(format!("pubky://{pubky}/pub/pubky.app/foo").as_str(), &[]) + .await + .unwrap(); + + assert_eq!( + client + .put(format!("pubky://{pubky}/pub/pubky.app").as_str(), &[]) + .await + .map_err(|e| match e { + crate::Error::Reqwest(e) => e.status(), + _ => None, + }), + Err(Some(StatusCode::FORBIDDEN)) + ); + + assert_eq!( + client + .put(format!("pubky://{pubky}/pub/foo.bar/file").as_str(), &[]) + .await + .map_err(|e| match e { + crate::Error::Reqwest(e) => e.status(), + _ => None, + }), + Err(Some(StatusCode::FORBIDDEN)) + ); + } +} diff --git a/rust/pubky/pubky/src/shared/list_builder.rs b/rust/pubky/pubky/src/shared/list_builder.rs new file mode 100644 index 0000000..0eaec77 --- /dev/null +++ b/rust/pubky/pubky/src/shared/list_builder.rs @@ -0,0 +1,105 @@ +use reqwest::Method; +use url::Url; + +use crate::{error::Result, PubkyClient}; + +#[derive(Debug)] +pub struct ListBuilder<'a> { + url: Url, + reverse: bool, + limit: Option, + cursor: Option<&'a str>, + client: &'a PubkyClient, + shallow: bool, +} + +impl<'a> ListBuilder<'a> { + /// Create a new List request builder + pub(crate) fn new(client: &'a PubkyClient, url: Url) -> Self { + Self { + client, + url, + limit: None, + cursor: None, + reverse: false, + shallow: false, + } + } + + /// Set the `reverse` option. + pub fn reverse(mut self, reverse: bool) -> Self { + self.reverse = reverse; + self + } + + /// Set the `limit` value. + pub fn limit(mut self, limit: u16) -> Self { + self.limit = limit.into(); + self + } + + /// Set the `cursor` value. + /// + /// Either a full `pubky://` Url (from previous list response), + /// or a path (to a file or directory) relative to the `url` + pub fn cursor(mut self, cursor: &'a str) -> Self { + self.cursor = cursor.into(); + self + } + + pub fn shallow(mut self, shallow: bool) -> Self { + self.shallow = shallow; + self + } + + /// Send the list request. + /// + /// Returns a list of Pubky URLs of the files in the path of the `url` + /// respecting [ListBuilder::reverse], [ListBuilder::limit] and [ListBuilder::cursor] + /// options. + pub async fn send(self) -> Result> { + let mut url = self.client.pubky_to_http(self.url).await?; + + if !url.path().ends_with('/') { + let path = url.path().to_string(); + let mut parts = path.split('/').collect::>(); + parts.pop(); + + let path = format!("{}/", parts.join("/")); + + url.set_path(&path) + } + + let mut query = url.query_pairs_mut(); + + if self.reverse { + query.append_key_only("reverse"); + } + + if self.shallow { + query.append_key_only("shallow"); + } + + if let Some(limit) = self.limit { + query.append_pair("limit", &limit.to_string()); + } + + if let Some(cursor) = self.cursor { + query.append_pair("cursor", cursor); + } + + drop(query); + + let response = self.client.request(Method::GET, url).send().await?; + + response.error_for_status_ref()?; + + // TODO: bail on too large files. + let bytes = response.bytes().await?; + + Ok(String::from_utf8_lossy(&bytes) + .lines() + .map(String::from) + .collect()) + } +} diff --git a/rust/pubky/pubky/src/shared/mod.rs b/rust/pubky/pubky/src/shared/mod.rs new file mode 100644 index 0000000..67b456f --- /dev/null +++ b/rust/pubky/pubky/src/shared/mod.rs @@ -0,0 +1,4 @@ +pub mod auth; +pub mod list_builder; +pub mod pkarr; +pub mod public; diff --git a/rust/pubky/pubky/src/shared/pkarr.rs b/rust/pubky/pubky/src/shared/pkarr.rs new file mode 100644 index 0000000..d01eded --- /dev/null +++ b/rust/pubky/pubky/src/shared/pkarr.rs @@ -0,0 +1,339 @@ +use url::Url; + +use pkarr::{ + dns::{rdata::SVCB, Packet}, + Keypair, PublicKey, SignedPacket, +}; + +use crate::{ + error::{Error, Result}, + PubkyClient, +}; + +const MAX_ENDPOINT_RESOLUTION_RECURSION: u8 = 3; + +impl PubkyClient { + /// Publish the SVCB record for `_pubky.`. + pub(crate) async fn publish_pubky_homeserver( + &self, + keypair: &Keypair, + host: &str, + ) -> Result<()> { + let existing = self.pkarr_resolve(&keypair.public_key()).await?; + + let mut packet = Packet::new_reply(0); + + if let Some(existing) = existing { + for answer in existing.packet().answers.iter().cloned() { + if !answer.name.to_string().starts_with("_pubky") { + packet.answers.push(answer.into_owned()) + } + } + } + + let svcb = SVCB::new(0, host.try_into()?); + + packet.answers.push(pkarr::dns::ResourceRecord::new( + "_pubky".try_into().unwrap(), + pkarr::dns::CLASS::IN, + 60 * 60, + pkarr::dns::rdata::RData::SVCB(svcb), + )); + + let signed_packet = SignedPacket::from_packet(keypair, &packet)?; + + self.pkarr_publish(&signed_packet).await?; + + Ok(()) + } + + /// Resolve the homeserver for a pubky. + pub(crate) async fn resolve_pubky_homeserver(&self, pubky: &PublicKey) -> Result { + let target = format!("_pubky.{pubky}"); + + self.resolve_endpoint(&target) + .await + .map_err(|_| Error::Generic("Could not resolve homeserver".to_string())) + } + + /// Resolve a service's public_key and "non-pkarr url" from a Pubky domain + /// + /// "non-pkarr" url is any URL where the hostname isn't a 52 z-base32 character, + /// usually an IPv4, IPv6 or ICANN domain, but could also be any other unknown hostname. + /// + /// Recursively resolve SVCB and HTTPS endpoints, with [MAX_ENDPOINT_RESOLUTION_RECURSION] limit. + pub(crate) async fn resolve_endpoint(&self, target: &str) -> Result { + let original_target = target; + // TODO: cache the result of this function? + + let mut target = target.to_string(); + + let mut endpoint_public_key = None; + let mut origin = target.clone(); + + let mut step = 0; + + // PublicKey is very good at extracting the Pkarr TLD from a string. + while let Ok(public_key) = PublicKey::try_from(target.clone()) { + if step >= MAX_ENDPOINT_RESOLUTION_RECURSION { + break; + }; + step += 1; + + if let Some(signed_packet) = self + .pkarr_resolve(&public_key) + .await + .map_err(|_| Error::ResolveEndpoint(original_target.into()))? + { + // Choose most prior SVCB record + let svcb = signed_packet.resource_records(&target).fold( + None, + |prev: Option, answer| { + if let Some(svcb) = match &answer.rdata { + pkarr::dns::rdata::RData::SVCB(svcb) => Some(svcb), + pkarr::dns::rdata::RData::HTTPS(curr) => Some(&curr.0), + _ => None, + } { + let curr = svcb.clone(); + + if curr.priority == 0 { + return Some(curr); + } + if let Some(prev) = &prev { + // TODO return random if priority is the same + if curr.priority >= prev.priority { + return Some(curr); + } + } else { + return Some(curr); + } + } + + prev + }, + ); + + if let Some(svcb) = svcb { + endpoint_public_key = Some(public_key.clone()); + target = svcb.target.to_string(); + + if let Some(port) = svcb.get_param(pkarr::dns::rdata::SVCB::PORT) { + if port.len() < 2 { + // TODO: debug! Error encoding port! + } + let port = u16::from_be_bytes([port[0], port[1]]); + + origin = format!("{target}:{port}"); + } else { + origin.clone_from(&target); + }; + + if step >= MAX_ENDPOINT_RESOLUTION_RECURSION { + continue; + }; + } + } else { + break; + } + } + + if PublicKey::try_from(origin.as_str()).is_ok() { + return Err(Error::ResolveEndpoint(original_target.into())); + } + + if let Some(public_key) = endpoint_public_key { + let url = Url::parse(&format!( + "{}://{}", + if origin.starts_with("localhost") { + "http" + } else { + "https" + }, + origin + ))?; + + return Ok(Endpoint { public_key, url }); + } + + Err(Error::ResolveEndpoint(original_target.into())) + } + + pub(crate) async fn resolve_url(&self, url: &mut Url) -> Result<()> { + if let Some(Ok(pubky)) = url.host_str().map(PublicKey::try_from) { + let Endpoint { url: x, .. } = self.resolve_endpoint(&format!("_pubky.{pubky}")).await?; + + url.set_host(x.host_str())?; + url.set_port(x.port()).expect("should work!"); + url.set_scheme(x.scheme()).expect("should work!"); + }; + + Ok(()) + } +} + +#[derive(Debug)] +pub(crate) struct Endpoint { + // TODO: we don't use this at all? + pub public_key: PublicKey, + pub url: Url, +} + +#[cfg(test)] +mod tests { + use super::*; + + use pkarr::{ + dns::{ + rdata::{HTTPS, SVCB}, + Packet, + }, + mainline::{dht::DhtSettings, Testnet}, + Keypair, PkarrClient, Settings, SignedPacket, + }; + use pubky_homeserver::Homeserver; + + #[tokio::test] + async fn resolve_endpoint_https() { + let testnet = Testnet::new(10); + + let pkarr_client = PkarrClient::new(Settings { + dht: DhtSettings { + bootstrap: Some(testnet.bootstrap.clone()), + ..Default::default() + }, + ..Default::default() + }) + .unwrap() + .as_async(); + + let domain = "example.com"; + let mut target; + + // Server + { + let keypair = Keypair::random(); + + let https = HTTPS(SVCB::new(0, domain.try_into().unwrap())); + + let mut packet = Packet::new_reply(0); + + packet.answers.push(pkarr::dns::ResourceRecord::new( + "foo".try_into().unwrap(), + pkarr::dns::CLASS::IN, + 60 * 60, + pkarr::dns::rdata::RData::HTTPS(https), + )); + + let signed_packet = SignedPacket::from_packet(&keypair, &packet).unwrap(); + + pkarr_client.publish(&signed_packet).await.unwrap(); + + target = format!("foo.{}", keypair.public_key()); + } + + // intermediate + { + let keypair = Keypair::random(); + + let svcb = SVCB::new(0, target.as_str().try_into().unwrap()); + + let mut packet = Packet::new_reply(0); + + packet.answers.push(pkarr::dns::ResourceRecord::new( + "bar".try_into().unwrap(), + pkarr::dns::CLASS::IN, + 60 * 60, + pkarr::dns::rdata::RData::SVCB(svcb), + )); + + let signed_packet = SignedPacket::from_packet(&keypair, &packet).unwrap(); + + pkarr_client.publish(&signed_packet).await.unwrap(); + + target = format!("bar.{}", keypair.public_key()) + } + + { + let keypair = Keypair::random(); + + let svcb = SVCB::new(0, target.as_str().try_into().unwrap()); + + let mut packet = Packet::new_reply(0); + + packet.answers.push(pkarr::dns::ResourceRecord::new( + "pubky".try_into().unwrap(), + pkarr::dns::CLASS::IN, + 60 * 60, + pkarr::dns::rdata::RData::SVCB(svcb), + )); + + let signed_packet = SignedPacket::from_packet(&keypair, &packet).unwrap(); + + pkarr_client.publish(&signed_packet).await.unwrap(); + + target = format!("pubky.{}", keypair.public_key()) + } + + let client = PubkyClient::test(&testnet); + + let endpoint = client.resolve_endpoint(&target).await.unwrap(); + + assert_eq!(endpoint.url.host_str().unwrap(), domain); + } + + #[tokio::test] + async fn resolve_homeserver() { + let testnet = Testnet::new(10); + let server = Homeserver::start_test(&testnet).await.unwrap(); + + // Publish an intermediate controller of the homeserver + let pkarr_client = PkarrClient::new(Settings { + dht: DhtSettings { + bootstrap: Some(testnet.bootstrap.clone()), + ..Default::default() + }, + ..Default::default() + }) + .unwrap() + .as_async(); + + let intermediate = Keypair::random(); + + let mut packet = Packet::new_reply(0); + + let server_tld = server.public_key().to_string(); + + let svcb = SVCB::new(0, server_tld.as_str().try_into().unwrap()); + + packet.answers.push(pkarr::dns::ResourceRecord::new( + "pubky".try_into().unwrap(), + pkarr::dns::CLASS::IN, + 60 * 60, + pkarr::dns::rdata::RData::SVCB(svcb), + )); + + let signed_packet = SignedPacket::from_packet(&intermediate, &packet).unwrap(); + + pkarr_client.publish(&signed_packet).await.unwrap(); + + { + let client = PubkyClient::test(&testnet); + + let pubky = Keypair::random(); + + client + .publish_pubky_homeserver(&pubky, &format!("pubky.{}", &intermediate.public_key())) + .await + .unwrap(); + + let Endpoint { public_key, url } = client + .resolve_pubky_homeserver(&pubky.public_key()) + .await + .unwrap(); + + assert_eq!(public_key, server.public_key()); + assert_eq!(url.host_str(), Some("localhost")); + assert_eq!(url.port(), Some(server.port())); + } + } +} diff --git a/rust/pubky/pubky/src/shared/public.rs b/rust/pubky/pubky/src/shared/public.rs new file mode 100644 index 0000000..febd1fe --- /dev/null +++ b/rust/pubky/pubky/src/shared/public.rs @@ -0,0 +1,768 @@ +use bytes::Bytes; + +use pkarr::PublicKey; +use reqwest::{Method, StatusCode}; +use url::Url; + +use crate::{ + error::{Error, Result}, + PubkyClient, +}; + +use super::{list_builder::ListBuilder, pkarr::Endpoint}; + +impl PubkyClient { + pub(crate) async fn inner_put>(&self, url: T, content: &[u8]) -> Result<()> { + let url = self.pubky_to_http(url).await?; + + let response = self + .request(Method::PUT, url) + .body(content.to_owned()) + .send() + .await?; + + response.error_for_status()?; + + Ok(()) + } + + pub(crate) async fn inner_get>(&self, url: T) -> Result> { + let url = self.pubky_to_http(url).await?; + + let response = self.request(Method::GET, url).send().await?; + + if response.status() == StatusCode::NOT_FOUND { + return Ok(None); + } + + response.error_for_status_ref()?; + + // TODO: bail on too large files. + let bytes = response.bytes().await?; + + Ok(Some(bytes)) + } + + pub(crate) async fn inner_delete>(&self, url: T) -> Result<()> { + let url = self.pubky_to_http(url).await?; + + let response = self.request(Method::DELETE, url).send().await?; + + response.error_for_status_ref()?; + + Ok(()) + } + + pub(crate) fn inner_list>(&self, url: T) -> Result { + Ok(ListBuilder::new( + self, + url.try_into().map_err(|_| Error::InvalidUrl)?, + )) + } + + pub(crate) async fn pubky_to_http>(&self, url: T) -> Result { + let original_url: Url = url.try_into().map_err(|_| Error::InvalidUrl)?; + + let pubky = original_url + .host_str() + .ok_or(Error::Generic("Missing Pubky Url host".to_string()))?; + + if let Ok(public_key) = PublicKey::try_from(pubky) { + let Endpoint { mut url, .. } = self.resolve_pubky_homeserver(&public_key).await?; + + // TODO: remove if we move to subdomains instead of paths. + if original_url.scheme() == "pubky" { + let path = original_url.path_segments(); + + let mut split = url.path_segments_mut().unwrap(); + split.push(pubky); + if let Some(segments) = path { + for segment in segments { + split.push(segment); + } + } + drop(split); + } + + return Ok(url); + } + + Ok(original_url) + } +} + +#[cfg(test)] +mod tests { + + use core::panic; + + use crate::*; + + use pkarr::{mainline::Testnet, Keypair}; + use pubky_homeserver::Homeserver; + use reqwest::{Method, StatusCode}; + + #[tokio::test] + async fn put_get_delete() { + let testnet = Testnet::new(10); + let server = Homeserver::start_test(&testnet).await.unwrap(); + + let client = PubkyClient::test(&testnet); + + let keypair = Keypair::random(); + + client.signup(&keypair, &server.public_key()).await.unwrap(); + + let url = format!("pubky://{}/pub/foo.txt", keypair.public_key()); + let url = url.as_str(); + + client.put(url, &[0, 1, 2, 3, 4]).await.unwrap(); + + let response = client.get(url).await.unwrap().unwrap(); + + assert_eq!(response, bytes::Bytes::from(vec![0, 1, 2, 3, 4])); + + client.delete(url).await.unwrap(); + + let response = client.get(url).await.unwrap(); + + assert_eq!(response, None); + } + + #[tokio::test] + async fn unauthorized_put_delete() { + let testnet = Testnet::new(10); + let server = Homeserver::start_test(&testnet).await.unwrap(); + + let client = PubkyClient::test(&testnet); + + let keypair = Keypair::random(); + + client.signup(&keypair, &server.public_key()).await.unwrap(); + + let public_key = keypair.public_key(); + + let url = format!("pubky://{public_key}/pub/foo.txt"); + let url = url.as_str(); + + let other_client = PubkyClient::test(&testnet); + { + let other = Keypair::random(); + + // TODO: remove extra client after switching to subdomains. + other_client + .signup(&other, &server.public_key()) + .await + .unwrap(); + + let response = other_client.put(url, &[0, 1, 2, 3, 4]).await; + + match response { + Err(Error::Reqwest(error)) => { + assert!(error.status() == Some(StatusCode::UNAUTHORIZED)) + } + _ => { + panic!("expected error StatusCode::UNAUTHORIZED") + } + } + } + + client.put(url, &[0, 1, 2, 3, 4]).await.unwrap(); + + { + let other = Keypair::random(); + + // TODO: remove extra client after switching to subdomains. + other_client + .signup(&other, &server.public_key()) + .await + .unwrap(); + + let response = other_client.delete(url).await; + + match response { + Err(Error::Reqwest(error)) => { + assert!(error.status() == Some(StatusCode::UNAUTHORIZED)) + } + _ => { + panic!("expected error StatusCode::UNAUTHORIZED") + } + } + } + + let response = client.get(url).await.unwrap().unwrap(); + + assert_eq!(response, bytes::Bytes::from(vec![0, 1, 2, 3, 4])); + } + + #[tokio::test] + async fn list() { + let testnet = Testnet::new(10); + let server = Homeserver::start_test(&testnet).await.unwrap(); + + let client = PubkyClient::test(&testnet); + + let keypair = Keypair::random(); + + client.signup(&keypair, &server.public_key()).await.unwrap(); + + let pubky = keypair.public_key(); + + let urls = vec![ + format!("pubky://{pubky}/pub/a.wrong/a.txt"), + format!("pubky://{pubky}/pub/example.com/a.txt"), + format!("pubky://{pubky}/pub/example.com/b.txt"), + format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"), + format!("pubky://{pubky}/pub/example.wrong/a.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), + format!("pubky://{pubky}/pub/example.com/d.txt"), + format!("pubky://{pubky}/pub/z.wrong/a.txt"), + ]; + + for url in urls { + client.put(url.as_str(), &[0]).await.unwrap(); + } + + let url = format!("pubky://{pubky}/pub/example.com/extra"); + let url = url.as_str(); + + { + let list = client.list(url).unwrap().send().await.unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{pubky}/pub/example.com/a.txt"), + format!("pubky://{pubky}/pub/example.com/b.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), + format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"), + format!("pubky://{pubky}/pub/example.com/d.txt"), + ], + "normal list with no limit or cursor" + ); + } + + { + let list = client.list(url).unwrap().limit(2).send().await.unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{pubky}/pub/example.com/a.txt"), + format!("pubky://{pubky}/pub/example.com/b.txt"), + ], + "normal list with limit but no cursor" + ); + } + + { + let list = client + .list(url) + .unwrap() + .limit(2) + .cursor("a.txt") + .send() + .await + .unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{pubky}/pub/example.com/b.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), + ], + "normal list with limit and a file cursor" + ); + } + + { + let list = client + .list(url) + .unwrap() + .limit(2) + .cursor("cc-nested/") + .send() + .await + .unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"), + format!("pubky://{pubky}/pub/example.com/d.txt"), + ], + "normal list with limit and a directory cursor" + ); + } + + { + let list = client + .list(url) + .unwrap() + .limit(2) + .cursor(&format!("pubky://{pubky}/pub/example.com/a.txt")) + .send() + .await + .unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{pubky}/pub/example.com/b.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), + ], + "normal list with limit and a full url cursor" + ); + } + + { + let list = client + .list(url) + .unwrap() + .limit(2) + .cursor("/a.txt") + .send() + .await + .unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{pubky}/pub/example.com/b.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), + ], + "normal list with limit and a leading / cursor" + ); + } + + { + let list = client + .list(url) + .unwrap() + .reverse(true) + .send() + .await + .unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{pubky}/pub/example.com/d.txt"), + format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), + format!("pubky://{pubky}/pub/example.com/b.txt"), + format!("pubky://{pubky}/pub/example.com/a.txt"), + ], + "reverse list with no limit or cursor" + ); + } + + { + let list = client + .list(url) + .unwrap() + .reverse(true) + .limit(2) + .send() + .await + .unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{pubky}/pub/example.com/d.txt"), + format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"), + ], + "reverse list with limit but no cursor" + ); + } + + { + let list = client + .list(url) + .unwrap() + .reverse(true) + .limit(2) + .cursor("d.txt") + .send() + .await + .unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), + ], + "reverse list with limit and cursor" + ); + } + } + + #[tokio::test] + async fn list_shallow() { + let testnet = Testnet::new(10); + let server = Homeserver::start_test(&testnet).await.unwrap(); + + let client = PubkyClient::test(&testnet); + + let keypair = Keypair::random(); + + client.signup(&keypair, &server.public_key()).await.unwrap(); + + let pubky = keypair.public_key(); + + let urls = vec![ + format!("pubky://{pubky}/pub/a.com/a.txt"), + format!("pubky://{pubky}/pub/example.com/a.txt"), + format!("pubky://{pubky}/pub/example.com/b.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), + format!("pubky://{pubky}/pub/example.com/d.txt"), + format!("pubky://{pubky}/pub/example.con/d.txt"), + format!("pubky://{pubky}/pub/example.con"), + format!("pubky://{pubky}/pub/file"), + format!("pubky://{pubky}/pub/file2"), + format!("pubky://{pubky}/pub/z.com/a.txt"), + ]; + + for url in urls { + client.put(url.as_str(), &[0]).await.unwrap(); + } + + let url = format!("pubky://{pubky}/pub/"); + let url = url.as_str(); + + { + let list = client + .list(url) + .unwrap() + .shallow(true) + .send() + .await + .unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{pubky}/pub/a.com/"), + format!("pubky://{pubky}/pub/example.com/"), + format!("pubky://{pubky}/pub/example.con"), + format!("pubky://{pubky}/pub/example.con/"), + format!("pubky://{pubky}/pub/file"), + format!("pubky://{pubky}/pub/file2"), + format!("pubky://{pubky}/pub/z.com/"), + ], + "normal list shallow" + ); + } + + { + let list = client + .list(url) + .unwrap() + .shallow(true) + .limit(2) + .send() + .await + .unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{pubky}/pub/a.com/"), + format!("pubky://{pubky}/pub/example.com/"), + ], + "normal list shallow with limit but no cursor" + ); + } + + { + let list = client + .list(url) + .unwrap() + .shallow(true) + .limit(2) + .cursor("example.com/a.txt") + .send() + .await + .unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{pubky}/pub/example.com/"), + format!("pubky://{pubky}/pub/example.con"), + ], + "normal list shallow with limit and a file cursor" + ); + } + + { + let list = client + .list(url) + .unwrap() + .shallow(true) + .limit(3) + .cursor("example.com/") + .send() + .await + .unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{pubky}/pub/example.con"), + format!("pubky://{pubky}/pub/example.con/"), + format!("pubky://{pubky}/pub/file"), + ], + "normal list shallow with limit and a directory cursor" + ); + } + + { + let list = client + .list(url) + .unwrap() + .reverse(true) + .shallow(true) + .send() + .await + .unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{pubky}/pub/z.com/"), + format!("pubky://{pubky}/pub/file2"), + format!("pubky://{pubky}/pub/file"), + format!("pubky://{pubky}/pub/example.con/"), + format!("pubky://{pubky}/pub/example.con"), + format!("pubky://{pubky}/pub/example.com/"), + format!("pubky://{pubky}/pub/a.com/"), + ], + "reverse list shallow" + ); + } + + { + let list = client + .list(url) + .unwrap() + .reverse(true) + .shallow(true) + .limit(2) + .send() + .await + .unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{pubky}/pub/z.com/"), + format!("pubky://{pubky}/pub/file2"), + ], + "reverse list shallow with limit but no cursor" + ); + } + + { + let list = client + .list(url) + .unwrap() + .shallow(true) + .reverse(true) + .limit(2) + .cursor("file2") + .send() + .await + .unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{pubky}/pub/file"), + format!("pubky://{pubky}/pub/example.con/"), + ], + "reverse list shallow with limit and a file cursor" + ); + } + + { + let list = client + .list(url) + .unwrap() + .shallow(true) + .reverse(true) + .limit(2) + .cursor("example.con/") + .send() + .await + .unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{pubky}/pub/example.con"), + format!("pubky://{pubky}/pub/example.com/"), + ], + "reverse list shallow with limit and a directory cursor" + ); + } + } + + #[tokio::test] + async fn list_events() { + let testnet = Testnet::new(10); + let server = Homeserver::start_test(&testnet).await.unwrap(); + + let client = PubkyClient::test(&testnet); + + let keypair = Keypair::random(); + + client.signup(&keypair, &server.public_key()).await.unwrap(); + + let pubky = keypair.public_key(); + + let urls = vec![ + format!("pubky://{pubky}/pub/a.com/a.txt"), + format!("pubky://{pubky}/pub/example.com/a.txt"), + format!("pubky://{pubky}/pub/example.com/b.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), + format!("pubky://{pubky}/pub/example.com/d.txt"), + format!("pubky://{pubky}/pub/example.con/d.txt"), + format!("pubky://{pubky}/pub/example.con"), + format!("pubky://{pubky}/pub/file"), + format!("pubky://{pubky}/pub/file2"), + format!("pubky://{pubky}/pub/z.com/a.txt"), + ]; + + for url in urls { + client.put(url.as_str(), &[0]).await.unwrap(); + client.delete(url.as_str()).await.unwrap(); + } + + let feed_url = format!("http://localhost:{}/events/", server.port()); + let feed_url = feed_url.as_str(); + + let client = PubkyClient::test(&testnet); + + let cursor; + + { + let response = client + .request( + Method::GET, + format!("{feed_url}?limit=10").as_str().try_into().unwrap(), + ) + .send() + .await + .unwrap(); + + let text = response.text().await.unwrap(); + let lines = text.split('\n').collect::>(); + + cursor = lines.last().unwrap().split(" ").last().unwrap().to_string(); + + assert_eq!( + lines, + vec![ + format!("PUT pubky://{pubky}/pub/a.com/a.txt"), + format!("DEL pubky://{pubky}/pub/a.com/a.txt"), + format!("PUT pubky://{pubky}/pub/example.com/a.txt"), + format!("DEL pubky://{pubky}/pub/example.com/a.txt"), + format!("PUT pubky://{pubky}/pub/example.com/b.txt"), + format!("DEL pubky://{pubky}/pub/example.com/b.txt"), + format!("PUT pubky://{pubky}/pub/example.com/c.txt"), + format!("DEL pubky://{pubky}/pub/example.com/c.txt"), + format!("PUT pubky://{pubky}/pub/example.com/d.txt"), + format!("DEL pubky://{pubky}/pub/example.com/d.txt"), + format!("cursor: {cursor}",) + ] + ); + } + + { + let response = client + .request( + Method::GET, + format!("{feed_url}?limit=10&cursor={cursor}") + .as_str() + .try_into() + .unwrap(), + ) + .send() + .await + .unwrap(); + + let text = response.text().await.unwrap(); + let lines = text.split('\n').collect::>(); + + assert_eq!( + lines, + vec![ + format!("PUT pubky://{pubky}/pub/example.con/d.txt"), + format!("DEL pubky://{pubky}/pub/example.con/d.txt"), + format!("PUT pubky://{pubky}/pub/example.con"), + format!("DEL pubky://{pubky}/pub/example.con"), + format!("PUT pubky://{pubky}/pub/file"), + format!("DEL pubky://{pubky}/pub/file"), + format!("PUT pubky://{pubky}/pub/file2"), + format!("DEL pubky://{pubky}/pub/file2"), + format!("PUT pubky://{pubky}/pub/z.com/a.txt"), + format!("DEL pubky://{pubky}/pub/z.com/a.txt"), + lines.last().unwrap().to_string() + ] + ) + } + } + + #[tokio::test] + async fn read_after_event() { + let testnet = Testnet::new(10); + let server = Homeserver::start_test(&testnet).await.unwrap(); + + let client = PubkyClient::test(&testnet); + + let keypair = Keypair::random(); + + client.signup(&keypair, &server.public_key()).await.unwrap(); + + let pubky = keypair.public_key(); + + let url = format!("pubky://{pubky}/pub/a.com/a.txt"); + + client.put(url.as_str(), &[0]).await.unwrap(); + + let feed_url = format!("http://localhost:{}/events/", server.port()); + let feed_url = feed_url.as_str(); + + let client = PubkyClient::test(&testnet); + + { + let response = client + .request( + Method::GET, + format!("{feed_url}?limit=10").as_str().try_into().unwrap(), + ) + .send() + .await + .unwrap(); + + let text = response.text().await.unwrap(); + let lines = text.split('\n').collect::>(); + + let cursor = lines.last().unwrap().split(" ").last().unwrap().to_string(); + + assert_eq!( + lines, + vec![ + format!("PUT pubky://{pubky}/pub/a.com/a.txt"), + format!("cursor: {cursor}",) + ] + ); + } + + let get = client.get(url.as_str()).await.unwrap(); + dbg!(get); + } +} diff --git a/rust/pubky/pubky/src/wasm.rs b/rust/pubky/pubky/src/wasm.rs new file mode 100644 index 0000000..09dc045 --- /dev/null +++ b/rust/pubky/pubky/src/wasm.rs @@ -0,0 +1,255 @@ +use std::{ + collections::HashSet, + sync::{Arc, RwLock}, +}; + +use js_sys::{Array, Uint8Array}; +use wasm_bindgen::prelude::*; + +use url::Url; + +use pubky_common::capabilities::Capabilities; + +use crate::error::Error; +use crate::PubkyClient; + +mod http; +mod keys; +mod pkarr; +mod recovery_file; +mod session; + +use keys::{Keypair, PublicKey}; +use session::Session; + +impl Default for PubkyClient { + fn default() -> Self { + Self::new() + } +} + +static DEFAULT_RELAYS: [&str; 1] = ["https://relay.pkarr.org"]; +static TESTNET_RELAYS: [&str; 1] = ["http://localhost:15411/pkarr"]; + +#[wasm_bindgen] +impl PubkyClient { + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + Self { + http: reqwest::Client::builder().build().unwrap(), + session_cookies: Arc::new(RwLock::new(HashSet::new())), + pkarr_relays: DEFAULT_RELAYS.into_iter().map(|s| s.to_string()).collect(), + } + } + + /// Create a client with with configurations appropriate for local testing: + /// - set Pkarr relays to `["http://localhost:15411/pkarr"]` instead of default relay. + #[wasm_bindgen] + pub fn testnet() -> Self { + Self { + http: reqwest::Client::builder().build().unwrap(), + session_cookies: Arc::new(RwLock::new(HashSet::new())), + pkarr_relays: TESTNET_RELAYS.into_iter().map(|s| s.to_string()).collect(), + } + } + + /// Set Pkarr relays used for publishing and resolving Pkarr packets. + /// + /// By default, [PubkyClient] will use `["https://relay.pkarr.org"]` + #[wasm_bindgen(js_name = "setPkarrRelays")] + pub fn set_pkarr_relays(mut self, relays: Vec) -> Self { + self.pkarr_relays = relays; + self + } + + // Read the set of pkarr relays used by this client. + #[wasm_bindgen(js_name = "getPkarrRelays")] + pub fn get_pkarr_relays(&self) -> Vec { + self.pkarr_relays.clone() + } + + /// Signup to a homeserver and update Pkarr accordingly. + /// + /// The homeserver is a Pkarr domain name, where the TLD is a Pkarr public key + /// for example "pubky.o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy" + #[wasm_bindgen] + pub async fn signup( + &self, + keypair: &Keypair, + homeserver: &PublicKey, + ) -> Result { + Ok(Session( + self.inner_signup(keypair.as_inner(), homeserver.as_inner()) + .await + .map_err(|e| JsValue::from(e))?, + )) + } + + /// Check the current sesison for a given Pubky in its homeserver. + /// + /// Returns [Session] or `None` (if recieved `404 NOT_FOUND`), + /// or throws the recieved error if the response has any other `>=400` status code. + #[wasm_bindgen] + pub async fn session(&self, pubky: &PublicKey) -> Result, JsValue> { + self.inner_session(pubky.as_inner()) + .await + .map(|s| s.map(Session)) + .map_err(|e| e.into()) + } + + /// Signout from a homeserver. + #[wasm_bindgen] + pub async fn signout(&self, pubky: &PublicKey) -> Result<(), JsValue> { + self.inner_signout(pubky.as_inner()) + .await + .map_err(|e| e.into()) + } + + /// Signin to a homeserver using the root Keypair. + #[wasm_bindgen] + pub async fn signin(&self, keypair: &Keypair) -> Result<(), JsValue> { + self.inner_signin(keypair.as_inner()) + .await + .map(|_| ()) + .map_err(|e| e.into()) + } + + /// Return `pubkyauth://` url and wait for the incoming [AuthToken] + /// verifying that AuthToken, and if capabilities were requested, signing in to + /// the Pubky's homeserver and returning the [Session] information. + /// + /// Returns a tuple of [pubkyAuthUrl, Promise] + #[wasm_bindgen(js_name = "authRequest")] + pub fn auth_request(&self, relay: &str, capabilities: &str) -> Result { + let mut relay: Url = relay + .try_into() + .map_err(|_| Error::Generic("Invalid relay Url".into()))?; + + let (pubkyauth_url, client_secret) = self.create_auth_request( + &mut relay, + &Capabilities::try_from(capabilities).map_err(|_| "Invalid capaiblities")?, + )?; + + let this = self.clone(); + + let future = async move { + this.subscribe_to_auth_response(relay, &client_secret) + .await + .map(|opt| { + opt.map_or_else( + || JsValue::NULL, // Convert `None` to `JsValue::NULL` + |session| JsValue::from(Session(session)), + ) + }) + .map_err(|err| JsValue::from_str(&format!("{:?}", err))) + }; + + let promise = wasm_bindgen_futures::future_to_promise(future); + + // Return the URL and the promise + let js_tuple = js_sys::Array::new(); + js_tuple.push(&JsValue::from_str(pubkyauth_url.as_ref())); + js_tuple.push(&promise); + + Ok(js_tuple) + } + + /// Sign an [pubky_common::auth::AuthToken], encrypt it and send it to the + /// source of the pubkyauth request url. + #[wasm_bindgen(js_name = "sendAuthToken")] + pub async fn send_auth_token( + &self, + keypair: &Keypair, + pubkyauth_url: &str, + ) -> Result<(), JsValue> { + let pubkyauth_url: Url = pubkyauth_url + .try_into() + .map_err(|_| Error::Generic("Invalid relay Url".into()))?; + + self.inner_send_auth_token(keypair.as_inner(), pubkyauth_url) + .await?; + + Ok(()) + } + + // === Public data === + + #[wasm_bindgen] + /// Upload a small payload to a given path. + pub async fn put(&self, url: &str, content: &[u8]) -> Result<(), JsValue> { + self.inner_put(url, content).await.map_err(|e| e.into()) + } + + /// Download a small payload from a given path relative to a pubky author. + #[wasm_bindgen] + pub async fn get(&self, url: &str) -> Result, JsValue> { + self.inner_get(url) + .await + .map(|b| b.map(|b| (&*b).into())) + .map_err(|e| e.into()) + } + + /// Delete a file at a path relative to a pubky author. + #[wasm_bindgen] + pub async fn delete(&self, url: &str) -> Result<(), JsValue> { + self.inner_delete(url).await.map_err(|e| e.into()) + } + + /// Returns a list of Pubky urls (as strings). + /// + /// - `url`: The Pubky url (string) to the directory you want to list its content. + /// - `cursor`: Either a full `pubky://` Url (from previous list response), + /// or a path (to a file or directory) relative to the `url` + /// - `reverse`: List in reverse order + /// - `limit` Limit the number of urls in the response + /// - `shallow`: List directories and files, instead of flat list of files. + #[wasm_bindgen] + pub async fn list( + &self, + url: &str, + cursor: Option, + reverse: Option, + limit: Option, + shallow: Option, + ) -> Result { + // TODO: try later to return Vec from async function. + + if let Some(cursor) = cursor { + return self + .inner_list(url)? + .reverse(reverse.unwrap_or(false)) + .limit(limit.unwrap_or(u16::MAX)) + .cursor(&cursor) + .shallow(shallow.unwrap_or(false)) + .send() + .await + .map(|urls| { + let js_array = Array::new(); + + for url in urls { + js_array.push(&JsValue::from_str(&url)); + } + + js_array + }) + .map_err(|e| e.into()); + } + + self.inner_list(url)? + .reverse(reverse.unwrap_or(false)) + .limit(limit.unwrap_or(u16::MAX)) + .shallow(shallow.unwrap_or(false)) + .send() + .await + .map(|urls| { + let js_array = Array::new(); + + for url in urls { + js_array.push(&JsValue::from_str(&url)); + } + + js_array + }) + .map_err(|e| e.into()) + } +} diff --git a/rust/pubky/pubky/src/wasm/http.rs b/rust/pubky/pubky/src/wasm/http.rs new file mode 100644 index 0000000..61fee29 --- /dev/null +++ b/rust/pubky/pubky/src/wasm/http.rs @@ -0,0 +1,40 @@ +use crate::PubkyClient; + +use reqwest::{Method, RequestBuilder, Response}; +use url::Url; + +impl PubkyClient { + pub(crate) fn request(&self, method: Method, url: Url) -> RequestBuilder { + let mut request = self.http.request(method, url).fetch_credentials_include(); + + for cookie in self.session_cookies.read().unwrap().iter() { + request = request.header("Cookie", cookie); + } + + request + } + + // Support cookies for nodejs + + pub(crate) fn store_session(&self, response: &Response) { + if let Some(cookie) = response + .headers() + .get("set-cookie") + .and_then(|h| h.to_str().ok()) + .and_then(|s| s.split(';').next()) + { + self.session_cookies + .write() + .unwrap() + .insert(cookie.to_string()); + } + } + pub(crate) fn remove_session(&self, pubky: &pkarr::PublicKey) { + let key = pubky.to_string(); + + self.session_cookies + .write() + .unwrap() + .retain(|cookie| !cookie.starts_with(&key)); + } +} diff --git a/rust/pubky/pubky/src/wasm/keys.rs b/rust/pubky/pubky/src/wasm/keys.rs new file mode 100644 index 0000000..3b27045 --- /dev/null +++ b/rust/pubky/pubky/src/wasm/keys.rs @@ -0,0 +1,99 @@ +use wasm_bindgen::prelude::*; + +use crate::Error; + +#[wasm_bindgen] +pub struct Keypair(pkarr::Keypair); + +#[wasm_bindgen] +impl Keypair { + #[wasm_bindgen] + /// Generate a random [Keypair] + pub fn random() -> Self { + Self(pkarr::Keypair::random()) + } + + /// Generate a [Keypair] from a secret key. + #[wasm_bindgen(js_name = "fromSecretKey")] + pub fn from_secret_key(secret_key: js_sys::Uint8Array) -> Result { + if !js_sys::Uint8Array::instanceof(&secret_key) { + return Err("Expected secret_key to be an instance of Uint8Array".into()); + } + + let len = secret_key.byte_length(); + if len != 32 { + return Err(format!("Expected secret_key to be 32 bytes, got {len}"))?; + } + + let mut bytes = [0; 32]; + secret_key.copy_to(&mut bytes); + + Ok(Self(pkarr::Keypair::from_secret_key(&bytes))) + } + + /// Returns the secret key of this keypair. + #[wasm_bindgen(js_name = "secretKey")] + pub fn secret_key(&self) -> js_sys::Uint8Array { + self.0.secret_key().as_slice().into() + } + + /// Returns the [PublicKey] of this keypair. + #[wasm_bindgen(js_name = "publicKey")] + pub fn public_key(&self) -> PublicKey { + PublicKey(self.0.public_key()) + } +} + +impl Keypair { + pub fn as_inner(&self) -> &pkarr::Keypair { + &self.0 + } +} + +impl From for Keypair { + fn from(keypair: pkarr::Keypair) -> Self { + Self(keypair) + } +} + +#[wasm_bindgen] +pub struct PublicKey(pub(crate) pkarr::PublicKey); + +#[wasm_bindgen] +impl PublicKey { + #[wasm_bindgen] + /// Convert the PublicKey to Uint8Array + pub fn to_uint8array(&self) -> js_sys::Uint8Array { + js_sys::Uint8Array::from(self.0.as_bytes().as_slice()) + } + + #[wasm_bindgen] + /// Returns the z-base32 encoding of this public key + pub fn z32(&self) -> String { + self.0.to_string() + } + + #[wasm_bindgen(js_name = "from")] + /// @throws + pub fn try_from(value: JsValue) -> Result { + let string = value + .as_string() + .ok_or("Couldn't create a PublicKey from this type of value")?; + + Ok(PublicKey( + pkarr::PublicKey::try_from(string).map_err(Error::Pkarr)?, + )) + } +} + +impl PublicKey { + pub fn as_inner(&self) -> &pkarr::PublicKey { + &self.0 + } +} + +impl From for PublicKey { + fn from(value: pkarr::PublicKey) -> Self { + PublicKey(value) + } +} diff --git a/rust/pubky/pubky/src/wasm/pkarr.rs b/rust/pubky/pubky/src/wasm/pkarr.rs new file mode 100644 index 0000000..49726f6 --- /dev/null +++ b/rust/pubky/pubky/src/wasm/pkarr.rs @@ -0,0 +1,48 @@ +use reqwest::StatusCode; + +pub use pkarr::{PublicKey, SignedPacket}; + +use crate::error::Result; +use crate::PubkyClient; + +// TODO: Add an in memory cache of packets + +impl PubkyClient { + //TODO: migrate to pkarr::PkarrRelayClient + pub(crate) async fn pkarr_resolve( + &self, + public_key: &PublicKey, + ) -> Result> { + //TODO: Allow multiple relays in parallel + let relay = self.pkarr_relays.first().expect("initialized with relays"); + + let res = self + .http + .get(format!("{relay}/{}", public_key)) + .send() + .await?; + + if res.status() == StatusCode::NOT_FOUND { + return Ok(None); + }; + + // TODO: guard against too large responses. + let bytes = res.bytes().await?; + + let existing = SignedPacket::from_relay_payload(public_key, &bytes)?; + + Ok(Some(existing)) + } + + pub(crate) async fn pkarr_publish(&self, signed_packet: &SignedPacket) -> Result<()> { + let relay = self.pkarr_relays.first().expect("initialized with relays"); + + self.http + .put(format!("{relay}/{}", signed_packet.public_key())) + .body(signed_packet.to_relay_payload()) + .send() + .await?; + + Ok(()) + } +} diff --git a/rust/pubky/pubky/src/wasm/recovery_file.rs b/rust/pubky/pubky/src/wasm/recovery_file.rs new file mode 100644 index 0000000..7b85178 --- /dev/null +++ b/rust/pubky/pubky/src/wasm/recovery_file.rs @@ -0,0 +1,24 @@ +use js_sys::Uint8Array; +use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; + +use crate::error::Error; + +use super::keys::Keypair; + +/// Create a recovery file of the `keypair`, containing the secret key encrypted +/// using the `passphrase`. +#[wasm_bindgen(js_name = "createRecoveryFile")] +pub fn create_recovery_file(keypair: &Keypair, passphrase: &str) -> Result { + pubky_common::recovery_file::create_recovery_file(keypair.as_inner(), passphrase) + .map(|b| b.as_slice().into()) + .map_err(|e| Error::from(e).into()) +} + +/// Create a recovery file of the `keypair`, containing the secret key encrypted +/// using the `passphrase`. +#[wasm_bindgen(js_name = "decryptRecoveryFile")] +pub fn decrypt_recovery_file(recovery_file: &[u8], passphrase: &str) -> Result { + pubky_common::recovery_file::decrypt_recovery_file(recovery_file, passphrase) + .map(Keypair::from) + .map_err(|e| Error::from(e).into()) +} diff --git a/rust/pubky/pubky/src/wasm/session.rs b/rust/pubky/pubky/src/wasm/session.rs new file mode 100644 index 0000000..e838a80 --- /dev/null +++ b/rust/pubky/pubky/src/wasm/session.rs @@ -0,0 +1,27 @@ +use pubky_common::session; + +use wasm_bindgen::prelude::*; + +use super::keys::PublicKey; + +#[wasm_bindgen] +pub struct Session(pub(crate) session::Session); + +#[wasm_bindgen] +impl Session { + /// Return the [PublicKey] of this session + #[wasm_bindgen] + pub fn pubky(&self) -> PublicKey { + self.0.pubky().clone().into() + } + + /// Return the capabilities that this session has. + #[wasm_bindgen] + pub fn capabilities(&self) -> Vec { + self.0 + .capabilities() + .iter() + .map(|c| c.to_string()) + .collect() + } +} diff --git a/rust/src/bin/uniffi-bindgen.rs b/rust/src/bin/uniffi-bindgen.rs new file mode 100644 index 0000000..f6cff6c --- /dev/null +++ b/rust/src/bin/uniffi-bindgen.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi::uniffi_bindgen_main() +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000..c51f486 --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,40 @@ +uniffi::setup_scaffolding!(); + +use url::Url; +use pubky::PubkyClient; + +#[uniffi::export] +async fn auth(url: String, secret_key: String) -> Vec { + let bytes = match hex::decode(&secret_key) { + Ok(bytes) => bytes, + Err(_) => return create_response_vector(true, "Failed to decode secret key".to_string()) + }; + + let secret_key_bytes: [u8; 32] = match bytes.try_into() { + Ok(secret_key) => secret_key, + Err(_) => { + return create_response_vector(true, "Failed to convert secret key to 32-byte array".to_string()); + } + }; + + let keypair = pkarr::Keypair::from_secret_key(&secret_key_bytes); + let client = PubkyClient::testnet(); + + 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, "Auth token sent successfully".to_string()), + Err(error) => create_response_vector(true, format!("Error sending auth token: {:?}", error)), + } +} + +fn create_response_vector(error: bool, data: String) -> Vec { + if error { + vec!["error".to_string(), data] + } else { + vec!["success".to_string(), data] + } +} diff --git a/setup-android-bindings.js b/setup-android-bindings.js new file mode 100644 index 0000000..d40c97d --- /dev/null +++ b/setup-android-bindings.js @@ -0,0 +1,97 @@ +const exec = require('child_process').exec; +const fs = require('fs'); +const path = require('path'); +const { promisify } = require('util'); + +const execAsync = promisify(exec); + +const directoriesToRemove = ['app', 'target']; + +const removeDirectories = () => { + directoriesToRemove.forEach((dir) => { + const dirPath = path.resolve('rust', dir); + if (fs.existsSync(dirPath)) { + fs.rmSync(dirPath, { recursive: true }); + console.log(`Removed directory: ${dirPath}`); + } + }); +}; + +const setupAndroidCommand = ` + sed -i '' 's/crate\\_type = .\\*/crate\\_type = \\["cdylib"\\]/' Cargo.toml && \\ + cargo build --release && \\ + cargo install cargo-ndk && \\ + rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android && \\ + cargo ndk -o ./app/src/main/jniLibs --manifest-path ./Cargo.toml -t armeabi-v7a -t arm64-v8a -t x86 -t x86_64 build --release && \\ + cargo run --bin uniffi-bindgen generate --library ./target/release/libmobile.dylib --language kotlin --out-dir ./app/src/main/java/tech/forgen/todolist/rust +`; + +const postSetupAndroid = async () => { + const rustMobileKt = path.resolve( + 'rust', + 'app', + 'src', + 'main', + 'java', + 'tech', + 'forgen', + 'todolist', + 'rust', + 'uniffi', + 'mobile', + 'mobile.kt' + ); + const androidMobileKt = path.resolve( + 'android', + 'src', + 'main', + 'java', + 'uniffi', + 'mobile', + 'mobile.kt' + ); + + // Create the destination directory if it doesn't exist + await fs.promises.mkdir(path.dirname(androidMobileKt), { recursive: true }); + + // Copy rust/app/src/main/java/tech/forgen/todolist/rust/uniffi/mobile/mobile.kt to android/src/main/java/uniffi/mobile/mobile.kt + await fs.promises.copyFile(rustMobileKt, androidMobileKt); + console.log(`Copied ${rustMobileKt} to ${androidMobileKt}`); + + const rustJniLibs = path.resolve('rust', 'app', 'src', 'main', 'jniLibs'); + const androidJniLibs = path.resolve('android', 'src', 'main', 'jniLibs'); + + // Copy rust/app/src/main/jniLibs to android/src/main/jniLibs + await fs.promises.cp(rustJniLibs, androidJniLibs, { + recursive: true, + force: true, + }); + console.log(`Copied contents of ${rustJniLibs} to ${androidJniLibs}`); +}; + +const originalDir = process.cwd(); + +const runSetup = async () => { + try { + removeDirectories(); + + // Change the current working directory to the 'rust' directory + process.chdir('rust'); + + const { stdout, stderr } = await execAsync(setupAndroidCommand); + console.log(`Setup Android command output: ${stdout}`); + + if (stderr) { + console.error(`Setup Android command stderr: ${stderr}`); + } + + // Revert to the original directory after setupAndroidCommand execution + process.chdir(originalDir); + + await postSetupAndroid(); + } catch (error) { + console.error(`Error executing setup-android command: ${error.message}`); + } +}; + +runSetup(); diff --git a/setup-ios-bindings.js b/setup-ios-bindings.js new file mode 100644 index 0000000..87b1d53 --- /dev/null +++ b/setup-ios-bindings.js @@ -0,0 +1,108 @@ +const { exec } = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const { promisify } = require('util'); + +const execAsync = promisify(exec); + +const directoriesToRemove = ['bindings', 'ios', 'target']; + +const removeDirectories = () => { + directoriesToRemove.forEach((dir) => { + const dirPath = path.resolve('rust', dir); + if (fs.existsSync(dirPath)) { + fs.rmSync(dirPath, { recursive: true }); + console.log(`Removed directory: ${dirPath}`); + } + }); +}; + +const setupIosCommand = ` + export IPHONEOS_DEPLOYMENT_TARGET=13.4 + sed -i '' 's/crate_type = .*/crate_type = ["cdylib", "staticlib"]/' Cargo.toml && \\ + cargo build --release && \\ + cargo run --bin uniffi-bindgen generate --library ./target/release/libmobile.dylib --language swift --out-dir ./bindings && \\ + rustup target add aarch64-apple-ios-sim aarch64-apple-ios && \\ + cargo build --release --target=aarch64-apple-ios-sim && \\ + cargo build --release --target=aarch64-apple-ios && \\ + mv bindings/mobileFFI.modulemap bindings/module.modulemap && \\ + xcodebuild -create-xcframework -library ./target/aarch64-apple-ios-sim/release/libmobile.a -headers ./bindings -library ./target/aarch64-apple-ios/release/libmobile.a -headers ./bindings -output "ios/Mobile.xcframework" +`; + +const originalDir = process.cwd(); + +const postSetupIos = async () => { + const rustBindingsMobileSwift = path.resolve( + 'rust', + 'bindings', + 'mobile.swift' + ); + const iosMobileSwift = path.resolve('ios', 'mobile.swift'); + + // Copy rust/bindings/mobile.swift file to ios/ directory + await fs.promises.copyFile(rustBindingsMobileSwift, iosMobileSwift); + console.log(`Copied ${rustBindingsMobileSwift} to ${iosMobileSwift}`); + + // Delete rust/ios/Mobile.xcframework/ios-arm64/Headers/mobile.swift + const iosArm64HeadersMobileSwift = path.resolve( + 'rust', + 'ios', + 'Mobile.xcframework', + 'ios-arm64', + 'Headers', + 'mobile.swift' + ); + if (fs.existsSync(iosArm64HeadersMobileSwift)) { + await fs.promises.unlink(iosArm64HeadersMobileSwift); + console.log(`Deleted ${iosArm64HeadersMobileSwift}`); + } + + // Delete rust/ios/Mobile.xcframework/ios-arm64-simulator/Headers/mobile.swift + const iosArm64SimulatorHeadersMobileSwift = path.resolve( + 'rust', + 'ios', + 'Mobile.xcframework', + 'ios-arm64-simulator', + 'Headers', + 'mobile.swift' + ); + if (fs.existsSync(iosArm64SimulatorHeadersMobileSwift)) { + await fs.promises.unlink(iosArm64SimulatorHeadersMobileSwift); + console.log(`Deleted ${iosArm64SimulatorHeadersMobileSwift}`); + } + + const rustIos = path.resolve('rust', 'ios'); + const frameworksDir = path.resolve('ios', 'Frameworks'); + + // Copy contents of rust/ios/ to Frameworks directory + await fs.promises.cp(rustIos, frameworksDir, { + recursive: true, + force: true, + }); + console.log(`Copied contents of ${rustIos} to ${frameworksDir}`); +}; + +const runSetup = async () => { + try { + removeDirectories(); + + // Change the current working directory to the 'rust' directory + process.chdir('rust'); + + const { stdout, stderr } = await execAsync(setupIosCommand); + console.log(`Setup iOS command output: ${stdout}`); + + if (stderr) { + console.error(`Setup iOS command stderr: ${stderr}`); + } + + // Revert to the original directory after setupIosCommand + process.chdir(originalDir); + + await postSetupIos(); + } catch (error) { + console.error(`Error executing setup-ios command: ${error.message}`); + } +}; + +runSetup(); diff --git a/src/__tests__/index.test.tsx b/src/__tests__/index.test.tsx new file mode 100644 index 0000000..bf84291 --- /dev/null +++ b/src/__tests__/index.test.tsx @@ -0,0 +1 @@ +it.todo('write a test'); diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..c092e7d --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,30 @@ +import { NativeModules, Platform } from 'react-native'; +import { ok, err, type Result } from '@synonymdev/result'; + +const LINKING_ERROR = + `The package 'react-native-pubky' doesn't seem to be linked. Make sure: \n\n` + + Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + + '- You rebuilt the app after installing the package\n' + + '- You are not using Expo Go\n'; + +const Pubky = NativeModules.Pubky + ? NativeModules.Pubky + : new Proxy( + {}, + { + get() { + throw new Error(LINKING_ERROR); + }, + } + ); + +export async function auth( + url: string, + secretKey: string +): Promise> { + const res = await Pubky.auth(url, secretKey); + if (res[0] === 'error') { + return err(res[1]); + } + return ok(res[1]); +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..3c0636a --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig", + "exclude": ["example", "lib"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a1cf322 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "rootDir": ".", + "paths": { + "react-native-pubky": ["./src/index"] + }, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react-jsx", + "lib": ["ESNext"], + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noImplicitUseStrict": false, + "noStrictGenericChecks": false, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "ESNext", + "verbatimModuleSyntax": true + } +} diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..405897e --- /dev/null +++ b/turbo.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://turbo.build/schema.json", + "pipeline": { + "build:android": { + "env": ["ORG_GRADLE_PROJECT_newArchEnabled"], + "inputs": [ + "package.json", + "android", + "!android/build", + "src/*.ts", + "src/*.tsx", + "example/package.json", + "example/android", + "!example/android/.gradle", + "!example/android/build", + "!example/android/app/build" + ], + "outputs": [] + }, + "build:ios": { + "env": ["RCT_NEW_ARCH_ENABLED"], + "inputs": [ + "package.json", + "*.podspec", + "ios", + "src/*.ts", + "src/*.tsx", + "example/package.json", + "example/ios", + "!example/ios/build", + "!example/ios/Pods" + ], + "outputs": [] + } + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..95618ac --- /dev/null +++ b/yarn.lock @@ -0,0 +1,7324 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.24.7": + version "7.24.7" + dependencies: + "@babel/highlight" "^7.24.7" + picocolors "^1.0.0" + +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.2", "@babel/compat-data@^7.25.4": + version "7.25.4" + +"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.0.0-0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.11.0", "@babel/core@^7.11.6", "@babel/core@^7.12.0", "@babel/core@^7.12.3", "@babel/core@^7.13.0", "@babel/core@^7.13.16", "@babel/core@^7.20.0", "@babel/core@^7.23.9", "@babel/core@^7.25.2", "@babel/core@^7.4.0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.8.0": + version "7.25.2" + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.25.0" + "@babel/helper-compilation-targets" "^7.25.2" + "@babel/helper-module-transforms" "^7.25.2" + "@babel/helpers" "^7.25.0" + "@babel/parser" "^7.25.0" + "@babel/template" "^7.25.0" + "@babel/traverse" "^7.25.2" + "@babel/types" "^7.25.2" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/eslint-parser@^7.12.0", "@babel/eslint-parser@^7.20.0": + version "7.25.1" + dependencies: + "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" + eslint-visitor-keys "^2.1.0" + semver "^6.3.1" + +"@babel/generator@^7.20.0", "@babel/generator@^7.25.0", "@babel/generator@^7.25.6", "@babel/generator@^7.7.2": + version "7.25.6" + dependencies: + "@babel/types" "^7.25.6" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@^7.24.7": + version "7.24.7" + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.24.7": + version "7.24.7" + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.24.7", "@babel/helper-compilation-targets@^7.24.8", "@babel/helper-compilation-targets@^7.25.2": + version "7.25.2" + dependencies: + "@babel/compat-data" "^7.25.2" + "@babel/helper-validator-option" "^7.24.8" + browserslist "^4.23.1" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.24.7", "@babel/helper-create-class-features-plugin@^7.25.0", "@babel/helper-create-class-features-plugin@^7.25.4": + version "7.25.4" + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-member-expression-to-functions" "^7.24.8" + "@babel/helper-optimise-call-expression" "^7.24.7" + "@babel/helper-replace-supers" "^7.25.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/traverse" "^7.25.4" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.24.7", "@babel/helper-create-regexp-features-plugin@^7.25.0", "@babel/helper-create-regexp-features-plugin@^7.25.2": + version "7.25.2" + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + regexpu-core "^5.3.1" + semver "^6.3.1" + +"@babel/helper-define-polyfill-provider@^0.6.2": + version "0.6.2" + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + +"@babel/helper-member-expression-to-functions@^7.24.8": + version "7.24.8" + dependencies: + "@babel/traverse" "^7.24.8" + "@babel/types" "^7.24.8" + +"@babel/helper-module-imports@^7.24.7": + version "7.24.7" + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-module-transforms@^7.24.7", "@babel/helper-module-transforms@^7.24.8", "@babel/helper-module-transforms@^7.25.0", "@babel/helper-module-transforms@^7.25.2": + version "7.25.2" + dependencies: + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-simple-access" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + "@babel/traverse" "^7.25.2" + +"@babel/helper-optimise-call-expression@^7.24.7": + version "7.24.7" + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.24.8", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.24.8" + +"@babel/helper-remap-async-to-generator@^7.24.7", "@babel/helper-remap-async-to-generator@^7.25.0": + version "7.25.0" + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-wrap-function" "^7.25.0" + "@babel/traverse" "^7.25.0" + +"@babel/helper-replace-supers@^7.24.7", "@babel/helper-replace-supers@^7.25.0": + version "7.25.0" + dependencies: + "@babel/helper-member-expression-to-functions" "^7.24.8" + "@babel/helper-optimise-call-expression" "^7.24.7" + "@babel/traverse" "^7.25.0" + +"@babel/helper-simple-access@^7.24.7": + version "7.24.7" + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-skip-transparent-expression-wrappers@^7.20.0", "@babel/helper-skip-transparent-expression-wrappers@^7.24.7": + version "7.24.7" + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-string-parser@^7.24.8": + version "7.24.8" + +"@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + +"@babel/helper-validator-option@^7.24.7", "@babel/helper-validator-option@^7.24.8": + version "7.24.8" + +"@babel/helper-wrap-function@^7.25.0": + version "7.25.0" + dependencies: + "@babel/template" "^7.25.0" + "@babel/traverse" "^7.25.0" + "@babel/types" "^7.25.0" + +"@babel/helpers@^7.25.0": + version "7.25.6" + dependencies: + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.6" + +"@babel/highlight@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-validator-identifier" "^7.24.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.7", "@babel/parser@^7.20.0", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.0", "@babel/parser@^7.25.6": + version "7.25.6" + dependencies: + "@babel/types" "^7.25.6" + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.3": + version "7.25.3" + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/traverse" "^7.25.3" + +"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.25.0": + version "7.25.0" + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.25.0": + version "7.25.0" + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/plugin-transform-optional-chaining" "^7.24.7" + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.25.0": + version "7.25.0" + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/traverse" "^7.25.0" + +"@babel/plugin-proposal-class-properties@^7.13.0": + version "7.18.6" + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-proposal-export-default-from@^7.0.0": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-export-default-from" "^7.24.7" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.13.8": + version "7.18.6" + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-proposal-optional-chaining@^7.13.12": + version "7.21.0" + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": + version "7.21.0-placeholder-for-preset-env.2" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-dynamic-import@^7.8.0", "@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-default-from@^7.0.0", "@babel/plugin-syntax-export-default-from@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-syntax-export-namespace-from@^7.8.3": + version "7.8.3" + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-flow@^7.12.1", "@babel/plugin-syntax-flow@^7.18.0", "@babel/plugin-syntax-flow@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-syntax-import-assertions@^7.24.7": + version "7.25.6" + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.25.6" + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.24.7", "@babel/plugin-syntax-jsx@^7.7.2": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.0.0", "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.0.0", "@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.24.7", "@babel/plugin-syntax-typescript@^7.7.2": + version "7.25.4" + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": + version "7.18.6" + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-arrow-functions@^7.0.0", "@babel/plugin-transform-arrow-functions@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-async-generator-functions@^7.24.3", "@babel/plugin-transform-async-generator-functions@^7.25.4": + version "7.25.4" + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-remap-async-to-generator" "^7.25.0" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/traverse" "^7.25.4" + +"@babel/plugin-transform-async-to-generator@^7.20.0", "@babel/plugin-transform-async-to-generator@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-remap-async-to-generator" "^7.24.7" + +"@babel/plugin-transform-block-scoped-functions@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-block-scoping@^7.0.0", "@babel/plugin-transform-block-scoping@^7.25.0": + version "7.25.0" + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-transform-class-properties@^7.24.1", "@babel/plugin-transform-class-properties@^7.25.4": + version "7.25.4" + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.4" + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-transform-class-static-block@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-transform-classes@^7.0.0", "@babel/plugin-transform-classes@^7.25.4": + version "7.25.4" + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-compilation-targets" "^7.25.2" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-replace-supers" "^7.25.0" + "@babel/traverse" "^7.25.4" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.0.0", "@babel/plugin-transform-computed-properties@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/template" "^7.24.7" + +"@babel/plugin-transform-destructuring@^7.20.0", "@babel/plugin-transform-destructuring@^7.24.8": + version "7.24.8" + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-transform-dotall-regex@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-duplicate-keys@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.25.0": + version "7.25.0" + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.0" + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-transform-dynamic-import@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-transform-exponentiation-operator@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-export-namespace-from@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-transform-flow-strip-types@^7.20.0", "@babel/plugin-transform-flow-strip-types@^7.24.7": + version "7.25.2" + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/plugin-syntax-flow" "^7.24.7" + +"@babel/plugin-transform-for-of@^7.0.0", "@babel/plugin-transform-for-of@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + +"@babel/plugin-transform-function-name@^7.0.0", "@babel/plugin-transform-function-name@^7.25.1": + version "7.25.1" + dependencies: + "@babel/helper-compilation-targets" "^7.24.8" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/traverse" "^7.25.1" + +"@babel/plugin-transform-json-strings@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-json-strings" "^7.8.3" + +"@babel/plugin-transform-literals@^7.0.0", "@babel/plugin-transform-literals@^7.25.2": + version "7.25.2" + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-transform-logical-assignment-operators@^7.24.1", "@babel/plugin-transform-logical-assignment-operators@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-transform-member-expression-literals@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-modules-amd@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-module-transforms" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.13.8", "@babel/plugin-transform-modules-commonjs@^7.24.7", "@babel/plugin-transform-modules-commonjs@^7.24.8": + version "7.24.8" + dependencies: + "@babel/helper-module-transforms" "^7.24.8" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-simple-access" "^7.24.7" + +"@babel/plugin-transform-modules-systemjs@^7.25.0": + version "7.25.0" + dependencies: + "@babel/helper-module-transforms" "^7.25.0" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-validator-identifier" "^7.24.7" + "@babel/traverse" "^7.25.0" + +"@babel/plugin-transform-modules-umd@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-module-transforms" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.0.0", "@babel/plugin-transform-named-capturing-groups-regex@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-new-target@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.24.1", "@babel/plugin-transform-nullish-coalescing-operator@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-transform-numeric-separator@^7.24.1", "@babel/plugin-transform-numeric-separator@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-transform-object-rest-spread@^7.24.5", "@babel/plugin-transform-object-rest-spread@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-compilation-targets" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.24.7" + +"@babel/plugin-transform-object-super@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-replace-supers" "^7.24.7" + +"@babel/plugin-transform-optional-catch-binding@^7.24.1", "@babel/plugin-transform-optional-catch-binding@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-transform-optional-chaining@^7.24.5", "@babel/plugin-transform-optional-chaining@^7.24.7", "@babel/plugin-transform-optional-chaining@^7.24.8": + version "7.24.8" + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-private-methods@^7.22.5", "@babel/plugin-transform-private-methods@^7.25.4": + version "7.25.4" + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.4" + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-transform-private-property-in-object@^7.22.11", "@babel/plugin-transform-private-property-in-object@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-transform-property-literals@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-react-display-name@^7.0.0", "@babel/plugin-transform-react-display-name@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-react-jsx-development@^7.24.7": + version "7.24.7" + dependencies: + "@babel/plugin-transform-react-jsx" "^7.24.7" + +"@babel/plugin-transform-react-jsx-self@^7.0.0": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-react-jsx-source@^7.0.0": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-react-jsx@^7.0.0", "@babel/plugin-transform-react-jsx@^7.24.7": + version "7.25.2" + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/plugin-syntax-jsx" "^7.24.7" + "@babel/types" "^7.25.2" + +"@babel/plugin-transform-react-pure-annotations@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-regenerator@^7.20.0", "@babel/plugin-transform-regenerator@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + regenerator-transform "^0.15.2" + +"@babel/plugin-transform-reserved-words@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-runtime@^7.0.0": + version "7.25.4" + dependencies: + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.10.6" + babel-plugin-polyfill-regenerator "^0.6.1" + semver "^6.3.1" + +"@babel/plugin-transform-shorthand-properties@^7.0.0", "@babel/plugin-transform-shorthand-properties@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-spread@^7.0.0", "@babel/plugin-transform-spread@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + +"@babel/plugin-transform-sticky-regex@^7.0.0", "@babel/plugin-transform-sticky-regex@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-strict-mode@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-template-literals@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-typeof-symbol@^7.24.8": + version "7.24.8" + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-transform-typescript@^7.24.7", "@babel/plugin-transform-typescript@^7.5.0": + version "7.25.2" + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-create-class-features-plugin" "^7.25.0" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/plugin-syntax-typescript" "^7.24.7" + +"@babel/plugin-transform-unicode-escapes@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-unicode-property-regex@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-unicode-regex@^7.0.0", "@babel/plugin-transform-unicode-regex@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-unicode-sets-regex@^7.25.4": + version "7.25.4" + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.2" + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/preset-env@^7.1.6", "@babel/preset-env@^7.20.0", "@babel/preset-env@^7.25.2": + version "7.25.4" + dependencies: + "@babel/compat-data" "^7.25.4" + "@babel/helper-compilation-targets" "^7.25.2" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-validator-option" "^7.24.8" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.25.3" + "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.25.0" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.25.0" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.7" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.25.0" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.24.7" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.24.7" + "@babel/plugin-transform-async-generator-functions" "^7.25.4" + "@babel/plugin-transform-async-to-generator" "^7.24.7" + "@babel/plugin-transform-block-scoped-functions" "^7.24.7" + "@babel/plugin-transform-block-scoping" "^7.25.0" + "@babel/plugin-transform-class-properties" "^7.25.4" + "@babel/plugin-transform-class-static-block" "^7.24.7" + "@babel/plugin-transform-classes" "^7.25.4" + "@babel/plugin-transform-computed-properties" "^7.24.7" + "@babel/plugin-transform-destructuring" "^7.24.8" + "@babel/plugin-transform-dotall-regex" "^7.24.7" + "@babel/plugin-transform-duplicate-keys" "^7.24.7" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.25.0" + "@babel/plugin-transform-dynamic-import" "^7.24.7" + "@babel/plugin-transform-exponentiation-operator" "^7.24.7" + "@babel/plugin-transform-export-namespace-from" "^7.24.7" + "@babel/plugin-transform-for-of" "^7.24.7" + "@babel/plugin-transform-function-name" "^7.25.1" + "@babel/plugin-transform-json-strings" "^7.24.7" + "@babel/plugin-transform-literals" "^7.25.2" + "@babel/plugin-transform-logical-assignment-operators" "^7.24.7" + "@babel/plugin-transform-member-expression-literals" "^7.24.7" + "@babel/plugin-transform-modules-amd" "^7.24.7" + "@babel/plugin-transform-modules-commonjs" "^7.24.8" + "@babel/plugin-transform-modules-systemjs" "^7.25.0" + "@babel/plugin-transform-modules-umd" "^7.24.7" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.24.7" + "@babel/plugin-transform-new-target" "^7.24.7" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.7" + "@babel/plugin-transform-numeric-separator" "^7.24.7" + "@babel/plugin-transform-object-rest-spread" "^7.24.7" + "@babel/plugin-transform-object-super" "^7.24.7" + "@babel/plugin-transform-optional-catch-binding" "^7.24.7" + "@babel/plugin-transform-optional-chaining" "^7.24.8" + "@babel/plugin-transform-parameters" "^7.24.7" + "@babel/plugin-transform-private-methods" "^7.25.4" + "@babel/plugin-transform-private-property-in-object" "^7.24.7" + "@babel/plugin-transform-property-literals" "^7.24.7" + "@babel/plugin-transform-regenerator" "^7.24.7" + "@babel/plugin-transform-reserved-words" "^7.24.7" + "@babel/plugin-transform-shorthand-properties" "^7.24.7" + "@babel/plugin-transform-spread" "^7.24.7" + "@babel/plugin-transform-sticky-regex" "^7.24.7" + "@babel/plugin-transform-template-literals" "^7.24.7" + "@babel/plugin-transform-typeof-symbol" "^7.24.8" + "@babel/plugin-transform-unicode-escapes" "^7.24.7" + "@babel/plugin-transform-unicode-property-regex" "^7.24.7" + "@babel/plugin-transform-unicode-regex" "^7.24.7" + "@babel/plugin-transform-unicode-sets-regex" "^7.25.4" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.10.6" + babel-plugin-polyfill-regenerator "^0.6.1" + core-js-compat "^3.37.1" + semver "^6.3.1" + +"@babel/preset-flow@^7.13.13", "@babel/preset-flow@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-validator-option" "^7.24.7" + "@babel/plugin-transform-flow-strip-types" "^7.24.7" + +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/preset-react@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-validator-option" "^7.24.7" + "@babel/plugin-transform-react-display-name" "^7.24.7" + "@babel/plugin-transform-react-jsx" "^7.24.7" + "@babel/plugin-transform-react-jsx-development" "^7.24.7" + "@babel/plugin-transform-react-pure-annotations" "^7.24.7" + +"@babel/preset-typescript@^7.13.0", "@babel/preset-typescript@^7.24.7": + version "7.24.7" + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-validator-option" "^7.24.7" + "@babel/plugin-syntax-jsx" "^7.24.7" + "@babel/plugin-transform-modules-commonjs" "^7.24.7" + "@babel/plugin-transform-typescript" "^7.24.7" + +"@babel/register@^7.13.16": + version "7.24.6" + dependencies: + clone-deep "^4.0.1" + find-cache-dir "^2.0.0" + make-dir "^2.1.0" + pirates "^4.0.6" + source-map-support "^0.5.16" + +"@babel/regjsgen@^0.8.0": + version "0.8.0" + +"@babel/runtime@^7.20.0", "@babel/runtime@^7.25.0", "@babel/runtime@^7.8.4": + version "7.25.6" + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/template@^7.0.0", "@babel/template@^7.24.7", "@babel/template@^7.25.0", "@babel/template@^7.3.3": + version "7.25.0" + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.25.0" + "@babel/types" "^7.25.0" + +"@babel/traverse@^7.20.0", "@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0", "@babel/traverse@^7.25.1", "@babel/traverse@^7.25.2", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.4": + version "7.25.6" + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.25.6" + "@babel/parser" "^7.25.6" + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.6" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.6", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.25.6" + dependencies: + "@babel/helper-string-parser" "^7.24.8" + "@babel/helper-validator-identifier" "^7.24.7" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + +"@commitlint/cli@^17.8.1": + version "17.8.1" + dependencies: + "@commitlint/format" "^17.8.1" + "@commitlint/lint" "^17.8.1" + "@commitlint/load" "^17.8.1" + "@commitlint/read" "^17.8.1" + "@commitlint/types" "^17.8.1" + execa "^5.0.0" + lodash.isfunction "^3.0.9" + resolve-from "5.0.0" + resolve-global "1.0.0" + yargs "^17.0.0" + +"@commitlint/config-conventional@^17.0.2": + version "17.8.1" + dependencies: + conventional-changelog-conventionalcommits "^6.1.0" + +"@commitlint/config-validator@^17.8.1": + version "17.8.1" + dependencies: + "@commitlint/types" "^17.8.1" + ajv "^8.11.0" + +"@commitlint/ensure@^17.8.1": + version "17.8.1" + dependencies: + "@commitlint/types" "^17.8.1" + lodash.camelcase "^4.3.0" + lodash.kebabcase "^4.1.1" + lodash.snakecase "^4.1.1" + lodash.startcase "^4.4.0" + lodash.upperfirst "^4.3.1" + +"@commitlint/execute-rule@^17.8.1": + version "17.8.1" + +"@commitlint/format@^17.8.1": + version "17.8.1" + dependencies: + "@commitlint/types" "^17.8.1" + chalk "^4.1.0" + +"@commitlint/is-ignored@^17.8.1": + version "17.8.1" + dependencies: + "@commitlint/types" "^17.8.1" + semver "7.5.4" + +"@commitlint/lint@^17.8.1": + version "17.8.1" + dependencies: + "@commitlint/is-ignored" "^17.8.1" + "@commitlint/parse" "^17.8.1" + "@commitlint/rules" "^17.8.1" + "@commitlint/types" "^17.8.1" + +"@commitlint/load@^17.8.1": + version "17.8.1" + dependencies: + "@commitlint/config-validator" "^17.8.1" + "@commitlint/execute-rule" "^17.8.1" + "@commitlint/resolve-extends" "^17.8.1" + "@commitlint/types" "^17.8.1" + "@types/node" "20.5.1" + chalk "^4.1.0" + cosmiconfig "^8.0.0" + cosmiconfig-typescript-loader "^4.0.0" + lodash.isplainobject "^4.0.6" + lodash.merge "^4.6.2" + lodash.uniq "^4.5.0" + resolve-from "^5.0.0" + ts-node "^10.8.1" + typescript "^4.6.4 || ^5.2.2" + +"@commitlint/message@^17.8.1": + version "17.8.1" + +"@commitlint/parse@^17.8.1": + version "17.8.1" + dependencies: + "@commitlint/types" "^17.8.1" + conventional-changelog-angular "^6.0.0" + conventional-commits-parser "^4.0.0" + +"@commitlint/read@^17.8.1": + version "17.8.1" + dependencies: + "@commitlint/top-level" "^17.8.1" + "@commitlint/types" "^17.8.1" + fs-extra "^11.0.0" + git-raw-commits "^2.0.11" + minimist "^1.2.6" + +"@commitlint/resolve-extends@^17.8.1": + version "17.8.1" + dependencies: + "@commitlint/config-validator" "^17.8.1" + "@commitlint/types" "^17.8.1" + import-fresh "^3.0.0" + lodash.mergewith "^4.6.2" + resolve-from "^5.0.0" + resolve-global "^1.0.0" + +"@commitlint/rules@^17.8.1": + version "17.8.1" + dependencies: + "@commitlint/ensure" "^17.8.1" + "@commitlint/message" "^17.8.1" + "@commitlint/to-lines" "^17.8.1" + "@commitlint/types" "^17.8.1" + execa "^5.0.0" + +"@commitlint/to-lines@^17.8.1": + version "17.8.1" + +"@commitlint/top-level@^17.8.1": + version "17.8.1" + dependencies: + find-up "^5.0.0" + +"@commitlint/types@^17.8.1": + version "17.8.1" + dependencies: + chalk "^4.1.0" + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": + version "4.11.0" + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.0": + version "8.57.0" + +"@evilmartians/lefthook@^1.5.0": + version "1.7.15" + +"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": + version "9.3.0" + +"@hapi/topo@^5.1.0": + version "5.1.0" + dependencies: + "@hapi/hoek" "^9.0.0" + +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + dependencies: + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.3" + +"@hutson/parse-repository-url@^3.0.0": + version "3.0.2" + +"@iarna/toml@2.2.5": + version "2.2.5" + +"@isaacs/ttlcache@^1.4.1": + version "1.4.1" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + +"@jest/console@^29.7.0": + version "29.7.0" + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/create-cache-key-function@^29.6.3": + version "29.7.0" + dependencies: + "@jest/types" "^29.6.3" + +"@jest/environment@^29.7.0": + version "29.7.0" + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^26.6.2": + version "26.6.2" + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + +"@jest/types@^29.6.3": + version "29.6.3" + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + +"@jridgewell/source-map@^0.3.3": + version "0.3.6" + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": + version "5.1.1-v1" + dependencies: + eslint-scope "5.1.1" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": + version "2.0.5" + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@octokit/auth-token@^3.0.0": + version "3.0.4" + +"@octokit/core@^4.2.1", "@octokit/core@>=3", "@octokit/core@>=4": + version "4.2.4" + dependencies: + "@octokit/auth-token" "^3.0.0" + "@octokit/graphql" "^5.0.0" + "@octokit/request" "^6.0.0" + "@octokit/request-error" "^3.0.0" + "@octokit/types" "^9.0.0" + before-after-hook "^2.2.0" + universal-user-agent "^6.0.0" + +"@octokit/endpoint@^7.0.0": + version "7.0.6" + dependencies: + "@octokit/types" "^9.0.0" + is-plain-object "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/graphql@^5.0.0": + version "5.0.6" + dependencies: + "@octokit/request" "^6.0.0" + "@octokit/types" "^9.0.0" + universal-user-agent "^6.0.0" + +"@octokit/openapi-types@^18.0.0": + version "18.1.1" + +"@octokit/plugin-paginate-rest@^6.1.2": + version "6.1.2" + dependencies: + "@octokit/tsconfig" "^1.0.2" + "@octokit/types" "^9.2.3" + +"@octokit/plugin-request-log@^1.0.4": + version "1.0.4" + +"@octokit/plugin-rest-endpoint-methods@^7.1.2": + version "7.2.3" + dependencies: + "@octokit/types" "^10.0.0" + +"@octokit/request-error@^3.0.0": + version "3.0.3" + dependencies: + "@octokit/types" "^9.0.0" + deprecation "^2.0.0" + once "^1.4.0" + +"@octokit/request@^6.0.0": + version "6.2.8" + dependencies: + "@octokit/endpoint" "^7.0.0" + "@octokit/request-error" "^3.0.0" + "@octokit/types" "^9.0.0" + is-plain-object "^5.0.0" + node-fetch "^2.6.7" + universal-user-agent "^6.0.0" + +"@octokit/rest@19.0.11": + version "19.0.11" + dependencies: + "@octokit/core" "^4.2.1" + "@octokit/plugin-paginate-rest" "^6.1.2" + "@octokit/plugin-request-log" "^1.0.4" + "@octokit/plugin-rest-endpoint-methods" "^7.1.2" + +"@octokit/tsconfig@^1.0.2": + version "1.0.2" + +"@octokit/types@^10.0.0": + version "10.0.0" + dependencies: + "@octokit/openapi-types" "^18.0.0" + +"@octokit/types@^9.0.0", "@octokit/types@^9.2.3": + version "9.3.2" + dependencies: + "@octokit/openapi-types" "^18.0.0" + +"@pkgr/core@^0.1.0": + version "0.1.1" + +"@pnpm/config.env-replace@^1.1.0": + version "1.1.0" + +"@pnpm/network.ca-file@^1.0.1": + version "1.0.2" + dependencies: + graceful-fs "4.2.10" + +"@pnpm/npm-conf@^2.1.0": + version "2.3.1" + dependencies: + "@pnpm/config.env-replace" "^1.1.0" + "@pnpm/network.ca-file" "^1.0.1" + config-chain "^1.1.11" + +"@react-native-community/cli-clean@14.0.0": + version "14.0.0" + dependencies: + "@react-native-community/cli-tools" "14.0.0" + chalk "^4.1.2" + execa "^5.0.0" + fast-glob "^3.3.2" + +"@react-native-community/cli-config@14.0.0": + version "14.0.0" + dependencies: + "@react-native-community/cli-tools" "14.0.0" + chalk "^4.1.2" + cosmiconfig "^9.0.0" + deepmerge "^4.3.0" + fast-glob "^3.3.2" + joi "^17.2.1" + +"@react-native-community/cli-debugger-ui@14.0.0-alpha.11": + version "14.0.0-alpha.11" + dependencies: + serve-static "^1.13.1" + +"@react-native-community/cli-debugger-ui@14.0.0": + version "14.0.0" + dependencies: + serve-static "^1.13.1" + +"@react-native-community/cli-doctor@14.0.0": + version "14.0.0" + dependencies: + "@react-native-community/cli-config" "14.0.0" + "@react-native-community/cli-platform-android" "14.0.0" + "@react-native-community/cli-platform-apple" "14.0.0" + "@react-native-community/cli-platform-ios" "14.0.0" + "@react-native-community/cli-tools" "14.0.0" + chalk "^4.1.2" + command-exists "^1.2.8" + deepmerge "^4.3.0" + envinfo "^7.13.0" + execa "^5.0.0" + node-stream-zip "^1.9.1" + ora "^5.4.1" + semver "^7.5.2" + strip-ansi "^5.2.0" + wcwidth "^1.0.1" + yaml "^2.2.1" + +"@react-native-community/cli-platform-android@14.0.0": + version "14.0.0" + dependencies: + "@react-native-community/cli-tools" "14.0.0" + chalk "^4.1.2" + execa "^5.0.0" + fast-glob "^3.3.2" + fast-xml-parser "^4.2.4" + logkitty "^0.7.1" + +"@react-native-community/cli-platform-apple@14.0.0": + version "14.0.0" + dependencies: + "@react-native-community/cli-tools" "14.0.0" + chalk "^4.1.2" + execa "^5.0.0" + fast-glob "^3.3.2" + fast-xml-parser "^4.2.4" + ora "^5.4.1" + +"@react-native-community/cli-platform-ios@14.0.0": + version "14.0.0" + dependencies: + "@react-native-community/cli-platform-apple" "14.0.0" + +"@react-native-community/cli-server-api@14.0.0-alpha.11": + version "14.0.0-alpha.11" + dependencies: + "@react-native-community/cli-debugger-ui" "14.0.0-alpha.11" + "@react-native-community/cli-tools" "14.0.0-alpha.11" + compression "^1.7.1" + connect "^3.6.5" + errorhandler "^1.5.1" + nocache "^3.0.1" + pretty-format "^26.6.2" + serve-static "^1.13.1" + ws "^6.2.3" + +"@react-native-community/cli-server-api@14.0.0": + version "14.0.0" + dependencies: + "@react-native-community/cli-debugger-ui" "14.0.0" + "@react-native-community/cli-tools" "14.0.0" + compression "^1.7.1" + connect "^3.6.5" + errorhandler "^1.5.1" + nocache "^3.0.1" + pretty-format "^26.6.2" + serve-static "^1.13.1" + ws "^6.2.3" + +"@react-native-community/cli-tools@14.0.0-alpha.11": + version "14.0.0-alpha.11" + dependencies: + appdirsjs "^1.2.4" + chalk "^4.1.2" + execa "^5.0.0" + find-up "^5.0.0" + mime "^2.4.1" + open "^6.2.0" + ora "^5.4.1" + semver "^7.5.2" + shell-quote "^1.7.3" + sudo-prompt "^9.0.0" + +"@react-native-community/cli-tools@14.0.0": + version "14.0.0" + dependencies: + appdirsjs "^1.2.4" + chalk "^4.1.2" + execa "^5.0.0" + find-up "^5.0.0" + mime "^2.4.1" + open "^6.2.0" + ora "^5.4.1" + semver "^7.5.2" + shell-quote "^1.7.3" + sudo-prompt "^9.0.0" + +"@react-native-community/cli-types@14.0.0": + version "14.0.0" + dependencies: + joi "^17.2.1" + +"@react-native-community/cli@14.0.0": + version "14.0.0" + dependencies: + "@react-native-community/cli-clean" "14.0.0" + "@react-native-community/cli-config" "14.0.0" + "@react-native-community/cli-debugger-ui" "14.0.0" + "@react-native-community/cli-doctor" "14.0.0" + "@react-native-community/cli-server-api" "14.0.0" + "@react-native-community/cli-tools" "14.0.0" + "@react-native-community/cli-types" "14.0.0" + chalk "^4.1.2" + commander "^9.4.1" + deepmerge "^4.3.0" + execa "^5.0.0" + find-up "^5.0.0" + fs-extra "^8.1.0" + graceful-fs "^4.1.3" + prompts "^2.4.2" + semver "^7.5.2" + +"@react-native/assets-registry@0.75.2": + version "0.75.2" + +"@react-native/babel-plugin-codegen@0.75.2": + version "0.75.2" + dependencies: + "@react-native/codegen" "0.75.2" + +"@react-native/babel-preset@0.75.2": + version "0.75.2" + dependencies: + "@babel/core" "^7.20.0" + "@babel/plugin-proposal-export-default-from" "^7.0.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-export-default-from" "^7.0.0" + "@babel/plugin-syntax-flow" "^7.18.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.0.0" + "@babel/plugin-syntax-optional-chaining" "^7.0.0" + "@babel/plugin-transform-arrow-functions" "^7.0.0" + "@babel/plugin-transform-async-generator-functions" "^7.24.3" + "@babel/plugin-transform-async-to-generator" "^7.20.0" + "@babel/plugin-transform-block-scoping" "^7.0.0" + "@babel/plugin-transform-class-properties" "^7.24.1" + "@babel/plugin-transform-classes" "^7.0.0" + "@babel/plugin-transform-computed-properties" "^7.0.0" + "@babel/plugin-transform-destructuring" "^7.20.0" + "@babel/plugin-transform-flow-strip-types" "^7.20.0" + "@babel/plugin-transform-for-of" "^7.0.0" + "@babel/plugin-transform-function-name" "^7.0.0" + "@babel/plugin-transform-literals" "^7.0.0" + "@babel/plugin-transform-logical-assignment-operators" "^7.24.1" + "@babel/plugin-transform-modules-commonjs" "^7.0.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.0.0" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.1" + "@babel/plugin-transform-numeric-separator" "^7.24.1" + "@babel/plugin-transform-object-rest-spread" "^7.24.5" + "@babel/plugin-transform-optional-catch-binding" "^7.24.1" + "@babel/plugin-transform-optional-chaining" "^7.24.5" + "@babel/plugin-transform-parameters" "^7.0.0" + "@babel/plugin-transform-private-methods" "^7.22.5" + "@babel/plugin-transform-private-property-in-object" "^7.22.11" + "@babel/plugin-transform-react-display-name" "^7.0.0" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/plugin-transform-react-jsx-self" "^7.0.0" + "@babel/plugin-transform-react-jsx-source" "^7.0.0" + "@babel/plugin-transform-regenerator" "^7.20.0" + "@babel/plugin-transform-runtime" "^7.0.0" + "@babel/plugin-transform-shorthand-properties" "^7.0.0" + "@babel/plugin-transform-spread" "^7.0.0" + "@babel/plugin-transform-sticky-regex" "^7.0.0" + "@babel/plugin-transform-typescript" "^7.5.0" + "@babel/plugin-transform-unicode-regex" "^7.0.0" + "@babel/template" "^7.0.0" + "@react-native/babel-plugin-codegen" "0.75.2" + babel-plugin-transform-flow-enums "^0.0.2" + react-refresh "^0.14.0" + +"@react-native/codegen@0.75.2": + version "0.75.2" + dependencies: + "@babel/parser" "^7.20.0" + glob "^7.1.1" + hermes-parser "0.22.0" + invariant "^2.2.4" + jscodeshift "^0.14.0" + mkdirp "^0.5.1" + nullthrows "^1.1.1" + yargs "^17.6.2" + +"@react-native/community-cli-plugin@0.75.2": + version "0.75.2" + dependencies: + "@react-native-community/cli-server-api" "14.0.0-alpha.11" + "@react-native-community/cli-tools" "14.0.0-alpha.11" + "@react-native/dev-middleware" "0.75.2" + "@react-native/metro-babel-transformer" "0.75.2" + chalk "^4.0.0" + execa "^5.1.1" + metro "^0.80.3" + metro-config "^0.80.3" + metro-core "^0.80.3" + node-fetch "^2.2.0" + querystring "^0.2.1" + readline "^1.3.0" + +"@react-native/debugger-frontend@0.75.2": + version "0.75.2" + +"@react-native/dev-middleware@0.75.2": + version "0.75.2" + dependencies: + "@isaacs/ttlcache" "^1.4.1" + "@react-native/debugger-frontend" "0.75.2" + chrome-launcher "^0.15.2" + chromium-edge-launcher "^0.2.0" + connect "^3.6.5" + debug "^2.2.0" + node-fetch "^2.2.0" + nullthrows "^1.1.1" + open "^7.0.3" + selfsigned "^2.4.1" + serve-static "^1.13.1" + ws "^6.2.2" + +"@react-native/eslint-config@^0.73.1": + version "0.73.2" + dependencies: + "@babel/core" "^7.20.0" + "@babel/eslint-parser" "^7.20.0" + "@react-native/eslint-plugin" "0.73.1" + "@typescript-eslint/eslint-plugin" "^5.57.1" + "@typescript-eslint/parser" "^5.57.1" + eslint-config-prettier "^8.5.0" + eslint-plugin-eslint-comments "^3.2.0" + eslint-plugin-ft-flow "^2.0.1" + eslint-plugin-jest "^26.5.3" + eslint-plugin-prettier "^4.2.1" + eslint-plugin-react "^7.30.1" + eslint-plugin-react-hooks "^4.6.0" + eslint-plugin-react-native "^4.0.0" + +"@react-native/eslint-plugin@0.73.1": + version "0.73.1" + +"@react-native/gradle-plugin@0.75.2": + version "0.75.2" + +"@react-native/js-polyfills@0.75.2": + version "0.75.2" + +"@react-native/metro-babel-transformer@0.75.2": + version "0.75.2" + dependencies: + "@babel/core" "^7.20.0" + "@react-native/babel-preset" "0.75.2" + hermes-parser "0.22.0" + nullthrows "^1.1.1" + +"@react-native/metro-config@0.75.2": + version "0.75.2" + dependencies: + "@react-native/js-polyfills" "0.75.2" + "@react-native/metro-babel-transformer" "0.75.2" + metro-config "^0.80.3" + metro-runtime "^0.80.3" + +"@react-native/normalize-colors@0.75.2": + version "0.75.2" + +"@react-native/typescript-config@0.75.2": + version "0.75.2" + +"@react-native/virtualized-lists@0.75.2": + version "0.75.2" + dependencies: + invariant "^2.2.4" + nullthrows "^1.1.1" + +"@release-it/conventional-changelog@^5.0.0": + version "5.1.1" + dependencies: + concat-stream "^2.0.0" + conventional-changelog "^3.1.25" + conventional-recommended-bump "^6.1.0" + semver "7.3.8" + +"@sideway/address@^4.1.5": + version "4.1.5" + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + +"@sindresorhus/is@^5.2.0": + version "5.6.0" + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@synonymdev/result@^0.0.2": + version "0.0.2" + resolved "https://registry.npmjs.org/@synonymdev/result/-/result-0.0.2.tgz" + integrity sha512-Ni5qknulcf350qfPVTw3DWXqT2i6K68BoFc18zlqIAj9YA2RUOIJsKTdemX31i3wSma5LRHVcGLESVga16iAag== + +"@szmarczak/http-timer@^5.0.1": + version "5.0.1" + dependencies: + defer-to-connect "^2.0.1" + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + +"@types/babel__core@^7.1.14": + version "7.20.5" + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.6" + dependencies: + "@babel/types" "^7.20.7" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + dependencies: + "@types/node" "*" + +"@types/http-cache-semantics@^4.0.2": + version "4.0.4" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + +"@types/istanbul-lib-report@*": + version "3.0.3" + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.5.5": + version "29.5.12" + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/json-schema@^7.0.9": + version "7.0.15" + +"@types/minimist@^1.2.0", "@types/minimist@^1.2.2": + version "1.2.5" + +"@types/node-forge@^1.3.0": + version "1.3.11" + dependencies: + "@types/node" "*" + +"@types/node@*", "@types/node@20.5.1": + version "20.5.1" + +"@types/normalize-package-data@^2.4.0": + version "2.4.4" + +"@types/prop-types@*": + version "15.7.12" + +"@types/react@^18.2.44", "@types/react@^18.2.6": + version "18.3.5" + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + +"@types/semver@^7.3.12": + version "7.5.8" + +"@types/stack-utils@^2.0.0": + version "2.0.3" + +"@types/yargs-parser@*": + version "21.0.3" + +"@types/yargs@^15.0.0": + version "15.0.19" + dependencies: + "@types/yargs-parser" "*" + +"@types/yargs@^17.0.8": + version "17.0.33" + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^5.0.0", "@typescript-eslint/eslint-plugin@^5.57.1": + version "5.62.0" + dependencies: + "@eslint-community/regexpp" "^4.4.0" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/type-utils" "5.62.0" + "@typescript-eslint/utils" "5.62.0" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.0" + natural-compare-lite "^1.4.0" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/parser@^5.0.0", "@typescript-eslint/parser@^5.57.1": + version "5.62.0" + dependencies: + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.62.0": + version "5.62.0" + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + +"@typescript-eslint/type-utils@5.62.0": + version "5.62.0" + dependencies: + "@typescript-eslint/typescript-estree" "5.62.0" + "@typescript-eslint/utils" "5.62.0" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.62.0": + version "5.62.0" + +"@typescript-eslint/typescript-estree@5.62.0": + version "5.62.0" + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@^5.10.0", "@typescript-eslint/utils@5.62.0": + version "5.62.0" + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" + eslint-scope "^5.1.1" + semver "^7.3.7" + +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + dependencies: + "@typescript-eslint/types" "5.62.0" + eslint-visitor-keys "^3.3.0" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + +abort-controller@^3.0.0: + version "3.0.0" + dependencies: + event-target-shim "^5.0.0" + +accepts@^1.3.7, accepts@~1.3.5, accepts@~1.3.7: + version "1.3.8" + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-jsx@^5.3.2: + version "5.3.2" + +acorn-walk@^8.1.1, acorn-walk@^8.2.0: + version "8.3.3" + dependencies: + acorn "^8.11.0" + +"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.11.0, acorn@^8.4.1, acorn@^8.7.0, acorn@^8.8.2, acorn@^8.9.0: + version "8.12.1" + +add-stream@^1.0.0: + version "1.0.0" + +agent-base@^7.0.2, agent-base@^7.1.0, agent-base@^7.1.1: + version "7.1.1" + dependencies: + debug "^4.3.4" + +aggregate-error@^3.0.0: + version "3.1.0" + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +aggregate-error@^4.0.0: + version "4.0.1" + dependencies: + clean-stack "^4.0.0" + indent-string "^5.0.0" + +ajv@^6.12.4: + version "6.12.6" + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.11.0: + version "8.17.1" + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +anser@^1.4.9: + version "1.4.10" + +ansi-align@^3.0.1: + version "3.0.1" + dependencies: + string-width "^4.1.0" + +ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: + version "4.3.2" + dependencies: + type-fest "^0.21.3" + +ansi-fragments@^0.2.1: + version "0.2.1" + dependencies: + colorette "^1.0.7" + slice-ansi "^2.0.0" + strip-ansi "^5.0.0" + +ansi-regex@^4.1.0: + version "4.1.1" + +ansi-regex@^5.0.0, ansi-regex@^5.0.1: + version "5.0.1" + +ansi-regex@^6.0.1: + version "6.0.1" + +ansi-styles@^3.2.0: + version "3.2.1" + dependencies: + color-convert "^1.9.0" + +ansi-styles@^3.2.1: + version "3.2.1" + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + +ansi-styles@^6.1.0: + version "6.2.1" + +anymatch@^3.0.3: + version "3.1.3" + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +appdirsjs@^1.2.4: + version "1.2.7" + +arg@^4.1.0: + version "4.1.3" + +argparse@^1.0.7: + version "1.0.10" + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + +array-buffer-byte-length@^1.0.1: + version "1.0.1" + dependencies: + call-bind "^1.0.5" + is-array-buffer "^3.0.4" + +array-ify@^1.0.0: + version "1.0.0" + +array-includes@^3.1.6, array-includes@^3.1.8: + version "3.1.8" + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + is-string "^1.0.7" + +array-union@^2.1.0: + version "2.1.0" + +array.prototype.findlast@^1.2.5: + version "1.2.5" + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + +array.prototype.flat@^1.3.1: + version "1.3.2" + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.2: + version "1.3.2" + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.map@^1.0.5: + version "1.0.7" + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-array-method-boxes-properly "^1.0.0" + es-object-atoms "^1.0.0" + is-string "^1.0.7" + +array.prototype.tosorted@^1.1.4: + version "1.1.4" + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + es-shim-unscopables "^1.0.2" + +arraybuffer.prototype.slice@^1.0.3: + version "1.0.3" + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.2.1" + get-intrinsic "^1.2.3" + is-array-buffer "^3.0.4" + is-shared-array-buffer "^1.0.2" + +arrify@^1.0.1: + version "1.0.1" + +asap@~2.0.6: + version "2.0.6" + +ast-types@^0.13.4: + version "0.13.4" + dependencies: + tslib "^2.0.1" + +ast-types@0.15.2: + version "0.15.2" + dependencies: + tslib "^2.0.1" + +astral-regex@^1.0.0: + version "1.0.0" + +async-limiter@~1.0.0: + version "1.0.1" + +async-retry@1.3.3: + version "1.3.3" + dependencies: + retry "0.13.1" + +available-typed-arrays@^1.0.7: + version "1.0.7" + dependencies: + possible-typed-array-names "^1.0.0" + +babel-core@^7.0.0-bridge.0: + version "7.0.0-bridge.0" + +babel-jest@^29.7.0: + version "29.7.0" + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-plugin-module-resolver@^5.0.2: + version "5.0.2" + dependencies: + find-babel-config "^2.1.1" + glob "^9.3.3" + pkg-up "^3.1.0" + reselect "^4.1.7" + resolve "^1.22.8" + +babel-plugin-polyfill-corejs2@^0.4.10: + version "0.4.11" + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.6.2" + semver "^6.3.1" + +babel-plugin-polyfill-corejs3@^0.10.6: + version "0.10.6" + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.2" + core-js-compat "^3.38.0" + +babel-plugin-polyfill-regenerator@^0.6.1: + version "0.6.2" + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.2" + +babel-plugin-transform-flow-enums@^0.0.2: + version "0.0.2" + dependencies: + "@babel/plugin-syntax-flow" "^7.12.1" + +babel-preset-current-node-syntax@^1.0.0: + version "1.1.0" + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^29.6.3: + version "29.6.3" + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + +base64-js@^1.3.1, base64-js@^1.5.1: + version "1.5.1" + +basic-ftp@^5.0.2: + version "5.0.5" + +before-after-hook@^2.2.0: + version "2.2.3" + +big-integer@^1.6.44: + version "1.6.52" + +bl@^4.1.0: + version "4.1.0" + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +bl@^5.0.0: + version "5.1.0" + dependencies: + buffer "^6.0.3" + inherits "^2.0.4" + readable-stream "^3.4.0" + +boxen@^7.0.0: + version "7.1.1" + dependencies: + ansi-align "^3.0.1" + camelcase "^7.0.1" + chalk "^5.2.0" + cli-boxes "^3.0.0" + string-width "^5.1.2" + type-fest "^2.13.0" + widest-line "^4.0.1" + wrap-ansi "^8.1.0" + +bplist-parser@^0.2.0: + version "0.2.0" + dependencies: + big-integer "^1.6.44" + +brace-expansion@^1.1.7: + version "1.1.11" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + dependencies: + fill-range "^7.1.1" + +browserslist@^4.20.4, browserslist@^4.23.1, browserslist@^4.23.3, "browserslist@>= 4.21.0": + version "4.23.3" + dependencies: + caniuse-lite "^1.0.30001646" + electron-to-chromium "^1.5.4" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" + +bser@2.1.1: + version "2.1.1" + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + +buffer@^5.5.0: + version "5.7.1" + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +buffer@^6.0.3: + version "6.0.3" + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +bundle-name@^3.0.0: + version "3.0.0" + dependencies: + run-applescript "^5.0.0" + +bytes@3.0.0: + version "3.0.0" + +cacheable-lookup@^7.0.0: + version "7.0.0" + +cacheable-request@^10.2.8: + version "10.2.14" + dependencies: + "@types/http-cache-semantics" "^4.0.2" + get-stream "^6.0.1" + http-cache-semantics "^4.1.1" + keyv "^4.5.3" + mimic-response "^4.0.0" + normalize-url "^8.0.0" + responselike "^3.0.0" + +call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: + version "1.0.7" + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + +caller-callsite@^2.0.0: + version "2.0.0" + dependencies: + callsites "^2.0.0" + +caller-path@^2.0.0: + version "2.0.0" + dependencies: + caller-callsite "^2.0.0" + +callsites@^2.0.0: + version "2.0.0" + +callsites@^3.0.0: + version "3.1.0" + +camelcase-keys@^6.2.2: + version "6.2.2" + dependencies: + camelcase "^5.3.1" + map-obj "^4.0.0" + quick-lru "^4.0.1" + +camelcase-keys@^7.0.0: + version "7.0.2" + dependencies: + camelcase "^6.3.0" + map-obj "^4.1.0" + quick-lru "^5.1.1" + type-fest "^1.2.1" + +camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + +camelcase@^6.2.0: + version "6.3.0" + +camelcase@^6.3.0: + version "6.3.0" + +camelcase@^7.0.1: + version "7.0.1" + +caniuse-lite@^1.0.30001646: + version "1.0.30001658" + +chalk@^2.4.2: + version "2.4.2" + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^5.0.0: + version "5.3.0" + +chalk@^5.0.1: + version "5.3.0" + +chalk@^5.2.0: + version "5.3.0" + +chalk@5.2.0: + version "5.2.0" + +char-regex@^1.0.2: + version "1.0.2" + +chardet@^0.7.0: + version "0.7.0" + +chrome-launcher@^0.15.2: + version "0.15.2" + dependencies: + "@types/node" "*" + escape-string-regexp "^4.0.0" + is-wsl "^2.2.0" + lighthouse-logger "^1.0.0" + +chromium-edge-launcher@^0.2.0: + version "0.2.0" + dependencies: + "@types/node" "*" + escape-string-regexp "^4.0.0" + is-wsl "^2.2.0" + lighthouse-logger "^1.0.0" + mkdirp "^1.0.4" + rimraf "^3.0.2" + +ci-info@^2.0.0: + version "2.0.0" + +ci-info@^3.2.0: + version "3.9.0" + +cjs-module-lexer@^1.0.0: + version "1.4.1" + +clean-stack@^2.0.0: + version "2.2.0" + +clean-stack@^4.0.0: + version "4.2.0" + dependencies: + escape-string-regexp "5.0.0" + +cli-boxes@^3.0.0: + version "3.0.0" + +cli-cursor@^3.1.0: + version "3.1.0" + dependencies: + restore-cursor "^3.1.0" + +cli-cursor@^4.0.0: + version "4.0.0" + dependencies: + restore-cursor "^4.0.0" + +cli-spinners@^2.5.0, cli-spinners@^2.6.1: + version "2.9.2" + +cli-width@^4.0.0: + version "4.1.0" + +cliui@^6.0.0: + version "6.0.0" + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + +cliui@^7.0.2: + version "7.0.4" + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +cliui@^8.0.1: + version "8.0.1" + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +clone-deep@^4.0.1: + version "4.0.1" + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +clone@^1.0.2: + version "1.0.4" + +co@^4.6.0: + version "4.6.0" + +collect-v8-coverage@^1.0.0: + version "1.0.2" + +color-convert@^1.9.0: + version "1.9.3" + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + +color-name@1.1.3: + version "1.1.3" + +colorette@^1.0.7: + version "1.4.0" + +command-exists@^1.2.8: + version "1.2.9" + +commander@^2.20.0: + version "2.20.3" + +commander@^9.4.1: + version "9.5.0" + +commitlint@^17.0.2: + version "17.8.1" + dependencies: + "@commitlint/cli" "^17.8.1" + "@commitlint/types" "^17.8.1" + +commondir@^1.0.1: + version "1.0.1" + +compare-func@^2.0.0: + version "2.0.0" + dependencies: + array-ify "^1.0.0" + dot-prop "^5.1.0" + +compressible@~2.0.16: + version "2.0.18" + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.1: + version "1.7.4" + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + +concat-stream@^2.0.0: + version "2.0.0" + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.0.2" + typedarray "^0.0.6" + +config-chain@^1.1.11: + version "1.1.13" + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + +configstore@^6.0.0: + version "6.0.0" + dependencies: + dot-prop "^6.0.1" + graceful-fs "^4.2.6" + unique-string "^3.0.0" + write-file-atomic "^3.0.3" + xdg-basedir "^5.0.1" + +connect@^3.6.5: + version "3.7.0" + dependencies: + debug "2.6.9" + finalhandler "1.1.2" + parseurl "~1.3.3" + utils-merge "1.0.1" + +conventional-changelog-angular@^5.0.12: + version "5.0.13" + dependencies: + compare-func "^2.0.0" + q "^1.5.1" + +conventional-changelog-angular@^6.0.0: + version "6.0.0" + dependencies: + compare-func "^2.0.0" + +conventional-changelog-atom@^2.0.8: + version "2.0.8" + dependencies: + q "^1.5.1" + +conventional-changelog-codemirror@^2.0.8: + version "2.0.8" + dependencies: + q "^1.5.1" + +conventional-changelog-conventionalcommits@^4.5.0: + version "4.6.3" + dependencies: + compare-func "^2.0.0" + lodash "^4.17.15" + q "^1.5.1" + +conventional-changelog-conventionalcommits@^6.1.0: + version "6.1.0" + dependencies: + compare-func "^2.0.0" + +conventional-changelog-core@^4.2.1: + version "4.2.4" + dependencies: + add-stream "^1.0.0" + conventional-changelog-writer "^5.0.0" + conventional-commits-parser "^3.2.0" + dateformat "^3.0.0" + get-pkg-repo "^4.0.0" + git-raw-commits "^2.0.8" + git-remote-origin-url "^2.0.0" + git-semver-tags "^4.1.1" + lodash "^4.17.15" + normalize-package-data "^3.0.0" + q "^1.5.1" + read-pkg "^3.0.0" + read-pkg-up "^3.0.0" + through2 "^4.0.0" + +conventional-changelog-ember@^2.0.9: + version "2.0.9" + dependencies: + q "^1.5.1" + +conventional-changelog-eslint@^3.0.9: + version "3.0.9" + dependencies: + q "^1.5.1" + +conventional-changelog-express@^2.0.6: + version "2.0.6" + dependencies: + q "^1.5.1" + +conventional-changelog-jquery@^3.0.11: + version "3.0.11" + dependencies: + q "^1.5.1" + +conventional-changelog-jshint@^2.0.9: + version "2.0.9" + dependencies: + compare-func "^2.0.0" + q "^1.5.1" + +conventional-changelog-preset-loader@^2.3.4: + version "2.3.4" + +conventional-changelog-writer@^5.0.0: + version "5.0.1" + dependencies: + conventional-commits-filter "^2.0.7" + dateformat "^3.0.0" + handlebars "^4.7.7" + json-stringify-safe "^5.0.1" + lodash "^4.17.15" + meow "^8.0.0" + semver "^6.0.0" + split "^1.0.0" + through2 "^4.0.0" + +conventional-changelog@^3.1.25: + version "3.1.25" + dependencies: + conventional-changelog-angular "^5.0.12" + conventional-changelog-atom "^2.0.8" + conventional-changelog-codemirror "^2.0.8" + conventional-changelog-conventionalcommits "^4.5.0" + conventional-changelog-core "^4.2.1" + conventional-changelog-ember "^2.0.9" + conventional-changelog-eslint "^3.0.9" + conventional-changelog-express "^2.0.6" + conventional-changelog-jquery "^3.0.11" + conventional-changelog-jshint "^2.0.9" + conventional-changelog-preset-loader "^2.3.4" + +conventional-commits-filter@^2.0.7: + version "2.0.7" + dependencies: + lodash.ismatch "^4.4.0" + modify-values "^1.0.0" + +conventional-commits-parser@^3.2.0: + version "3.2.4" + dependencies: + is-text-path "^1.0.1" + JSONStream "^1.0.4" + lodash "^4.17.15" + meow "^8.0.0" + split2 "^3.0.0" + through2 "^4.0.0" + +conventional-commits-parser@^4.0.0: + version "4.0.0" + dependencies: + is-text-path "^1.0.1" + JSONStream "^1.3.5" + meow "^8.1.2" + split2 "^3.2.2" + +conventional-recommended-bump@^6.1.0: + version "6.1.0" + dependencies: + concat-stream "^2.0.0" + conventional-changelog-preset-loader "^2.3.4" + conventional-commits-filter "^2.0.7" + conventional-commits-parser "^3.2.0" + git-raw-commits "^2.0.8" + git-semver-tags "^4.1.1" + meow "^8.0.0" + q "^1.5.1" + +convert-source-map@^2.0.0: + version "2.0.0" + +core-js-compat@^3.37.1, core-js-compat@^3.38.0: + version "3.38.1" + dependencies: + browserslist "^4.23.3" + +core-util-is@~1.0.0: + version "1.0.3" + +cosmiconfig-typescript-loader@^4.0.0: + version "4.4.0" + +cosmiconfig@^5.0.5: + version "5.2.1" + dependencies: + import-fresh "^2.0.0" + is-directory "^0.3.1" + js-yaml "^3.13.1" + parse-json "^4.0.0" + +cosmiconfig@^8.0.0, cosmiconfig@>=7: + version "8.3.6" + dependencies: + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + path-type "^4.0.0" + +cosmiconfig@^9.0.0: + version "9.0.0" + dependencies: + env-paths "^2.2.1" + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + +cosmiconfig@8.1.3: + version "8.1.3" + dependencies: + import-fresh "^3.2.1" + js-yaml "^4.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + +create-jest@^29.7.0: + version "29.7.0" + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +create-require@^1.1.0: + version "1.1.1" + +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-random-string@^4.0.0: + version "4.0.0" + dependencies: + type-fest "^1.0.1" + +csstype@^3.0.2: + version "3.1.3" + +dargs@^7.0.0: + version "7.0.0" + +data-uri-to-buffer@^4.0.0: + version "4.0.1" + +data-uri-to-buffer@^6.0.2: + version "6.0.2" + +data-view-buffer@^1.0.1: + version "1.0.1" + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-length@^1.0.1: + version "1.0.1" + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-offset@^1.0.0: + version "1.0.0" + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +dateformat@^3.0.0: + version "3.0.3" + +dayjs@^1.8.15: + version "1.11.13" + +debug@^2.2.0, debug@^2.6.9, debug@2.6.9: + version "2.6.9" + dependencies: + ms "2.0.0" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@4: + version "4.3.7" + dependencies: + ms "^2.1.3" + +decamelize-keys@^1.1.0: + version "1.1.1" + dependencies: + decamelize "^1.1.0" + map-obj "^1.0.0" + +decamelize@^1.1.0, decamelize@^1.2.0: + version "1.2.0" + +decamelize@^5.0.0: + version "5.0.1" + +decompress-response@^6.0.0: + version "6.0.0" + dependencies: + mimic-response "^3.1.0" + +dedent@^0.7.0: + version "0.7.0" + +dedent@^1.0.0: + version "1.5.3" + +deep-extend@^0.6.0: + version "0.6.0" + +deep-is@^0.1.3, deep-is@~0.1.3: + version "0.1.4" + +deepmerge@^4.2.2, deepmerge@^4.3.0: + version "4.3.1" + +default-browser-id@^3.0.0: + version "3.0.0" + dependencies: + bplist-parser "^0.2.0" + untildify "^4.0.0" + +default-browser@^4.0.0: + version "4.0.0" + dependencies: + bundle-name "^3.0.0" + default-browser-id "^3.0.0" + execa "^7.1.1" + titleize "^3.0.0" + +defaults@^1.0.3: + version "1.0.4" + dependencies: + clone "^1.0.2" + +defer-to-connect@^2.0.1: + version "2.0.1" + +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-lazy-prop@^3.0.0: + version "3.0.0" + +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0, define-properties@^1.2.1: + version "1.2.1" + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +degenerator@^4.0.4: + version "4.0.4" + dependencies: + ast-types "^0.13.4" + escodegen "^1.14.3" + esprima "^4.0.1" + vm2 "^3.9.19" + +del-cli@^5.1.0: + version "5.1.0" + dependencies: + del "^7.1.0" + meow "^10.1.3" + +del@^6.1.1: + version "6.1.1" + dependencies: + globby "^11.0.1" + graceful-fs "^4.2.4" + is-glob "^4.0.1" + is-path-cwd "^2.2.0" + is-path-inside "^3.0.2" + p-map "^4.0.0" + rimraf "^3.0.2" + slash "^3.0.0" + +del@^7.1.0: + version "7.1.0" + dependencies: + globby "^13.1.2" + graceful-fs "^4.2.10" + is-glob "^4.0.3" + is-path-cwd "^3.0.0" + is-path-inside "^4.0.0" + p-map "^5.5.0" + rimraf "^3.0.2" + slash "^4.0.0" + +denodeify@^1.2.1: + version "1.2.1" + +depd@2.0.0: + version "2.0.0" + +deprecation@^2.0.0: + version "2.3.1" + +destroy@1.2.0: + version "1.2.0" + +detect-newline@^3.0.0: + version "3.1.0" + +diff-sequences@^29.6.3: + version "29.6.3" + +diff@^4.0.1: + version "4.0.2" + +dir-glob@^3.0.1: + version "3.0.1" + dependencies: + path-type "^4.0.0" + +doctrine@^2.1.0: + version "2.1.0" + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + dependencies: + esutils "^2.0.2" + +dot-prop@^5.1.0: + version "5.3.0" + dependencies: + is-obj "^2.0.0" + +dot-prop@^6.0.1: + version "6.0.1" + dependencies: + is-obj "^2.0.0" + +eastasianwidth@^0.2.0: + version "0.2.0" + +ee-first@1.1.1: + version "1.1.1" + +electron-to-chromium@^1.5.4: + version "1.5.18" + +emittery@^0.13.1: + version "0.13.1" + +emoji-regex@^8.0.0: + version "8.0.0" + +emoji-regex@^9.2.2: + version "9.2.2" + +encodeurl@~1.0.2: + version "1.0.2" + +encoding@^0.1.0: + version "0.1.13" + dependencies: + iconv-lite "^0.6.2" + +end-of-stream@^1.1.0: + version "1.4.4" + dependencies: + once "^1.4.0" + +env-paths@^2.2.1: + version "2.2.1" + +envinfo@^7.13.0: + version "7.13.0" + +error-ex@^1.3.1: + version "1.3.2" + dependencies: + is-arrayish "^0.2.1" + +error-stack-parser@^2.0.6: + version "2.1.4" + dependencies: + stackframe "^1.3.4" + +errorhandler@^1.5.1: + version "1.5.1" + dependencies: + accepts "~1.3.7" + escape-html "~1.0.3" + +es-abstract@^1.17.5, es-abstract@^1.20.4, es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2, es-abstract@^1.23.3: + version "1.23.3" + dependencies: + array-buffer-byte-length "^1.0.1" + arraybuffer.prototype.slice "^1.0.3" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + data-view-buffer "^1.0.1" + data-view-byte-length "^1.0.1" + data-view-byte-offset "^1.0.0" + es-define-property "^1.0.0" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-set-tostringtag "^2.0.3" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.4" + get-symbol-description "^1.0.2" + globalthis "^1.0.3" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + hasown "^2.0.2" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" + is-callable "^1.2.7" + is-data-view "^1.0.1" + is-negative-zero "^2.0.3" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.3" + is-string "^1.0.7" + is-typed-array "^1.1.13" + is-weakref "^1.0.2" + object-inspect "^1.13.1" + object-keys "^1.1.1" + object.assign "^4.1.5" + regexp.prototype.flags "^1.5.2" + safe-array-concat "^1.1.2" + safe-regex-test "^1.0.3" + string.prototype.trim "^1.2.9" + string.prototype.trimend "^1.0.8" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.2" + typed-array-byte-length "^1.0.1" + typed-array-byte-offset "^1.0.2" + typed-array-length "^1.0.6" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.15" + +es-array-method-boxes-properly@^1.0.0: + version "1.0.0" + +es-define-property@^1.0.0: + version "1.0.0" + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.2.1, es-errors@^1.3.0: + version "1.3.0" + +es-get-iterator@^1.0.2: + version "1.1.3" + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + has-symbols "^1.0.3" + is-arguments "^1.1.1" + is-map "^2.0.2" + is-set "^2.0.2" + is-string "^1.0.7" + isarray "^2.0.5" + stop-iteration-iterator "^1.0.0" + +es-iterator-helpers@^1.0.19: + version "1.0.19" + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + es-set-tostringtag "^2.0.3" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + globalthis "^1.0.3" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + internal-slot "^1.0.7" + iterator.prototype "^1.1.2" + safe-array-concat "^1.1.2" + +es-object-atoms@^1.0.0: + version "1.0.0" + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.0.3: + version "2.0.3" + dependencies: + get-intrinsic "^1.2.4" + has-tostringtag "^1.0.2" + hasown "^2.0.1" + +es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: + version "1.0.2" + dependencies: + hasown "^2.0.0" + +es-to-primitive@^1.2.1: + version "1.2.1" + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escalade@^3.1.1, escalade@^3.1.2: + version "3.2.0" + +escape-goat@^4.0.0: + version "4.0.0" + +escape-html@~1.0.3: + version "1.0.3" + +escape-string-regexp@^1.0.5: + version "1.0.5" + +escape-string-regexp@^2.0.0: + version "2.0.0" + +escape-string-regexp@^4.0.0: + version "4.0.0" + +escape-string-regexp@^5.0.0: + version "5.0.0" + +escape-string-regexp@5.0.0: + version "5.0.0" + +escodegen@^1.14.3: + version "1.14.3" + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +eslint-config-prettier@*, eslint-config-prettier@^9.0.0: + version "9.1.0" + +eslint-config-prettier@^8.5.0: + version "8.10.0" + +eslint-plugin-eslint-comments@^3.2.0: + version "3.2.0" + dependencies: + escape-string-regexp "^1.0.5" + ignore "^5.0.5" + +eslint-plugin-ft-flow@^2.0.1: + version "2.0.3" + dependencies: + lodash "^4.17.21" + string-natural-compare "^3.0.1" + +eslint-plugin-jest@^26.5.3: + version "26.9.0" + dependencies: + "@typescript-eslint/utils" "^5.10.0" + +eslint-plugin-prettier@^4.2.1: + version "4.2.1" + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-plugin-prettier@^5.0.1: + version "5.2.1" + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.9.1" + +eslint-plugin-react-hooks@^4.6.0: + version "4.6.2" + +eslint-plugin-react-native-globals@^0.1.1: + version "0.1.2" + +eslint-plugin-react-native@^4.0.0: + version "4.1.0" + dependencies: + eslint-plugin-react-native-globals "^0.1.1" + +eslint-plugin-react@^7.30.1: + version "7.35.2" + dependencies: + array-includes "^3.1.8" + array.prototype.findlast "^1.2.5" + array.prototype.flatmap "^1.3.2" + array.prototype.tosorted "^1.1.4" + doctrine "^2.1.0" + es-iterator-helpers "^1.0.19" + estraverse "^5.3.0" + hasown "^2.0.2" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.1.2" + object.entries "^1.1.8" + object.fromentries "^2.0.8" + object.values "^1.2.0" + prop-types "^15.8.1" + resolve "^2.0.0-next.5" + semver "^6.3.1" + string.prototype.matchall "^4.0.11" + string.prototype.repeat "^1.0.0" + +eslint-scope@^5.1.1, eslint-scope@5.1.1: + version "5.1.1" + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.2.2: + version "7.2.2" + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^2.1.0: + version "2.1.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + +eslint@*, "eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7", "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "eslint@^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8", "eslint@^6.0.0 || ^7.0.0 || ^8.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.5.0 || ^8.0.0 || ^9.0.0", eslint@^8.1.0, eslint@^8.51.0, eslint@>=4.19.1, eslint@>=7.0.0, eslint@>=7.28.0, eslint@>=8, eslint@>=8.0.0: + version "8.57.0" + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: + version "4.0.1" + +esquery@^1.4.2: + version "1.6.0" + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + +estraverse@^4.2.0: + version "4.3.0" + +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: + version "5.3.0" + +esutils@^2.0.2: + version "2.0.3" + +etag@~1.8.1: + version "1.8.1" + +event-target-shim@^5.0.0, event-target-shim@^5.0.1: + version "5.0.1" + +execa@^4.0.3: + version "4.1.0" + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + +execa@^5.0.0, execa@^5.1.1: + version "5.1.1" + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +execa@^7.1.1: + version "7.2.0" + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.1" + human-signals "^4.3.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^3.0.7" + strip-final-newline "^3.0.0" + +execa@7.1.1: + version "7.1.1" + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.1" + human-signals "^4.3.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^3.0.7" + strip-final-newline "^3.0.0" + +exit@^0.1.2: + version "0.1.2" + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +exponential-backoff@^3.1.1: + version "3.1.1" + +external-editor@^3.0.3: + version "3.1.0" + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + +fast-diff@^1.1.2: + version "1.3.0" + +fast-glob@^3.2.11, fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.2: + version "3.3.2" + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: + version "2.0.6" + +fast-uri@^3.0.1: + version "3.0.1" + +fast-xml-parser@^4.2.4: + version "4.5.0" + dependencies: + strnum "^1.0.5" + +fastq@^1.6.0: + version "1.17.1" + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + dependencies: + bser "2.1.1" + +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + +figures@^5.0.0: + version "5.0.0" + dependencies: + escape-string-regexp "^5.0.0" + is-unicode-supported "^1.2.0" + +file-entry-cache@^6.0.1: + version "6.0.1" + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.1.1: + version "7.1.1" + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.1.2: + version "1.1.2" + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +find-babel-config@^2.1.1: + version "2.1.2" + dependencies: + json5 "^2.2.3" + +find-cache-dir@^2.0.0: + version "2.1.0" + dependencies: + commondir "^1.0.1" + make-dir "^2.0.0" + pkg-dir "^3.0.0" + +find-up@^2.0.0: + version "2.1.0" + dependencies: + locate-path "^2.0.0" + +find-up@^3.0.0: + version "3.0.0" + dependencies: + locate-path "^3.0.0" + +find-up@^4.0.0: + version "4.1.0" + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^4.1.0: + version "4.1.0" + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.1" + +flow-enums-runtime@^0.0.6: + version "0.0.6" + +flow-parser@0.*: + version "0.245.2" + +for-each@^0.3.3: + version "0.3.3" + dependencies: + is-callable "^1.1.3" + +form-data-encoder@^2.1.2: + version "2.1.4" + +formdata-polyfill@^4.0.10: + version "4.0.10" + dependencies: + fetch-blob "^3.1.2" + +fresh@0.5.2: + version "0.5.2" + +fs-extra@^10.1.0: + version "10.1.0" + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^11.0.0, fs-extra@^11.2.0: + version "11.2.0" + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^8.1.0: + version "8.1.0" + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs.realpath@^1.0.0: + version "1.0.0" + +fsevents@^2.3.2: + version "2.3.3" + +function-bind@^1.1.2: + version "1.1.2" + +function.prototype.name@^1.1.6: + version "1.1.6" + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" + +functions-have-names@^1.2.3: + version "1.2.3" + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + +get-caller-file@^2.0.1, get-caller-file@^2.0.5: + version "2.0.5" + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: + version "1.2.4" + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +get-package-type@^0.1.0: + version "0.1.0" + +get-pkg-repo@^4.0.0: + version "4.2.1" + dependencies: + "@hutson/parse-repository-url" "^3.0.0" + hosted-git-info "^4.0.0" + through2 "^2.0.0" + yargs "^16.2.0" + +get-stream@^5.0.0: + version "5.2.0" + dependencies: + pump "^3.0.0" + +get-stream@^6.0.0, get-stream@^6.0.1: + version "6.0.1" + +get-symbol-description@^1.0.2: + version "1.0.2" + dependencies: + call-bind "^1.0.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + +get-uri@^6.0.1: + version "6.0.3" + dependencies: + basic-ftp "^5.0.2" + data-uri-to-buffer "^6.0.2" + debug "^4.3.4" + fs-extra "^11.2.0" + +git-raw-commits@^2.0.11, git-raw-commits@^2.0.8: + version "2.0.11" + dependencies: + dargs "^7.0.0" + lodash "^4.17.15" + meow "^8.0.0" + split2 "^3.0.0" + through2 "^4.0.0" + +git-remote-origin-url@^2.0.0: + version "2.0.0" + dependencies: + gitconfiglocal "^1.0.0" + pify "^2.3.0" + +git-semver-tags@^4.1.1: + version "4.1.1" + dependencies: + meow "^8.0.0" + semver "^6.0.0" + +git-up@^7.0.0: + version "7.0.0" + dependencies: + is-ssh "^1.4.0" + parse-url "^8.1.0" + +git-url-parse@13.1.0: + version "13.1.0" + dependencies: + git-up "^7.0.0" + +gitconfiglocal@^1.0.0: + version "1.0.0" + dependencies: + ini "^1.3.2" + +glob-parent@^5.1.2: + version "5.1.2" + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + dependencies: + is-glob "^4.0.3" + +glob@^7.0.0, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^8.0.3: + version "8.1.0" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +glob@^9.3.3: + version "9.3.5" + dependencies: + fs.realpath "^1.0.0" + minimatch "^8.0.2" + minipass "^4.2.4" + path-scurry "^1.6.1" + +global-dirs@^0.1.1: + version "0.1.1" + dependencies: + ini "^1.3.4" + +global-dirs@^3.0.0: + version "3.0.1" + dependencies: + ini "2.0.0" + +globals@^11.1.0: + version "11.12.0" + +globals@^13.19.0: + version "13.24.0" + dependencies: + type-fest "^0.20.2" + +globalthis@^1.0.3: + version "1.0.4" + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + +globby@^11.0.1, globby@^11.1.0: + version "11.1.0" + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +globby@^13.1.2: + version "13.2.2" + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.3.0" + ignore "^5.2.4" + merge2 "^1.4.1" + slash "^4.0.0" + +globby@13.1.4: + version "13.1.4" + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.2.11" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^4.0.0" + +gopd@^1.0.1: + version "1.0.1" + dependencies: + get-intrinsic "^1.1.3" + +got@^12.1.0, got@12.6.1: + version "12.6.1" + dependencies: + "@sindresorhus/is" "^5.2.0" + "@szmarczak/http-timer" "^5.0.1" + cacheable-lookup "^7.0.0" + cacheable-request "^10.2.8" + decompress-response "^6.0.0" + form-data-encoder "^2.1.2" + get-stream "^6.0.1" + http2-wrapper "^2.1.10" + lowercase-keys "^3.0.0" + p-cancelable "^3.0.0" + responselike "^3.0.0" + +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.11" + +graceful-fs@4.2.10: + version "4.2.10" + +graphemer@^1.4.0: + version "1.4.0" + +handlebars@^4.7.7: + version "4.7.8" + dependencies: + minimist "^1.2.5" + neo-async "^2.6.2" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +hard-rejection@^2.1.0: + version "2.1.0" + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + +has-flag@^3.0.0: + version "3.0.0" + +has-flag@^4.0.0: + version "4.0.0" + +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1, has-proto@^1.0.3: + version "1.0.3" + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: + version "1.0.2" + dependencies: + has-symbols "^1.0.3" + +has-yarn@^3.0.0: + version "3.0.0" + +hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: + version "2.0.2" + dependencies: + function-bind "^1.1.2" + +hermes-estree@0.22.0: + version "0.22.0" + +hermes-estree@0.23.1: + version "0.23.1" + +hermes-parser@0.22.0: + version "0.22.0" + dependencies: + hermes-estree "0.22.0" + +hermes-parser@0.23.1: + version "0.23.1" + dependencies: + hermes-estree "0.23.1" + +hosted-git-info@^2.1.4: + version "2.8.9" + +hosted-git-info@^4.0.0, hosted-git-info@^4.0.1: + version "4.1.0" + dependencies: + lru-cache "^6.0.0" + +html-escaper@^2.0.0: + version "2.0.2" + +http-cache-semantics@^4.1.1: + version "4.1.1" + +http-errors@2.0.0: + version "2.0.0" + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-proxy-agent@^7.0.0: + version "7.0.2" + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + +http2-wrapper@^2.1.10: + version "2.2.1" + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.2.0" + +https-proxy-agent@^7.0.0: + version "7.0.5" + dependencies: + agent-base "^7.0.2" + debug "4" + +human-signals@^1.1.1: + version "1.1.1" + +human-signals@^2.1.0: + version "2.1.0" + +human-signals@^4.3.0: + version "4.3.1" + +iconv-lite@^0.4.24: + version "0.4.24" + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.6.2: + version "0.6.3" + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ieee754@^1.1.13, ieee754@^1.2.1: + version "1.2.1" + +ignore@^5.0.5, ignore@^5.2.0, ignore@^5.2.4: + version "5.3.2" + +image-size@^1.0.2: + version "1.1.1" + dependencies: + queue "6.0.2" + +import-fresh@^2.0.0: + version "2.0.0" + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + +import-fresh@^3.0.0, import-fresh@^3.2.1, import-fresh@^3.3.0: + version "3.3.0" + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-lazy@^4.0.0: + version "4.0.0" + +import-local@^3.0.2: + version "3.2.0" + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + +indent-string@^4.0.0: + version "4.0.0" + +indent-string@^5.0.0: + version "5.0.0" + +inflight@^1.0.4: + version "1.0.6" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@2, inherits@2.0.4: + version "2.0.4" + +ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: + version "1.3.8" + +ini@2.0.0: + version "2.0.0" + +inquirer@9.2.6: + version "9.2.6" + dependencies: + ansi-escapes "^4.3.2" + chalk "^5.2.0" + cli-cursor "^3.1.0" + cli-width "^4.0.0" + external-editor "^3.0.3" + figures "^5.0.0" + lodash "^4.17.21" + mute-stream "1.0.0" + ora "^5.4.1" + run-async "^3.0.0" + rxjs "^7.8.1" + string-width "^4.2.3" + strip-ansi "^6.0.1" + through "^2.3.6" + wrap-ansi "^6.0.1" + +internal-slot@^1.0.4, internal-slot@^1.0.7: + version "1.0.7" + dependencies: + es-errors "^1.3.0" + hasown "^2.0.0" + side-channel "^1.0.4" + +interpret@^1.0.0: + version "1.4.0" + +invariant@^2.2.4: + version "2.2.4" + dependencies: + loose-envify "^1.0.0" + +ip-address@^9.0.5: + version "9.0.5" + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" + +ip@^1.1.8: + version "1.1.9" + +is-absolute@^1.0.0: + version "1.0.0" + dependencies: + is-relative "^1.0.0" + is-windows "^1.0.1" + +is-arguments@^1.1.1: + version "1.1.1" + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-array-buffer@^3.0.4: + version "3.0.4" + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + +is-arrayish@^0.2.1: + version "0.2.1" + +is-async-function@^2.0.0: + version "2.0.0" + dependencies: + has-tostringtag "^1.0.0" + +is-bigint@^1.0.1: + version "1.0.4" + dependencies: + has-bigints "^1.0.1" + +is-boolean-object@^1.1.0: + version "1.1.2" + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + +is-ci@^3.0.1, is-ci@3.0.1: + version "3.0.1" + dependencies: + ci-info "^3.2.0" + +is-core-module@^2.13.0, is-core-module@^2.5.0: + version "2.15.1" + dependencies: + hasown "^2.0.2" + +is-data-view@^1.0.1: + version "1.0.1" + dependencies: + is-typed-array "^1.1.13" + +is-date-object@^1.0.1, is-date-object@^1.0.5: + version "1.0.5" + dependencies: + has-tostringtag "^1.0.0" + +is-directory@^0.3.1: + version "0.3.1" + +is-docker@^2.0.0: + version "2.2.1" + +is-docker@^3.0.0: + version "3.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + +is-finalizationregistry@^1.0.2: + version "1.0.2" + dependencies: + call-bind "^1.0.2" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + +is-generator-fn@^2.0.0: + version "2.1.0" + +is-generator-function@^1.0.10: + version "1.0.10" + dependencies: + has-tostringtag "^1.0.0" + +is-git-dirty@^2.0.1: + version "2.0.2" + dependencies: + execa "^4.0.3" + is-git-repository "^2.0.0" + +is-git-repository@^2.0.0: + version "2.0.0" + dependencies: + execa "^4.0.3" + is-absolute "^1.0.0" + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + dependencies: + is-extglob "^2.1.1" + +is-inside-container@^1.0.0: + version "1.0.0" + dependencies: + is-docker "^3.0.0" + +is-installed-globally@^0.4.0: + version "0.4.0" + dependencies: + global-dirs "^3.0.0" + is-path-inside "^3.0.2" + +is-interactive@^1.0.0: + version "1.0.0" + +is-interactive@^2.0.0: + version "2.0.0" + +is-map@^2.0.2, is-map@^2.0.3: + version "2.0.3" + +is-negative-zero@^2.0.3: + version "2.0.3" + +is-npm@^6.0.0: + version "6.0.0" + +is-number-object@^1.0.4: + version "1.0.7" + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + +is-obj@^2.0.0: + version "2.0.0" + +is-path-cwd@^2.2.0: + version "2.2.0" + +is-path-cwd@^3.0.0: + version "3.0.0" + +is-path-inside@^3.0.2, is-path-inside@^3.0.3: + version "3.0.3" + +is-path-inside@^4.0.0: + version "4.0.0" + +is-plain-obj@^1.1.0: + version "1.1.0" + +is-plain-object@^2.0.4: + version "2.0.4" + dependencies: + isobject "^3.0.1" + +is-plain-object@^5.0.0: + version "5.0.0" + +is-regex@^1.1.4: + version "1.1.4" + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-relative@^1.0.0: + version "1.0.0" + dependencies: + is-unc-path "^1.0.0" + +is-set@^2.0.2, is-set@^2.0.3: + version "2.0.3" + +is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: + version "1.0.3" + dependencies: + call-bind "^1.0.7" + +is-ssh@^1.4.0: + version "1.4.0" + dependencies: + protocols "^2.0.1" + +is-stream@^2.0.0: + version "2.0.1" + +is-stream@^3.0.0: + version "3.0.0" + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + dependencies: + has-symbols "^1.0.2" + +is-text-path@^1.0.1: + version "1.0.1" + dependencies: + text-extensions "^1.0.0" + +is-typed-array@^1.1.13: + version "1.1.13" + dependencies: + which-typed-array "^1.1.14" + +is-typedarray@^1.0.0: + version "1.0.0" + +is-unc-path@^1.0.0: + version "1.0.0" + dependencies: + unc-path-regex "^0.1.2" + +is-unicode-supported@^0.1.0: + version "0.1.0" + +is-unicode-supported@^1.1.0, is-unicode-supported@^1.2.0: + version "1.3.0" + +is-weakmap@^2.0.2: + version "2.0.2" + +is-weakref@^1.0.2: + version "1.0.2" + dependencies: + call-bind "^1.0.2" + +is-weakset@^2.0.3: + version "2.0.3" + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + +is-windows@^1.0.1: + version "1.0.2" + +is-wsl@^1.1.0: + version "1.1.0" + +is-wsl@^2.1.1, is-wsl@^2.2.0: + version "2.2.0" + dependencies: + is-docker "^2.0.0" + +is-yarn-global@^0.4.0: + version "0.4.1" + +isarray@^2.0.5: + version "2.0.5" + +isarray@~1.0.0: + version "1.0.0" + +isexe@^2.0.0: + version "2.0.0" + +isobject@^3.0.1: + version "3.0.1" + +issue-parser@6.0.0: + version "6.0.0" + dependencies: + lodash.capitalize "^4.2.1" + lodash.escaperegexp "^4.1.2" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.uniqby "^4.7.0" + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.7" + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +iterate-iterator@^1.0.1: + version "1.0.2" + +iterate-value@^1.0.2: + version "1.0.2" + dependencies: + es-get-iterator "^1.0.2" + iterate-iterator "^1.0.1" + +iterator.prototype@^1.1.2: + version "1.1.2" + dependencies: + define-properties "^1.2.1" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + reflect.getprototypeof "^1.0.4" + set-function-name "^2.0.1" + +jest-changed-files@^29.7.0: + version "29.7.0" + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.6.3, jest-environment-node@^29.7.0: + version "29.7.0" + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + +jest-haste-map@^29.7.0: + version "29.7.0" + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + +jest-regex-util@^29.6.3: + version "29.6.3" + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@*, jest-resolve@^29.7.0: + version "29.7.0" + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.7.0: + version "29.7.0" + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.6.3, jest-validate@^29.7.0: + version "29.7.0" + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.6.3, jest-worker@^29.7.0: + version "29.7.0" + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.7.0: + version "29.7.0" + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +joi@^17.2.1: + version "17.13.3" + dependencies: + "@hapi/hoek" "^9.3.0" + "@hapi/topo" "^5.1.0" + "@sideway/address" "^4.1.5" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + +js-yaml@^3.13.1: + version "3.14.1" + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + dependencies: + argparse "^2.0.1" + +jsbn@1.1.0: + version "1.1.0" + +jsc-android@^250231.0.0: + version "250231.0.0" + +jsc-safe-url@^0.2.2: + version "0.2.4" + +jscodeshift@^0.14.0: + version "0.14.0" + dependencies: + "@babel/core" "^7.13.16" + "@babel/parser" "^7.13.16" + "@babel/plugin-proposal-class-properties" "^7.13.0" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.13.8" + "@babel/plugin-proposal-optional-chaining" "^7.13.12" + "@babel/plugin-transform-modules-commonjs" "^7.13.8" + "@babel/preset-flow" "^7.13.13" + "@babel/preset-typescript" "^7.13.0" + "@babel/register" "^7.13.16" + babel-core "^7.0.0-bridge.0" + chalk "^4.1.2" + flow-parser "0.*" + graceful-fs "^4.2.4" + micromatch "^4.0.4" + neo-async "^2.5.0" + node-dir "^0.1.17" + recast "^0.21.0" + temp "^0.8.4" + write-file-atomic "^2.3.0" + +jsesc@^2.5.1: + version "2.5.2" + +jsesc@~0.5.0: + version "0.5.0" + +json-buffer@3.0.1: + version "3.0.1" + +json-parse-better-errors@^1.0.1: + version "1.0.2" + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + +json-schema-traverse@^0.4.1: + version "0.4.1" + +json-schema-traverse@^1.0.0: + version "1.0.0" + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + +json-stringify-safe@^5.0.1: + version "5.0.1" + +json5@^2.2.1, json5@^2.2.3: + version "2.2.3" + +jsonfile@^4.0.0: + version "4.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^6.0.1: + version "6.1.0" + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonparse@^1.2.0: + version "1.3.1" + +JSONStream@^1.0.4, JSONStream@^1.3.5: + version "1.3.5" + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + +"jsx-ast-utils@^2.4.1 || ^3.0.0": + version "3.3.5" + dependencies: + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + object.assign "^4.1.4" + object.values "^1.1.6" + +keyv@^4.5.3: + version "4.5.4" + dependencies: + json-buffer "3.0.1" + +kind-of@^6.0.2, kind-of@^6.0.3: + version "6.0.3" + +kleur@^3.0.3: + version "3.0.3" + +kleur@^4.1.4: + version "4.1.5" + +latest-version@^7.0.0: + version "7.0.0" + dependencies: + package-json "^8.1.0" + +leven@^3.1.0: + version "3.1.0" + +levn@^0.4.1: + version "0.4.1" + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +levn@~0.3.0: + version "0.3.0" + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lighthouse-logger@^1.0.0: + version "1.4.2" + dependencies: + debug "^2.6.9" + marky "^1.2.2" + +lines-and-columns@^1.1.6: + version "1.2.4" + +load-json-file@^4.0.0: + version "4.0.0" + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + dependencies: + p-locate "^5.0.0" + +lodash.camelcase@^4.3.0: + version "4.3.0" + +lodash.capitalize@^4.2.1: + version "4.2.1" + +lodash.debounce@^4.0.8: + version "4.0.8" + +lodash.escaperegexp@^4.1.2: + version "4.1.2" + +lodash.isfunction@^3.0.9: + version "3.0.9" + +lodash.ismatch@^4.4.0: + version "4.4.0" + +lodash.isplainobject@^4.0.6: + version "4.0.6" + +lodash.isstring@^4.0.1: + version "4.0.1" + +lodash.kebabcase@^4.1.1: + version "4.1.1" + +lodash.merge@^4.6.2: + version "4.6.2" + +lodash.mergewith@^4.6.2: + version "4.6.2" + +lodash.snakecase@^4.1.1: + version "4.1.1" + +lodash.startcase@^4.4.0: + version "4.4.0" + +lodash.throttle@^4.1.1: + version "4.1.1" + +lodash.uniq@^4.5.0: + version "4.5.0" + +lodash.uniqby@^4.7.0: + version "4.7.0" + +lodash.upperfirst@^4.3.1: + version "4.3.1" + +lodash@^4.17.15, lodash@^4.17.21, lodash@4.17.21: + version "4.17.21" + +log-symbols@^4.1.0: + version "4.1.0" + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +log-symbols@^5.1.0: + version "5.1.0" + dependencies: + chalk "^5.0.0" + is-unicode-supported "^1.1.0" + +logkitty@^0.7.1: + version "0.7.1" + dependencies: + ansi-fragments "^0.2.1" + dayjs "^1.8.15" + yargs "^15.1.0" + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lowercase-keys@^3.0.0: + version "3.0.0" + +lru-cache@^10.2.0: + version "10.4.3" + +lru-cache@^5.1.1: + version "5.1.1" + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + dependencies: + yallist "^4.0.0" + +lru-cache@^7.14.1: + version "7.18.3" + +macos-release@^3.1.0: + version "3.3.0" + +make-dir@^2.0.0, make-dir@^2.1.0: + version "2.1.0" + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +make-dir@^4.0.0: + version "4.0.0" + dependencies: + semver "^7.5.3" + +make-error@^1.1.1: + version "1.3.6" + +makeerror@1.0.12: + version "1.0.12" + dependencies: + tmpl "1.0.5" + +map-obj@^1.0.0: + version "1.0.1" + +map-obj@^4.0.0, map-obj@^4.1.0: + version "4.3.0" + +marky@^1.2.2: + version "1.2.5" + +memoize-one@^5.0.0: + version "5.2.1" + +meow@^10.1.3: + version "10.1.5" + dependencies: + "@types/minimist" "^1.2.2" + camelcase-keys "^7.0.0" + decamelize "^5.0.0" + decamelize-keys "^1.1.0" + hard-rejection "^2.1.0" + minimist-options "4.1.0" + normalize-package-data "^3.0.2" + read-pkg-up "^8.0.0" + redent "^4.0.0" + trim-newlines "^4.0.2" + type-fest "^1.2.2" + yargs-parser "^20.2.9" + +meow@^8.0.0, meow@^8.1.2: + version "8.1.2" + dependencies: + "@types/minimist" "^1.2.0" + camelcase-keys "^6.2.2" + decamelize-keys "^1.1.0" + hard-rejection "^2.1.0" + minimist-options "4.1.0" + normalize-package-data "^3.0.0" + read-pkg-up "^7.0.1" + redent "^3.0.0" + trim-newlines "^3.0.0" + type-fest "^0.18.0" + yargs-parser "^20.2.3" + +merge-stream@^2.0.0: + version "2.0.0" + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + +metro-babel-transformer@0.80.12: + version "0.80.12" + dependencies: + "@babel/core" "^7.20.0" + flow-enums-runtime "^0.0.6" + hermes-parser "0.23.1" + nullthrows "^1.1.1" + +metro-cache-key@0.80.12: + version "0.80.12" + dependencies: + flow-enums-runtime "^0.0.6" + +metro-cache@0.80.12: + version "0.80.12" + dependencies: + exponential-backoff "^3.1.1" + flow-enums-runtime "^0.0.6" + metro-core "0.80.12" + +metro-config@^0.80.3, metro-config@^0.80.9, metro-config@0.80.12: + version "0.80.12" + dependencies: + connect "^3.6.5" + cosmiconfig "^5.0.5" + flow-enums-runtime "^0.0.6" + jest-validate "^29.6.3" + metro "0.80.12" + metro-cache "0.80.12" + metro-core "0.80.12" + metro-runtime "0.80.12" + +metro-core@^0.80.3, metro-core@0.80.12: + version "0.80.12" + dependencies: + flow-enums-runtime "^0.0.6" + lodash.throttle "^4.1.1" + metro-resolver "0.80.12" + +metro-file-map@0.80.12: + version "0.80.12" + dependencies: + anymatch "^3.0.3" + debug "^2.2.0" + fb-watchman "^2.0.0" + flow-enums-runtime "^0.0.6" + graceful-fs "^4.2.4" + invariant "^2.2.4" + jest-worker "^29.6.3" + micromatch "^4.0.4" + node-abort-controller "^3.1.1" + nullthrows "^1.1.1" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.3.2" + +metro-minify-terser@0.80.12: + version "0.80.12" + dependencies: + flow-enums-runtime "^0.0.6" + terser "^5.15.0" + +metro-resolver@0.80.12: + version "0.80.12" + dependencies: + flow-enums-runtime "^0.0.6" + +metro-runtime@^0.80.3, metro-runtime@0.80.12: + version "0.80.12" + dependencies: + "@babel/runtime" "^7.25.0" + flow-enums-runtime "^0.0.6" + +metro-source-map@^0.80.3, metro-source-map@0.80.12: + version "0.80.12" + dependencies: + "@babel/traverse" "^7.20.0" + "@babel/types" "^7.20.0" + flow-enums-runtime "^0.0.6" + invariant "^2.2.4" + metro-symbolicate "0.80.12" + nullthrows "^1.1.1" + ob1 "0.80.12" + source-map "^0.5.6" + vlq "^1.0.0" + +metro-symbolicate@0.80.12: + version "0.80.12" + dependencies: + flow-enums-runtime "^0.0.6" + invariant "^2.2.4" + metro-source-map "0.80.12" + nullthrows "^1.1.1" + source-map "^0.5.6" + through2 "^2.0.1" + vlq "^1.0.0" + +metro-transform-plugins@0.80.12: + version "0.80.12" + dependencies: + "@babel/core" "^7.20.0" + "@babel/generator" "^7.20.0" + "@babel/template" "^7.0.0" + "@babel/traverse" "^7.20.0" + flow-enums-runtime "^0.0.6" + nullthrows "^1.1.1" + +metro-transform-worker@0.80.12: + version "0.80.12" + dependencies: + "@babel/core" "^7.20.0" + "@babel/generator" "^7.20.0" + "@babel/parser" "^7.20.0" + "@babel/types" "^7.20.0" + flow-enums-runtime "^0.0.6" + metro "0.80.12" + metro-babel-transformer "0.80.12" + metro-cache "0.80.12" + metro-cache-key "0.80.12" + metro-minify-terser "0.80.12" + metro-source-map "0.80.12" + metro-transform-plugins "0.80.12" + nullthrows "^1.1.1" + +metro@^0.80.3, metro@0.80.12: + version "0.80.12" + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/core" "^7.20.0" + "@babel/generator" "^7.20.0" + "@babel/parser" "^7.20.0" + "@babel/template" "^7.0.0" + "@babel/traverse" "^7.20.0" + "@babel/types" "^7.20.0" + accepts "^1.3.7" + chalk "^4.0.0" + ci-info "^2.0.0" + connect "^3.6.5" + debug "^2.2.0" + denodeify "^1.2.1" + error-stack-parser "^2.0.6" + flow-enums-runtime "^0.0.6" + graceful-fs "^4.2.4" + hermes-parser "0.23.1" + image-size "^1.0.2" + invariant "^2.2.4" + jest-worker "^29.6.3" + jsc-safe-url "^0.2.2" + lodash.throttle "^4.1.1" + metro-babel-transformer "0.80.12" + metro-cache "0.80.12" + metro-cache-key "0.80.12" + metro-config "0.80.12" + metro-core "0.80.12" + metro-file-map "0.80.12" + metro-resolver "0.80.12" + metro-runtime "0.80.12" + metro-source-map "0.80.12" + metro-symbolicate "0.80.12" + metro-transform-plugins "0.80.12" + metro-transform-worker "0.80.12" + mime-types "^2.1.27" + nullthrows "^1.1.1" + serialize-error "^2.1.0" + source-map "^0.5.6" + strip-ansi "^6.0.0" + throat "^5.0.0" + ws "^7.5.10" + yargs "^17.6.2" + +micromatch@^4.0.4: + version "4.0.8" + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +"mime-db@>= 1.43.0 < 2": + version "1.53.0" + +mime-db@1.52.0: + version "1.52.0" + +mime-types@^2.1.27, mime-types@~2.1.34, mime-types@2.1.35: + version "2.1.35" + dependencies: + mime-db "1.52.0" + +mime@^2.4.1: + version "2.6.0" + +mime@1.6.0: + version "1.6.0" + +mimic-fn@^2.1.0: + version "2.1.0" + +mimic-fn@^4.0.0: + version "4.0.0" + +mimic-response@^3.1.0: + version "3.1.0" + +mimic-response@^4.0.0: + version "4.0.0" + +min-indent@^1.0.0, min-indent@^1.0.1: + version "1.0.1" + +minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + dependencies: + brace-expansion "^2.0.1" + +minimatch@^8.0.2: + version "8.0.4" + dependencies: + brace-expansion "^2.0.1" + +minimist-options@4.1.0: + version "4.1.0" + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + kind-of "^6.0.3" + +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: + version "1.2.8" + +minipass@^4.2.4: + version "4.2.8" + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": + version "7.1.2" + +mkdirp@^0.5.1: + version "0.5.6" + dependencies: + minimist "^1.2.6" + +mkdirp@^1.0.4: + version "1.0.4" + +modify-values@^1.0.0: + version "1.0.1" + +ms@^2.1.3, ms@2.1.3: + version "2.1.3" + +ms@2.0.0: + version "2.0.0" + +mute-stream@1.0.0: + version "1.0.0" + +natural-compare-lite@^1.4.0: + version "1.4.0" + +natural-compare@^1.4.0: + version "1.4.0" + +negotiator@0.6.3: + version "0.6.3" + +neo-async@^2.5.0, neo-async@^2.6.2: + version "2.6.2" + +netmask@^2.0.2: + version "2.0.2" + +new-github-release-url@2.0.0: + version "2.0.0" + dependencies: + type-fest "^2.5.1" + +nocache@^3.0.1: + version "3.0.4" + +node-abort-controller@^3.1.1: + version "3.1.1" + +node-dir@^0.1.17: + version "0.1.17" + dependencies: + minimatch "^3.0.2" + +node-domexception@^1.0.0: + version "1.0.0" + +node-fetch@^2.2.0, node-fetch@^2.6.7: + version "2.7.0" + dependencies: + whatwg-url "^5.0.0" + +node-fetch@3.3.1: + version "3.3.1" + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + +node-forge@^1: + version "1.3.1" + +node-int64@^0.4.0: + version "0.4.0" + +node-releases@^2.0.18: + version "2.0.18" + +node-stream-zip@^1.9.1: + version "1.15.0" + +normalize-package-data@^2.3.2: + version "2.5.0" + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-package-data@^2.5.0: + version "2.5.0" + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-package-data@^3.0.0, normalize-package-data@^3.0.2: + version "3.0.3" + dependencies: + hosted-git-info "^4.0.1" + is-core-module "^2.5.0" + semver "^7.3.4" + validate-npm-package-license "^3.0.1" + +normalize-path@^3.0.0: + version "3.0.0" + +normalize-url@^8.0.0: + version "8.0.1" + +npm-run-path@^4.0.0, npm-run-path@^4.0.1: + version "4.0.1" + dependencies: + path-key "^3.0.0" + +npm-run-path@^5.1.0: + version "5.3.0" + dependencies: + path-key "^4.0.0" + +nullthrows@^1.1.1: + version "1.1.1" + +ob1@0.80.12: + version "0.80.12" + dependencies: + flow-enums-runtime "^0.0.6" + +object-assign@^4.1.1: + version "4.1.1" + +object-inspect@^1.13.1: + version "1.13.2" + +object-keys@^1.1.1: + version "1.1.1" + +object.assign@^4.1.4, object.assign@^4.1.5: + version "4.1.5" + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.entries@^1.1.8: + version "1.1.8" + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +object.fromentries@^2.0.8: + version "2.0.8" + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + +object.values@^1.1.6, object.values@^1.2.0: + version "1.2.0" + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +on-finished@~2.3.0: + version "2.3.0" + dependencies: + ee-first "1.1.1" + +on-finished@2.4.1: + version "2.4.1" + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + dependencies: + wrappy "1" + +onetime@^5.1.0, onetime@^5.1.2: + version "5.1.2" + dependencies: + mimic-fn "^2.1.0" + +onetime@^6.0.0: + version "6.0.0" + dependencies: + mimic-fn "^4.0.0" + +open@^6.2.0: + version "6.4.0" + dependencies: + is-wsl "^1.1.0" + +open@^7.0.3: + version "7.4.2" + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + +open@9.1.0: + version "9.1.0" + dependencies: + default-browser "^4.0.0" + define-lazy-prop "^3.0.0" + is-inside-container "^1.0.0" + is-wsl "^2.2.0" + +optionator@^0.8.1: + version "0.8.3" + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +optionator@^0.9.3: + version "0.9.4" + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +ora@^5.4.1: + version "5.4.1" + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + +ora@6.3.1: + version "6.3.1" + dependencies: + chalk "^5.0.0" + cli-cursor "^4.0.0" + cli-spinners "^2.6.1" + is-interactive "^2.0.0" + is-unicode-supported "^1.1.0" + log-symbols "^5.1.0" + stdin-discarder "^0.1.0" + strip-ansi "^7.0.1" + wcwidth "^1.0.1" + +os-name@5.1.0: + version "5.1.0" + dependencies: + macos-release "^3.1.0" + windows-release "^5.0.1" + +os-tmpdir@~1.0.2: + version "1.0.2" + +p-cancelable@^3.0.0: + version "3.0.0" + +p-limit@^1.1.0: + version "1.3.0" + dependencies: + p-try "^1.0.0" + +p-limit@^2.0.0, p-limit@^2.2.0: + version "2.3.0" + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + dependencies: + yocto-queue "^0.1.0" + +p-locate@^2.0.0: + version "2.0.0" + dependencies: + p-limit "^1.1.0" + +p-locate@^3.0.0: + version "3.0.0" + dependencies: + p-limit "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + dependencies: + p-limit "^3.0.2" + +p-map@^4.0.0: + version "4.0.0" + dependencies: + aggregate-error "^3.0.0" + +p-map@^5.5.0: + version "5.5.0" + dependencies: + aggregate-error "^4.0.0" + +p-try@^1.0.0: + version "1.0.0" + +p-try@^2.0.0: + version "2.2.0" + +pac-proxy-agent@^6.0.3: + version "6.0.4" + dependencies: + agent-base "^7.0.2" + debug "^4.3.4" + get-uri "^6.0.1" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.0" + pac-resolver "^6.0.1" + socks-proxy-agent "^8.0.1" + +pac-resolver@^6.0.1: + version "6.0.2" + dependencies: + degenerator "^4.0.4" + ip "^1.1.8" + netmask "^2.0.2" + +package-json@^8.1.0: + version "8.1.1" + dependencies: + got "^12.1.0" + registry-auth-token "^5.0.1" + registry-url "^6.0.0" + semver "^7.3.7" + +parent-module@^1.0.0: + version "1.0.1" + dependencies: + callsites "^3.0.0" + +parse-json@^4.0.0: + version "4.0.0" + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse-json@^5.0.0, parse-json@^5.2.0: + version "5.2.0" + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse-path@^7.0.0: + version "7.0.0" + dependencies: + protocols "^2.0.0" + +parse-url@^8.1.0: + version "8.1.0" + dependencies: + parse-path "^7.0.0" + +parseurl@~1.3.3: + version "1.3.3" + +path-exists@^3.0.0: + version "3.0.0" + +path-exists@^4.0.0: + version "4.0.0" + +path-is-absolute@^1.0.0: + version "1.0.1" + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + +path-key@^4.0.0: + version "4.0.0" + +path-parse@^1.0.7: + version "1.0.7" + +path-scurry@^1.6.1: + version "1.11.1" + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +path-type@^3.0.0: + version "3.0.0" + dependencies: + pify "^3.0.0" + +path-type@^4.0.0: + version "4.0.0" + +picocolors@^1.0.0, picocolors@^1.0.1: + version "1.1.0" + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + +pify@^2.3.0: + version "2.3.0" + +pify@^3.0.0: + version "3.0.0" + +pify@^4.0.1: + version "4.0.1" + +pirates@^4.0.4, pirates@^4.0.6: + version "4.0.6" + +pkg-dir@^3.0.0: + version "3.0.0" + dependencies: + find-up "^3.0.0" + +pkg-dir@^4.2.0: + version "4.2.0" + dependencies: + find-up "^4.0.0" + +pkg-up@^3.1.0: + version "3.1.0" + dependencies: + find-up "^3.0.0" + +possible-typed-array-names@^1.0.0: + version "1.0.0" + +prelude-ls@^1.2.1: + version "1.2.1" + +prelude-ls@~1.1.2: + version "1.1.2" + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + dependencies: + fast-diff "^1.1.2" + +prettier@^3.0.3, prettier@>=2, prettier@>=2.0.0, prettier@>=3.0.0: + version "3.3.3" + +pretty-format@^26.5.2, pretty-format@^26.6.2: + version "26.6.2" + dependencies: + "@jest/types" "^26.6.2" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^17.0.1" + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +process-nextick-args@~2.0.0: + version "2.0.1" + +promise.allsettled@1.0.6: + version "1.0.6" + dependencies: + array.prototype.map "^1.0.5" + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + get-intrinsic "^1.1.3" + iterate-value "^1.0.2" + +promise@^8.3.0: + version "8.3.0" + dependencies: + asap "~2.0.6" + +prompts@^2.0.1, prompts@^2.4.2: + version "2.4.2" + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +prop-types@^15.8.1: + version "15.8.1" + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +proto-list@~1.2.1: + version "1.2.4" + +protocols@^2.0.0, protocols@^2.0.1: + version "2.0.1" + +proxy-agent@6.2.1: + version "6.2.1" + dependencies: + agent-base "^7.0.2" + debug "^4.3.4" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.0" + lru-cache "^7.14.1" + pac-proxy-agent "^6.0.3" + proxy-from-env "^1.1.0" + socks-proxy-agent "^8.0.1" + +proxy-from-env@^1.1.0: + version "1.1.0" + +pump@^3.0.0: + version "3.0.0" + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^2.1.0: + version "2.3.1" + +pupa@^3.1.0: + version "3.1.0" + dependencies: + escape-goat "^4.0.0" + +pure-rand@^6.0.0: + version "6.1.0" + +q@^1.5.1: + version "1.5.1" + +querystring@^0.2.1: + version "0.2.1" + +queue-microtask@^1.2.2: + version "1.2.3" + +queue@6.0.2: + version "6.0.2" + dependencies: + inherits "~2.0.3" + +quick-lru@^4.0.1: + version "4.0.1" + +quick-lru@^5.1.1: + version "5.1.1" + +range-parser@~1.2.1: + version "1.2.1" + +rc@1.2.8: + version "1.2.8" + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-devtools-core@^5.3.1: + version "5.3.1" + dependencies: + shell-quote "^1.6.1" + ws "^7" + +react-is@^16.13.1: + version "16.13.1" + +react-is@^17.0.1: + version "17.0.2" + +react-is@^18.0.0: + version "18.3.1" + +react-native-builder-bob@^0.30.2: + version "0.30.2" + dependencies: + "@babel/core" "^7.25.2" + "@babel/plugin-transform-strict-mode" "^7.24.7" + "@babel/preset-env" "^7.25.2" + "@babel/preset-flow" "^7.24.7" + "@babel/preset-react" "^7.24.7" + "@babel/preset-typescript" "^7.24.7" + babel-plugin-module-resolver "^5.0.2" + browserslist "^4.20.4" + cosmiconfig "^9.0.0" + cross-spawn "^7.0.3" + dedent "^0.7.0" + del "^6.1.1" + escape-string-regexp "^4.0.0" + fs-extra "^10.1.0" + glob "^8.0.3" + is-git-dirty "^2.0.1" + json5 "^2.2.1" + kleur "^4.1.4" + metro-config "^0.80.9" + prompts "^2.4.2" + which "^2.0.2" + yargs "^17.5.1" + +"react-native-pubky-example@file:/Users/coreyphillips/Desktop/reat-native-pubky/quick/react-native-pubky/example": + version "0.0.1" + resolved "file:example" + dependencies: + react "18.3.1" + react-native "0.75.2" + +react-native@*, react-native@0.75.2: + version "0.75.2" + dependencies: + "@jest/create-cache-key-function" "^29.6.3" + "@react-native-community/cli" "14.0.0" + "@react-native-community/cli-platform-android" "14.0.0" + "@react-native-community/cli-platform-ios" "14.0.0" + "@react-native/assets-registry" "0.75.2" + "@react-native/codegen" "0.75.2" + "@react-native/community-cli-plugin" "0.75.2" + "@react-native/gradle-plugin" "0.75.2" + "@react-native/js-polyfills" "0.75.2" + "@react-native/normalize-colors" "0.75.2" + "@react-native/virtualized-lists" "0.75.2" + abort-controller "^3.0.0" + anser "^1.4.9" + ansi-regex "^5.0.0" + base64-js "^1.5.1" + chalk "^4.0.0" + event-target-shim "^5.0.1" + flow-enums-runtime "^0.0.6" + glob "^7.1.1" + invariant "^2.2.4" + jest-environment-node "^29.6.3" + jsc-android "^250231.0.0" + memoize-one "^5.0.0" + metro-runtime "^0.80.3" + metro-source-map "^0.80.3" + mkdirp "^0.5.1" + nullthrows "^1.1.1" + pretty-format "^26.5.2" + promise "^8.3.0" + react-devtools-core "^5.3.1" + react-refresh "^0.14.0" + regenerator-runtime "^0.13.2" + scheduler "0.24.0-canary-efb381bbf-20230505" + semver "^7.1.3" + stacktrace-parser "^0.1.10" + whatwg-fetch "^3.0.0" + ws "^6.2.2" + yargs "^17.6.2" + +react-refresh@^0.14.0: + version "0.14.2" + +react@*, react@^18.2.0, react@18.3.1: + version "18.3.1" + dependencies: + loose-envify "^1.1.0" + +read-pkg-up@^3.0.0: + version "3.0.0" + dependencies: + find-up "^2.0.0" + read-pkg "^3.0.0" + +read-pkg-up@^7.0.1: + version "7.0.1" + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg-up@^8.0.0: + version "8.0.0" + dependencies: + find-up "^5.0.0" + read-pkg "^6.0.0" + type-fest "^1.0.1" + +read-pkg@^3.0.0: + version "3.0.0" + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + +read-pkg@^5.2.0: + version "5.2.0" + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + +read-pkg@^6.0.0: + version "6.0.0" + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^3.0.2" + parse-json "^5.2.0" + type-fest "^1.0.1" + +readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.4.0, readable-stream@3: + version "3.6.2" + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-stream@~2.3.6: + version "2.3.8" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readline@^1.3.0: + version "1.3.0" + +recast@^0.21.0: + version "0.21.5" + dependencies: + ast-types "0.15.2" + esprima "~4.0.0" + source-map "~0.6.1" + tslib "^2.0.1" + +rechoir@^0.6.2: + version "0.6.2" + dependencies: + resolve "^1.1.6" + +redent@^3.0.0: + version "3.0.0" + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + +redent@^4.0.0: + version "4.0.0" + dependencies: + indent-string "^5.0.0" + strip-indent "^4.0.0" + +reflect.getprototypeof@^1.0.4: + version "1.0.6" + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.1" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + globalthis "^1.0.3" + which-builtin-type "^1.1.3" + +regenerate-unicode-properties@^10.1.0: + version "10.1.1" + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + +regenerator-runtime@^0.13.2: + version "0.13.11" + +regenerator-runtime@^0.14.0: + version "0.14.1" + +regenerator-transform@^0.15.2: + version "0.15.2" + dependencies: + "@babel/runtime" "^7.8.4" + +regexp.prototype.flags@^1.5.2: + version "1.5.2" + dependencies: + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" + +regexpu-core@^5.3.1: + version "5.3.2" + dependencies: + "@babel/regjsgen" "^0.8.0" + regenerate "^1.4.2" + regenerate-unicode-properties "^10.1.0" + regjsparser "^0.9.1" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + +registry-auth-token@^5.0.1: + version "5.0.2" + dependencies: + "@pnpm/npm-conf" "^2.1.0" + +registry-url@^6.0.0: + version "6.0.1" + dependencies: + rc "1.2.8" + +regjsparser@^0.9.1: + version "0.9.1" + dependencies: + jsesc "~0.5.0" + +release-it@^15.0.0, release-it@^15.4.1: + version "15.11.0" + dependencies: + "@iarna/toml" "2.2.5" + "@octokit/rest" "19.0.11" + async-retry "1.3.3" + chalk "5.2.0" + cosmiconfig "8.1.3" + execa "7.1.1" + git-url-parse "13.1.0" + globby "13.1.4" + got "12.6.1" + inquirer "9.2.6" + is-ci "3.0.1" + issue-parser "6.0.0" + lodash "4.17.21" + mime-types "2.1.35" + new-github-release-url "2.0.0" + node-fetch "3.3.1" + open "9.1.0" + ora "6.3.1" + os-name "5.1.0" + promise.allsettled "1.0.6" + proxy-agent "6.2.1" + semver "7.5.1" + shelljs "0.8.5" + update-notifier "6.0.2" + url-join "5.0.0" + wildcard-match "5.1.2" + yargs-parser "21.1.1" + +require-directory@^2.1.1: + version "2.1.1" + +require-from-string@^2.0.2: + version "2.0.2" + +require-main-filename@^2.0.0: + version "2.0.0" + +reselect@^4.1.7: + version "4.1.8" + +resolve-alpn@^1.2.0: + version "1.2.1" + +resolve-cwd@^3.0.0: + version "3.0.0" + dependencies: + resolve-from "^5.0.0" + +resolve-from@^3.0.0: + version "3.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + +resolve-from@^5.0.0, resolve-from@5.0.0: + version "5.0.0" + +resolve-global@^1.0.0, resolve-global@1.0.0: + version "1.0.0" + dependencies: + global-dirs "^0.1.1" + +resolve.exports@^2.0.0: + version "2.0.2" + +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.8: + version "1.22.8" + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^2.0.0-next.5: + version "2.0.0-next.5" + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +responselike@^3.0.0: + version "3.0.0" + dependencies: + lowercase-keys "^3.0.0" + +restore-cursor@^3.1.0: + version "3.1.0" + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +restore-cursor@^4.0.0: + version "4.0.0" + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +retry@0.13.1: + version "0.13.1" + +reusify@^1.0.4: + version "1.0.4" + +rimraf@^3.0.2: + version "3.0.2" + dependencies: + glob "^7.1.3" + +rimraf@~2.6.2: + version "2.6.3" + dependencies: + glob "^7.1.3" + +run-applescript@^5.0.0: + version "5.0.0" + dependencies: + execa "^5.0.0" + +run-async@^3.0.0: + version "3.0.0" + +run-parallel@^1.1.9: + version "1.2.0" + dependencies: + queue-microtask "^1.2.2" + +rxjs@^7.8.1: + version "7.8.1" + dependencies: + tslib "^2.1.0" + +safe-array-concat@^1.1.2: + version "1.1.2" + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + has-symbols "^1.0.3" + isarray "^2.0.5" + +safe-buffer@~5.1.0, safe-buffer@~5.1.1, safe-buffer@5.1.2: + version "5.1.2" + +safe-buffer@~5.2.0: + version "5.2.1" + +safe-regex-test@^1.0.3: + version "1.0.3" + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-regex "^1.1.4" + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + +scheduler@0.24.0-canary-efb381bbf-20230505: + version "0.24.0-canary-efb381bbf-20230505" + dependencies: + loose-envify "^1.1.0" + +selfsigned@^2.4.1: + version "2.4.1" + dependencies: + "@types/node-forge" "^1.3.0" + node-forge "^1" + +semver-diff@^4.0.0: + version "4.0.0" + dependencies: + semver "^7.3.5" + +semver@^5.6.0: + version "5.7.2" + +semver@^6.0.0: + version "6.3.1" + +semver@^6.3.0: + version "6.3.1" + +semver@^6.3.1: + version "6.3.1" + +semver@^7.1.3, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4: + version "7.6.3" + +"semver@2 || 3 || 4 || 5": + version "5.7.2" + +semver@7.3.8: + version "7.3.8" + dependencies: + lru-cache "^6.0.0" + +semver@7.5.1: + version "7.5.1" + dependencies: + lru-cache "^6.0.0" + +semver@7.5.4: + version "7.5.4" + dependencies: + lru-cache "^6.0.0" + +send@0.18.0: + version "0.18.0" + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serialize-error@^2.1.0: + version "2.1.0" + +serve-static@^1.13.1: + version "1.15.0" + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +set-blocking@^2.0.0: + version "2.0.0" + +set-function-length@^1.2.1: + version "1.2.2" + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.1, set-function-name@^2.0.2: + version "2.0.2" + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +setprototypeof@1.2.0: + version "1.2.0" + +shallow-clone@^3.0.0: + version "3.0.1" + dependencies: + kind-of "^6.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + +shell-quote@^1.6.1, shell-quote@^1.7.3: + version "1.8.1" + +shelljs@0.8.5: + version "0.8.5" + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +side-channel@^1.0.4, side-channel@^1.0.6: + version "1.0.6" + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + +sisteransi@^1.0.5: + version "1.0.5" + +slash@^3.0.0: + version "3.0.0" + +slash@^4.0.0: + version "4.0.0" + +slice-ansi@^2.0.0: + version "2.1.0" + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +smart-buffer@^4.2.0: + version "4.2.0" + +socks-proxy-agent@^8.0.1: + version "8.0.4" + dependencies: + agent-base "^7.1.1" + debug "^4.3.4" + socks "^2.8.3" + +socks@^2.8.3: + version "2.8.3" + dependencies: + ip-address "^9.0.5" + smart-buffer "^4.2.0" + +source-map-support@^0.5.16, source-map-support@~0.5.20: + version "0.5.21" + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-support@0.5.13: + version "0.5.13" + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.5.6: + version "0.5.7" + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + +spdx-correct@^3.0.0: + version "3.2.0" + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.5.0" + +spdx-expression-parse@^3.0.0: + version "3.0.1" + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.20" + +split@^1.0.0: + version "1.0.1" + dependencies: + through "2" + +split2@^3.0.0, split2@^3.2.2: + version "3.2.2" + dependencies: + readable-stream "^3.0.0" + +sprintf-js@^1.1.3: + version "1.1.3" + +sprintf-js@~1.0.2: + version "1.0.3" + +stack-utils@^2.0.3: + version "2.0.6" + dependencies: + escape-string-regexp "^2.0.0" + +stackframe@^1.3.4: + version "1.3.4" + +stacktrace-parser@^0.1.10: + version "0.1.10" + dependencies: + type-fest "^0.7.1" + +statuses@~1.5.0: + version "1.5.0" + +statuses@2.0.1: + version "2.0.1" + +stdin-discarder@^0.1.0: + version "0.1.0" + dependencies: + bl "^5.0.0" + +stop-iteration-iterator@^1.0.0: + version "1.0.0" + dependencies: + internal-slot "^1.0.4" + +string_decoder@^1.1.1: + version "1.3.0" + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + dependencies: + safe-buffer "~5.1.0" + +string-length@^4.0.1: + version "4.0.2" + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-natural-compare@^3.0.1: + version "3.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string.prototype.matchall@^4.0.11: + version "4.0.11" + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.7" + regexp.prototype.flags "^1.5.2" + set-function-name "^2.0.2" + side-channel "^1.0.6" + +string.prototype.repeat@^1.0.0: + version "1.0.0" + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.trim@^1.2.9: + version "1.2.9" + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-object-atoms "^1.0.0" + +string.prototype.trimend@^1.0.8: + version "1.0.8" + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +strip-ansi@^5.0.0: + version "5.2.0" + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^5.2.0: + version "5.2.0" + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.0" + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + +strip-bom@^4.0.0: + version "4.0.0" + +strip-final-newline@^2.0.0: + version "2.0.0" + +strip-final-newline@^3.0.0: + version "3.0.0" + +strip-indent@^3.0.0: + version "3.0.0" + dependencies: + min-indent "^1.0.0" + +strip-indent@^4.0.0: + version "4.0.0" + dependencies: + min-indent "^1.0.1" + +strip-json-comments@^3.1.1: + version "3.1.1" + +strip-json-comments@~2.0.1: + version "2.0.1" + +strnum@^1.0.5: + version "1.0.5" + +sudo-prompt@^9.0.0: + version "9.2.1" + +supports-color@^5.3.0: + version "5.5.0" + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + +synckit@^0.9.1: + version "0.9.1" + dependencies: + "@pkgr/core" "^0.1.0" + tslib "^2.6.2" + +temp@^0.8.4: + version "0.8.4" + dependencies: + rimraf "~2.6.2" + +terser@^5.15.0: + version "5.31.6" + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +test-exclude@^6.0.0: + version "6.0.0" + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-extensions@^1.0.0: + version "1.9.0" + +text-table@^0.2.0: + version "0.2.0" + +throat@^5.0.0: + version "5.0.0" + +through@^2.3.6, "through@>=2.2.7 <3", through@2: + version "2.3.8" + +through2@^2.0.0: + version "2.0.5" + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +through2@^2.0.1: + version "2.0.5" + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +through2@^4.0.0: + version "4.0.2" + dependencies: + readable-stream "3" + +titleize@^3.0.0: + version "3.0.0" + +tmp@^0.0.33: + version "0.0.33" + dependencies: + os-tmpdir "~1.0.2" + +tmpl@1.0.5: + version "1.0.5" + +to-fast-properties@^2.0.0: + version "2.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + +tr46@~0.0.3: + version "0.0.3" + +trim-newlines@^3.0.0: + version "3.0.1" + +trim-newlines@^4.0.2: + version "4.1.1" + +ts-node@^10.8.1, ts-node@>=10, ts-node@>=9.0.0: + version "10.9.2" + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tslib@^1.8.1: + version "1.14.1" + +tslib@^2.0.1, tslib@^2.1.0, tslib@^2.6.2: + version "2.7.0" + +tsutils@^3.21.0: + version "3.21.0" + dependencies: + tslib "^1.8.1" + +turbo-darwin-arm64@1.13.4: + version "1.13.4" + +turbo@^1.10.7: + version "1.13.4" + optionalDependencies: + turbo-darwin-64 "1.13.4" + turbo-darwin-arm64 "1.13.4" + turbo-linux-64 "1.13.4" + turbo-linux-arm64 "1.13.4" + turbo-windows-64 "1.13.4" + turbo-windows-arm64 "1.13.4" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + dependencies: + prelude-ls "^1.2.1" + +type-check@~0.3.2: + version "0.3.2" + dependencies: + prelude-ls "~1.1.2" + +type-detect@4.0.8: + version "4.0.8" + +type-fest@^0.18.0: + version "0.18.1" + +type-fest@^0.20.2: + version "0.20.2" + +type-fest@^0.21.3: + version "0.21.3" + +type-fest@^0.6.0: + version "0.6.0" + +type-fest@^0.7.1: + version "0.7.1" + +type-fest@^0.8.1: + version "0.8.1" + +type-fest@^1.0.1, type-fest@^1.2.1, type-fest@^1.2.2: + version "1.4.0" + +type-fest@^2.13.0: + version "2.19.0" + +type-fest@^2.5.1: + version "2.19.0" + +typed-array-buffer@^1.0.2: + version "1.0.2" + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-typed-array "^1.1.13" + +typed-array-byte-length@^1.0.1: + version "1.0.1" + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-byte-offset@^1.0.2: + version "1.0.2" + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-length@^1.0.6: + version "1.0.6" + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + dependencies: + is-typedarray "^1.0.0" + +typedarray@^0.0.6: + version "0.0.6" + +"typescript@^4.6.4 || ^5.2.2", typescript@^5.2.2, typescript@>=2.7, "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta", typescript@>=4, typescript@>=4.9.5: + version "5.5.4" + +uglify-js@^3.1.4: + version "3.19.3" + +unbox-primitive@^1.0.2: + version "1.0.2" + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +unc-path-regex@^0.1.2: + version "0.1.2" + +undici-types@~6.19.2: + version "6.19.8" + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.0" + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.1.0: + version "2.1.0" + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.1.0" + +unique-string@^3.0.0: + version "3.0.0" + dependencies: + crypto-random-string "^4.0.0" + +universal-user-agent@^6.0.0: + version "6.0.1" + +universalify@^0.1.0: + version "0.1.2" + +universalify@^2.0.0: + version "2.0.1" + +unpipe@~1.0.0: + version "1.0.0" + +untildify@^4.0.0: + version "4.0.0" + +update-browserslist-db@^1.1.0: + version "1.1.0" + dependencies: + escalade "^3.1.2" + picocolors "^1.0.1" + +update-notifier@6.0.2: + version "6.0.2" + dependencies: + boxen "^7.0.0" + chalk "^5.0.1" + configstore "^6.0.0" + has-yarn "^3.0.0" + import-lazy "^4.0.0" + is-ci "^3.0.1" + is-installed-globally "^0.4.0" + is-npm "^6.0.0" + is-yarn-global "^0.4.0" + latest-version "^7.0.0" + pupa "^3.1.0" + semver "^7.3.7" + semver-diff "^4.0.0" + xdg-basedir "^5.1.0" + +uri-js@^4.2.2: + version "4.4.1" + dependencies: + punycode "^2.1.0" + +url-join@5.0.0: + version "5.0.0" + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + +utils-merge@1.0.1: + version "1.0.1" + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + +v8-to-istanbul@^9.0.1: + version "9.3.0" + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +validate-npm-package-license@^3.0.1: + version "3.0.4" + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +vary@~1.1.2: + version "1.1.2" + +vlq@^1.0.0: + version "1.0.1" + +vm2@^3.9.19: + version "3.9.19" + dependencies: + acorn "^8.7.0" + acorn-walk "^8.2.0" + +walker@^1.0.7, walker@^1.0.8: + version "1.0.8" + dependencies: + makeerror "1.0.12" + +wcwidth@^1.0.1: + version "1.0.1" + dependencies: + defaults "^1.0.3" + +web-streams-polyfill@^3.0.3: + version "3.3.3" + +webidl-conversions@^3.0.0: + version "3.0.1" + +whatwg-fetch@^3.0.0: + version "3.6.20" + +whatwg-url@^5.0.0: + version "5.0.0" + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-builtin-type@^1.1.3: + version "1.1.4" + dependencies: + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" + is-async-function "^2.0.0" + is-date-object "^1.0.5" + is-finalizationregistry "^1.0.2" + is-generator-function "^1.0.10" + is-regex "^1.1.4" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.2" + which-typed-array "^1.1.15" + +which-collection@^1.0.2: + version "1.0.2" + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + +which-module@^2.0.0: + version "2.0.1" + +which-typed-array@^1.1.14, which-typed-array@^1.1.15: + version "1.1.15" + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.2" + +which@^2.0.1, which@^2.0.2: + version "2.0.2" + dependencies: + isexe "^2.0.0" + +widest-line@^4.0.1: + version "4.0.1" + dependencies: + string-width "^5.0.1" + +wildcard-match@5.1.2: + version "5.1.2" + +windows-release@^5.0.1: + version "5.1.1" + dependencies: + execa "^5.1.1" + +word-wrap@^1.2.5, word-wrap@~1.2.3: + version "1.2.5" + +wordwrap@^1.0.0: + version "1.0.0" + +wrap-ansi@^6.0.1: + version "6.2.0" + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^6.2.0: + version "6.2.0" + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + +write-file-atomic@^2.3.0: + version "2.4.3" + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + +write-file-atomic@^3.0.3: + version "3.0.3" + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +write-file-atomic@^4.0.2: + version "4.0.2" + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +ws@^6.2.2, ws@^6.2.3: + version "6.2.3" + dependencies: + async-limiter "~1.0.0" + +ws@^7: + version "7.5.10" + +ws@^7.5.10: + version "7.5.10" + +xdg-basedir@^5.0.1, xdg-basedir@^5.1.0: + version "5.1.0" + +xtend@~4.0.1: + version "4.0.2" + +y18n@^4.0.0: + version "4.0.3" + +y18n@^5.0.5: + version "5.0.8" + +yallist@^3.0.2: + version "3.1.1" + +yallist@^4.0.0: + version "4.0.0" + +yaml@^2.2.1: + version "2.5.1" + +yargs-parser@^18.1.2: + version "18.1.3" + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-parser@^20.2.2, yargs-parser@^20.2.3, yargs-parser@^20.2.9: + version "20.2.9" + +yargs-parser@^21.1.1: + version "21.1.1" + +yargs-parser@21.1.1: + version "21.1.1" + +yargs@^15.1.0: + version "15.4.1" + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + +yargs@^16.2.0: + version "16.2.0" + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^17.0.0, yargs@^17.3.1, yargs@^17.5.1, yargs@^17.6.2: + version "17.7.2" + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yn@3.1.1: + version "3.1.1" + +yocto-queue@^0.1.0: + version "0.1.0"