52 | {body_open} 53 | "# 54 | ), 55 | format!( 56 | r#"{body_close} 57 | 58 | "# 59 | ), 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/into/html/mod.rs: -------------------------------------------------------------------------------- 1 | mod envelope; 2 | mod sphere; 3 | 4 | pub use envelope::*; 5 | pub use sphere::*; 6 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/into/html/static/styles.css: -------------------------------------------------------------------------------- 1 | .monospace { 2 | font-family: "IBM Plex Mono", monospace; 3 | } 4 | 5 | html, 6 | body { 7 | min-height: 100vh; 8 | 9 | --small-text-size: 0.85rem; 10 | --text-color: #10101a; 11 | --light-text-color: #44444c; 12 | --background-color: #fefefe; 13 | 14 | --transclude-background-color: rgba(0, 0, 0, 0.025); 15 | } 16 | 17 | body { 18 | font-family: "IBM Plex Sans", sans-serif; 19 | display: flex; 20 | box-sizing: border-box; 21 | flex-direction: column; 22 | margin: 0; 23 | padding: 1em; 24 | color: var(--text-color); 25 | background-color: var(--background-color); 26 | } 27 | 28 | a[href=""] { 29 | display: none; 30 | } 31 | 32 | ol.blocks { 33 | margin: 0; 34 | padding: 0; 35 | } 36 | 37 | ol.blocks > li { 38 | display: flex; 39 | flex-direction: column; 40 | margin: 0; 41 | padding: 0; 42 | } 43 | 44 | ul.sphere-transcludes, ul.block-transcludes { 45 | margin: 0; 46 | padding: 0; 47 | } 48 | 49 | .transclude-item { 50 | display: flex; 51 | margin: 0; 52 | padding: 0.85em; 53 | background-color: var(--transclude-background-color); 54 | border-radius: 0.25em; 55 | } 56 | 57 | .transclude-item:not(:last-child) { 58 | margin-bottom: 0.5em; 59 | } 60 | 61 | .transclude-format-text { 62 | display: flex; 63 | flex-direction: column; 64 | text-decoration: none; 65 | } 66 | 67 | .transclude-format-text > *:not(:last-child) { 68 | margin-bottom: 0.5em; 69 | } 70 | 71 | .transclude-format-text .title { 72 | font-weight: bold; 73 | } 74 | 75 | .transclude-format-text .excerpt { 76 | color: var(--text-color); 77 | } 78 | .transclude-format-text .link-text { 79 | color: var(--light-text-color); 80 | } 81 | 82 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/into/mod.rs: -------------------------------------------------------------------------------- 1 | mod html; 2 | 3 | pub use html::*; 4 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate implements transformations of Noosphere content into other 2 | //! content types. For the time being, the focus is on transforming Subtext 3 | //! to HTML. 4 | 5 | #[macro_use] 6 | extern crate tracing; 7 | 8 | mod into; 9 | mod resolver; 10 | mod transcluder; 11 | mod transform; 12 | mod write; 13 | 14 | pub use into::*; 15 | pub use resolver::*; 16 | pub use transcluder::*; 17 | pub use transform::*; 18 | pub use write::*; 19 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/resolver/html.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use async_trait::async_trait; 3 | use subtext::{Peer, Slashlink}; 4 | 5 | use crate::{ResolvedLink, Resolver}; 6 | 7 | /// A [Resolver] that is suitable for resolving a [Slashlink] to an `href` for 8 | /// a basic static website generator. 9 | #[derive(Clone)] 10 | pub struct StaticHtmlResolver(); 11 | 12 | #[cfg_attr(not(target_arch = "wasm32"), async_trait)] 13 | #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] 14 | impl Resolver for StaticHtmlResolver { 15 | async fn resolve(&self, link: &Slashlink) -> Result { 16 | match link { 17 | Slashlink { 18 | slug: Some(slug), 19 | peer: Peer::None, 20 | } => Ok(ResolvedLink::Slashlink { 21 | link: link.clone(), 22 | href: format!("/{slug}"), 23 | }), 24 | _ => Err(anyhow!("Only local slashlinks with slugs are supported")), 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/resolver/link.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use subtext::Slashlink; 4 | 5 | #[cfg(doc)] 6 | use crate::Resolver; 7 | #[cfg(doc)] 8 | use crate::Transcluder; 9 | 10 | /// This enum represents the resolved value that may be returned by a [Resolver] 11 | /// and is provided to a [Transcluder] 12 | pub enum ResolvedLink { 13 | Hyperlink { href: String }, 14 | Slashlink { link: Slashlink, href: String }, 15 | } 16 | 17 | impl Display for ResolvedLink { 18 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 19 | let href = match self { 20 | ResolvedLink::Hyperlink { href } => href, 21 | ResolvedLink::Slashlink { href, .. } => href, 22 | }; 23 | write!(f, "{href}") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/resolver/mod.rs: -------------------------------------------------------------------------------- 1 | mod html; 2 | mod link; 3 | mod resolver_implementation; 4 | 5 | pub use html::*; 6 | pub use link::*; 7 | pub use resolver_implementation::*; 8 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/resolver/resolver_implementation.rs: -------------------------------------------------------------------------------- 1 | use crate::ResolvedLink; 2 | use anyhow::Result; 3 | use async_trait::async_trait; 4 | use subtext::Slashlink; 5 | 6 | #[cfg(not(target_arch = "wasm32"))] 7 | pub trait ResolverConditionalSendSync: Send + Sync {} 8 | 9 | #[cfg(not(target_arch = "wasm32"))] 10 | impl ResolverConditionalSendSync for S where S: Send + Sync {} 11 | 12 | #[cfg(target_arch = "wasm32")] 13 | pub trait ResolverConditionalSendSync {} 14 | 15 | #[cfg(target_arch = "wasm32")] 16 | impl ResolverConditionalSendSync for S {} 17 | 18 | /// A [Resolver] is given a [Slashlink] and resolves it to a [ResolvedLink] 19 | /// which includes an href, which is a URL string that can be used to link 20 | /// to the content referred to by the [Slashlink] over the hypertext web. 21 | #[cfg_attr(not(target_arch = "wasm32"), async_trait)] 22 | #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] 23 | pub trait Resolver: Clone + ResolverConditionalSendSync { 24 | async fn resolve(&self, link: &Slashlink) -> Result; 25 | } 26 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/transcluder/mod.rs: -------------------------------------------------------------------------------- 1 | mod content; 2 | mod transclude; 3 | mod transcluder_implementation; 4 | 5 | pub use content::*; 6 | pub use transclude::*; 7 | pub use transcluder_implementation::*; 8 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/transcluder/transclude.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug)] 2 | pub struct TextTransclude { 3 | pub title: Option, 4 | pub excerpt: Option, 5 | pub link_text: String, 6 | pub href: String, 7 | } 8 | 9 | /// The set of possible transcludes that may need to be rendered to a target 10 | /// format. At this time, only text transcludes are supported. 11 | #[derive(Clone, Debug)] 12 | pub enum Transclude { 13 | // TODO 14 | // Rich, 15 | // Interactive, 16 | // Bitmap, 17 | Text(TextTransclude), 18 | } 19 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/transcluder/transcluder_implementation.rs: -------------------------------------------------------------------------------- 1 | use crate::{ResolvedLink, Transclude}; 2 | 3 | use anyhow::Result; 4 | use async_trait::async_trait; 5 | 6 | #[cfg(not(target_arch = "wasm32"))] 7 | pub trait TranscluderConditionalSendSync: Send + Sync {} 8 | 9 | #[cfg(not(target_arch = "wasm32"))] 10 | impl TranscluderConditionalSendSync for S where S: Send + Sync {} 11 | 12 | #[cfg(target_arch = "wasm32")] 13 | pub trait TranscluderConditionalSendSync {} 14 | 15 | #[cfg(target_arch = "wasm32")] 16 | impl TranscluderConditionalSendSync for S {} 17 | /// A [Transcluder] is responsible for taking a slug and generating a transclude 18 | /// for the content that the slug refers to. 19 | #[cfg_attr(not(target_arch = "wasm32"), async_trait)] 20 | #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] 21 | pub trait Transcluder: Clone + TranscluderConditionalSendSync { 22 | /// Given a [ResolvedLink], produce a [Transclude] if it is appropriate to 23 | /// do so. 24 | async fn transclude(&self, link: &ResolvedLink) -> Result>; 25 | } 26 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/transform/file.rs: -------------------------------------------------------------------------------- 1 | use async_stream::stream; 2 | use futures::Stream; 3 | use noosphere_core::context::SphereFile; 4 | use noosphere_core::data::ContentType; 5 | use tokio::io::AsyncRead; 6 | 7 | use crate::{subtext_to_html_document_stream, subtext_to_html_fragment_stream, Transform}; 8 | 9 | /// Used to configure the output format of the [file_to_html_stream] transform 10 | pub enum HtmlOutput { 11 | /// Output as a full HTML document 12 | Document, 13 | /// Output as just a body content fragment 14 | Fragment, 15 | } 16 | 17 | /// Given a [Transform], a [SphereFile] and an [HtmlOutput], perform a streamed 18 | /// transformation of the [SphereFile] into HTML. The transformation that is 19 | /// performed may vary depending on content type. At this time, only Subtext 20 | /// is supported. 21 | pub fn file_to_html_stream( 22 | file: SphereFile, 23 | output: HtmlOutput, 24 | transform: T, 25 | ) -> impl Stream 26 | where 27 | T: Transform, 28 | R: AsyncRead + Unpin, 29 | { 30 | stream! { 31 | match file.memo.content_type() { 32 | Some(ContentType::Subtext) => { 33 | match output { 34 | HtmlOutput::Document => { 35 | let stream = subtext_to_html_document_stream(transform, file); 36 | for await part in stream { 37 | yield part; 38 | } 39 | }, 40 | HtmlOutput::Fragment => { 41 | let stream = subtext_to_html_fragment_stream(file, transform); 42 | for await part in stream { 43 | yield part; 44 | } 45 | } 46 | }; 47 | } 48 | _ => { 49 | yield "

Format cannot be rendered as HTML

".into(); 50 | } 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/transform/mod.rs: -------------------------------------------------------------------------------- 1 | mod file; 2 | mod sphere; 3 | mod stream; 4 | mod subtext; 5 | mod transform_implementation; 6 | 7 | pub use self::subtext::*; 8 | pub use file::*; 9 | pub use sphere::*; 10 | pub use stream::*; 11 | pub use transform_implementation::*; 12 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/transform/sphere/mod.rs: -------------------------------------------------------------------------------- 1 | mod html; 2 | 3 | pub use html::*; 4 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/transform/stream.rs: -------------------------------------------------------------------------------- 1 | use async_stream::stream; 2 | use bytes::Bytes; 3 | use futures::Stream; 4 | use std::io::Error as IoError; 5 | use tokio_util::io::StreamReader; 6 | 7 | #[cfg(doc)] 8 | use tokio::io::AsyncRead; 9 | 10 | /// This is a helper for taking a [Stream] of strings and converting it 11 | /// to an [AsyncRead] suitable for writing to a file. 12 | pub struct TransformStream(pub S) 13 | where 14 | S: Stream; 15 | 16 | impl TransformStream 17 | where 18 | S: Stream, 19 | { 20 | /// Consume the [TransformStream] and return a [StreamReader] that yields 21 | /// the stream as bytes. 22 | pub fn into_reader(self) -> StreamReader>, Bytes> { 23 | StreamReader::new(Box::pin(stream! { 24 | for await part in self.0 { 25 | yield Ok(Bytes::from(part)); 26 | } 27 | })) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/transform/subtext/html/document.rs: -------------------------------------------------------------------------------- 1 | use crate::{html_document_envelope, subtext_to_html_fragment_stream, Transform}; 2 | use async_stream::stream; 3 | use futures::Stream; 4 | use noosphere_core::context::SphereFile; 5 | use tokio::io::AsyncRead; 6 | 7 | /// Given a [Transform] and a [SphereFile], produce a stream that yields the 8 | /// file content as an HTML document 9 | pub fn subtext_to_html_document_stream( 10 | transform: T, 11 | file: SphereFile, 12 | ) -> impl Stream 13 | where 14 | T: Transform, 15 | R: AsyncRead + Unpin, 16 | { 17 | stream! { 18 | let (html_prefix, html_suffix) = html_document_envelope(&file.memo); 19 | let fragment_stream = subtext_to_html_fragment_stream(file, transform); 20 | 21 | yield html_prefix; 22 | 23 | for await fragment_part in fragment_stream { 24 | yield fragment_part; 25 | } 26 | 27 | yield html_suffix; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/transform/subtext/html/mod.rs: -------------------------------------------------------------------------------- 1 | mod document; 2 | mod fragment; 3 | 4 | pub use document::*; 5 | pub use fragment::*; 6 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/transform/subtext/mod.rs: -------------------------------------------------------------------------------- 1 | mod html; 2 | 3 | pub use html::*; 4 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/transform/transform_implementation.rs: -------------------------------------------------------------------------------- 1 | use noosphere_core::context::HasSphereContext; 2 | use noosphere_storage::Storage; 3 | 4 | use crate::{Resolver, SphereContentTranscluder, StaticHtmlResolver, Transcluder}; 5 | 6 | /// A [Transform] represents the combination of a [Resolver] and a 7 | /// [Transcluder]. Together these elements form a transformation over 8 | /// some input Noosphere content. 9 | pub trait Transform: Clone { 10 | type Resolver: Resolver; 11 | type Transcluder: Transcluder; 12 | 13 | fn resolver(&self) -> &Self::Resolver; 14 | fn transcluder(&self) -> &Self::Transcluder; 15 | } 16 | 17 | /// A [Transform] that is suitable for converting Noosphere content to 18 | /// HTML for a basic state website generator. 19 | #[derive(Clone)] 20 | pub struct StaticHtmlTransform 21 | where 22 | R: HasSphereContext + Clone, 23 | S: Storage + 'static, 24 | { 25 | pub resolver: StaticHtmlResolver, 26 | pub transcluder: SphereContentTranscluder, 27 | } 28 | 29 | impl StaticHtmlTransform 30 | where 31 | R: HasSphereContext + Clone, 32 | S: Storage + 'static, 33 | { 34 | pub fn new(content: R) -> Self { 35 | StaticHtmlTransform { 36 | resolver: StaticHtmlResolver(), 37 | transcluder: SphereContentTranscluder::new(content), 38 | } 39 | } 40 | } 41 | 42 | impl Transform for StaticHtmlTransform 43 | where 44 | R: HasSphereContext + Clone, 45 | S: Storage + 'static, 46 | { 47 | type Resolver = StaticHtmlResolver; 48 | 49 | type Transcluder = SphereContentTranscluder; 50 | 51 | fn resolver(&self) -> &Self::Resolver { 52 | &self.resolver 53 | } 54 | 55 | fn transcluder(&self) -> &Self::Transcluder { 56 | &self.transcluder 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/write/mod.rs: -------------------------------------------------------------------------------- 1 | mod memory; 2 | mod target; 3 | 4 | pub use memory::*; 5 | pub use target::*; 6 | 7 | #[cfg(not(target_arch = "wasm32"))] 8 | mod native; 9 | #[cfg(not(target_arch = "wasm32"))] 10 | pub use native::*; 11 | -------------------------------------------------------------------------------- /rust/noosphere-into/src/write/target.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::future::Future; 3 | use std::path::Path; 4 | 5 | use async_trait::async_trait; 6 | use tokio::io::AsyncRead; 7 | 8 | #[cfg(not(target_arch = "wasm32"))] 9 | pub trait WriteTargetConditionalSend: Send {} 10 | 11 | #[cfg(not(target_arch = "wasm32"))] 12 | pub trait WriteTargetConditionalSendSync: Send + Sync {} 13 | 14 | #[cfg(target_arch = "wasm32")] 15 | pub trait WriteTargetConditionalSend {} 16 | 17 | #[cfg(target_arch = "wasm32")] 18 | pub trait WriteTargetConditionalSendSync {} 19 | 20 | /// An interface for accessing durable storage. This is used by transformers 21 | /// in this crate to render files from Noosphere content. 22 | #[cfg_attr(not(target_arch = "wasm32"), async_trait)] 23 | #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] 24 | pub trait WriteTarget: Clone + WriteTargetConditionalSendSync { 25 | /// Returns true if a file exists at the provided path 26 | async fn exists(&self, path: &Path) -> Result; 27 | 28 | /// Given a path and an [AsyncRead], write the contents of the [AsyncRead] 29 | /// to the path 30 | async fn write(&self, path: &Path, contents: R) -> Result<()> 31 | where 32 | R: AsyncRead + Unpin + WriteTargetConditionalSend; 33 | 34 | /// Create a symbolic link between the give source path and destination path 35 | async fn symlink(&self, src: &Path, dst: &Path) -> Result<()>; 36 | 37 | /// Spawn a [Future] in a platform-appropriate fashion and poll it to 38 | /// completion 39 | async fn spawn(future: F) -> Result<()> 40 | where 41 | F: Future> + WriteTargetConditionalSend + 'static; 42 | } 43 | 44 | impl WriteTargetConditionalSendSync for W where W: WriteTarget {} 45 | 46 | #[cfg(not(target_arch = "wasm32"))] 47 | impl WriteTargetConditionalSend for S where S: Send {} 48 | 49 | #[cfg(target_arch = "wasm32")] 50 | impl WriteTargetConditionalSend for S {} 51 | -------------------------------------------------------------------------------- /rust/noosphere-ipfs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "noosphere-ipfs" 3 | version = "0.8.6" 4 | edition = "2021" 5 | description = "An interface for an IPFS client." 6 | keywords = [ 7 | "ipfs", 8 | "noosphere", 9 | "p2p", 10 | ] 11 | categories = [ 12 | "network-programming", 13 | "asynchronous", 14 | ] 15 | rust-version = "1.75.0" 16 | license = "MIT OR Apache-2.0" 17 | documentation = "https://docs.rs/noosphere-ipfs" 18 | repository = "https://github.com/subconsciousnetwork/noosphere" 19 | homepage = "https://github.com/subconsciousnetwork/noosphere" 20 | readme = "README.md" 21 | 22 | [features] 23 | default = ["storage"] 24 | storage = ["noosphere-ucan"] 25 | test-kubo = [] 26 | 27 | [dependencies] 28 | anyhow = { workspace = true } 29 | async-trait = { workspace = true } 30 | async-stream = { workspace = true } 31 | libipld-core = { workspace = true } 32 | libipld-cbor = { workspace = true } 33 | cid = { workspace = true } 34 | reqwest = { workspace = true, default-features = false, features = ["json", "rustls-tls", "stream"] } 35 | serde = { workspace = true } 36 | serde_json = { workspace = true } 37 | tokio = { workspace = true, features = ["io-util"] } 38 | tokio-stream = { workspace = true } 39 | tokio-util = { workspace = true, features = ["compat"] } 40 | tracing = { workspace = true } 41 | url = { workspace = true, features = [ "serde" ] } 42 | noosphere-storage = { workspace = true } 43 | noosphere-common = { workspace = true } 44 | noosphere-ucan = { workspace = true, optional = true } 45 | 46 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 47 | hyper = { version = "^0.14.27", features = ["full"] } 48 | hyper-multipart-rfc7578 = "~0.8" 49 | ipfs-api-prelude = "0.6" 50 | 51 | [dev-dependencies] 52 | rand = { workspace = true } 53 | iroh-car = { workspace = true } 54 | libipld-cbor = { workspace = true } 55 | libipld-json = { workspace = true } 56 | noosphere-core = { workspace = true } 57 | -------------------------------------------------------------------------------- /rust/noosphere-ipfs/README.md: -------------------------------------------------------------------------------- 1 | ![API Stability: Alpha](https://img.shields.io/badge/API%20Stability-Alpha-red) 2 | 3 | # noosphere-ipfs 4 | 5 | This crate includes helpers and adapters to support syndicating Noosphere 6 | content to the IPFS network through both Kubo and other implementations, and 7 | various traits used for interacting with IPFS. 8 | -------------------------------------------------------------------------------- /rust/noosphere-ipfs/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate tracing; 3 | 4 | mod client; 5 | #[cfg(feature = "storage")] 6 | mod storage; 7 | 8 | pub use client::*; 9 | 10 | #[cfg(feature = "storage")] 11 | pub use storage::*; 12 | -------------------------------------------------------------------------------- /rust/noosphere-ns/README.md: -------------------------------------------------------------------------------- 1 | ![API Stability: Alpha](https://img.shields.io/badge/API%20Stability-Alpha-red) 2 | 3 | # noosphere-ns 4 | 5 | Noosphere's P2P name system. 6 | 7 | ## Running nodes in Noosphere Name System 8 | 9 | The Noosphere Name System (`orb-ns`) binary target is an executable that runs one or many bootstrap 10 | nodes, based on configuration. 11 | 12 | ``` 13 | cargo run --bin orb-ns -- run --key my-key-name --port 6666 14 | ``` 15 | -------------------------------------------------------------------------------- /rust/noosphere-ns/src/bin/orb-ns/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_arch = "wasm32")] 2 | fn main() -> Result<(), Box> { 3 | Ok(()) 4 | } 5 | 6 | #[cfg(not(target_arch = "wasm32"))] 7 | pub mod cli; 8 | 9 | #[cfg(not(target_arch = "wasm32"))] 10 | mod runner; 11 | 12 | #[cfg(not(target_arch = "wasm32"))] 13 | mod utils; 14 | 15 | #[cfg(not(target_arch = "wasm32"))] 16 | mod inner { 17 | pub use anyhow::{anyhow, Result}; 18 | pub use noosphere::key::InsecureKeyStorage; 19 | pub use noosphere_core::tracing::initialize_tracing; 20 | pub use tokio; 21 | } 22 | 23 | #[cfg(not(target_arch = "wasm32"))] 24 | use inner::*; 25 | 26 | #[cfg(not(target_arch = "wasm32"))] 27 | #[tokio::main(flavor = "multi_thread")] 28 | async fn main() -> Result<()> { 29 | initialize_tracing(None); 30 | 31 | let key_storage = InsecureKeyStorage::new(&utils::get_keys_dir()?)?; 32 | cli::process_args(&key_storage) 33 | .await 34 | .map_err(|s| anyhow!(s)) 35 | } 36 | -------------------------------------------------------------------------------- /rust/noosphere-ns/src/bin/orb-ns/runner/mod.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod runner_implementation; 3 | 4 | pub use config::RunnerNodeConfig; 5 | pub use runner_implementation::NameSystemRunner; 6 | -------------------------------------------------------------------------------- /rust/noosphere-ns/src/bin/orb-ns/utils.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | 3 | use noosphere::key::{InsecureKeyStorage, KeyStorage}; 4 | use noosphere_ucan::key_material::ed25519::Ed25519KeyMaterial; 5 | use std::path::PathBuf; 6 | 7 | pub async fn get_key_material( 8 | key_storage: &InsecureKeyStorage, 9 | key_name: &str, 10 | ) -> Result { 11 | if let Some(km) = key_storage.read_key(key_name).await?.take() { 12 | Ok(km) 13 | } else { 14 | Err(anyhow!( 15 | "No key \"{}\" found in `~/.noosphere/keys/`.", 16 | key_name 17 | )) 18 | } 19 | } 20 | 21 | pub fn get_keys_dir() -> Result { 22 | Ok(home::home_dir() 23 | .ok_or_else(|| anyhow!("Could not discover home directory."))? 24 | .join(".noosphere")) 25 | } 26 | -------------------------------------------------------------------------------- /rust/noosphere-ns/src/dht/mod.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod errors; 3 | mod node; 4 | mod processor; 5 | mod rpc; 6 | mod swarm; 7 | mod types; 8 | mod validator; 9 | 10 | pub use config::DhtConfig; 11 | pub use errors::DhtError; 12 | pub use node::DhtNode; 13 | pub use types::{DhtRecord, NetworkInfo, Peer}; 14 | pub use validator::{AllowAllValidator, Validator}; 15 | -------------------------------------------------------------------------------- /rust/noosphere-ns/src/dht/types.rs: -------------------------------------------------------------------------------- 1 | use libp2p::{swarm::NetworkInfo as LibP2pNetworkInfo, PeerId}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use std::{fmt, str}; 5 | 6 | #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] 7 | pub struct NetworkInfo { 8 | pub num_peers: usize, 9 | pub num_connections: u32, 10 | pub num_pending: u32, 11 | pub num_established: u32, 12 | } 13 | 14 | impl From for NetworkInfo { 15 | fn from(info: LibP2pNetworkInfo) -> Self { 16 | let c = info.connection_counters(); 17 | NetworkInfo { 18 | num_peers: info.num_peers(), 19 | num_connections: c.num_connections(), 20 | num_pending: c.num_pending(), 21 | num_established: c.num_established(), 22 | } 23 | } 24 | } 25 | 26 | #[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)] 27 | pub struct Peer { 28 | pub peer_id: PeerId, 29 | } 30 | 31 | #[derive(Debug, Clone)] 32 | pub struct DhtRecord { 33 | pub key: Vec, 34 | pub value: Option>, 35 | } 36 | 37 | impl fmt::Display for DhtRecord { 38 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 39 | let value = if let Some(value) = self.value.as_ref() { 40 | str::from_utf8(value) 41 | } else { 42 | Ok("None") 43 | }; 44 | write!( 45 | fmt, 46 | "DHTRecord {{ key: {:?}, value: {:?} }}", 47 | str::from_utf8(&self.key), 48 | value 49 | ) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /rust/noosphere-ns/src/dht/validator.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | 3 | /// Trait that implements a `validate` function that determines 4 | /// what records can be set and stored on the [crate::dht::DHTNode]. 5 | /// Currently only validates "Value" records. 6 | /// 7 | /// # Example 8 | /// 9 | /// ``` 10 | /// use noosphere_ns::dht::Validator; 11 | /// use async_trait::async_trait; 12 | /// use tokio; 13 | /// 14 | /// #[derive(Clone)] 15 | /// struct MyValidator; 16 | /// 17 | /// #[async_trait] 18 | /// impl Validator for MyValidator { 19 | /// // Ensures value is "hello" in bytes. 20 | /// async fn validate(&mut self, data: &[u8]) -> bool { 21 | /// data[..] == [104, 101, 108, 108, 111][..] 22 | /// } 23 | /// } 24 | /// 25 | /// #[tokio::main(flavor = "multi_thread")] 26 | /// async fn main() { 27 | /// let mut validator = MyValidator {}; 28 | /// let data = String::from("hello").into_bytes(); 29 | /// let is_valid = validator.validate(&data).await; 30 | /// assert!(is_valid); 31 | /// } 32 | #[async_trait] 33 | pub trait Validator: Send + Sync { 34 | async fn validate(&mut self, record_value: &[u8]) -> bool; 35 | } 36 | 37 | /// An implementation of [Validator] that allows all records. 38 | /// Used for tests. 39 | #[derive(Clone)] 40 | pub struct AllowAllValidator {} 41 | 42 | #[async_trait] 43 | impl Validator for AllowAllValidator { 44 | async fn validate(&mut self, _data: &[u8]) -> bool { 45 | true 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /rust/noosphere-ns/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(not(target_arch = "wasm32"))] 2 | 3 | #[macro_use] 4 | extern crate tracing; 5 | 6 | #[macro_use] 7 | extern crate lazy_static; 8 | 9 | mod builder; 10 | pub mod dht; 11 | mod dht_client; 12 | pub mod helpers; 13 | mod name_resolver; 14 | mod name_system; 15 | pub mod utils; 16 | mod validator; 17 | 18 | //#[cfg(feature = "api_server")] 19 | pub mod server; 20 | 21 | pub use builder::NameSystemBuilder; 22 | pub use dht::{DhtConfig, NetworkInfo, Peer}; 23 | pub use dht_client::DhtClient; 24 | pub use libp2p::{multiaddr::Multiaddr, PeerId}; 25 | pub use name_resolver::NameResolver; 26 | pub use name_system::{NameSystem, NameSystemKeyMaterial, BOOTSTRAP_PEERS}; 27 | -------------------------------------------------------------------------------- /rust/noosphere-ns/src/server/mod.rs: -------------------------------------------------------------------------------- 1 | mod client; 2 | mod handlers; 3 | mod implementation; 4 | mod routes; 5 | 6 | pub use client::HttpClient; 7 | pub use implementation::{start_name_system_api_server, ApiServer}; 8 | -------------------------------------------------------------------------------- /rust/noosphere-ns/src/server/routes.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | pub const API_VERSION: &str = "v0alpha1"; 4 | 5 | pub enum Route { 6 | NetworkInfo, 7 | GetPeerId, 8 | 9 | GetPeers, 10 | AddPeers, 11 | 12 | Listen, 13 | StopListening, 14 | Address, 15 | 16 | GetRecord, 17 | PostRecord, 18 | 19 | Bootstrap, 20 | } 21 | 22 | impl Display for Route { 23 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 24 | let fragment = match self { 25 | Route::NetworkInfo => "network_info", 26 | Route::GetPeerId => "peer_id", 27 | 28 | Route::GetPeers => "peers", 29 | Route::AddPeers => "peers/*addr", 30 | 31 | Route::Listen => "listen/*addr", 32 | Route::StopListening => "listen", 33 | Route::Address => "addresses", 34 | 35 | Route::GetRecord => "records/:identity", 36 | Route::PostRecord => "records", 37 | 38 | Route::Bootstrap => "bootstrap", 39 | }; 40 | 41 | write!(f, "/api/{API_VERSION}/{fragment}") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /rust/noosphere-ns/src/validator.rs: -------------------------------------------------------------------------------- 1 | use crate::dht::Validator; 2 | use async_trait::async_trait; 3 | use noosphere_core::data::LinkRecord; 4 | use noosphere_ucan::store::UcanStore; 5 | 6 | /// Implements [Validator] for the DHT. 7 | pub(crate) struct RecordValidator { 8 | store: S, 9 | } 10 | 11 | impl RecordValidator 12 | where 13 | S: UcanStore, 14 | { 15 | pub fn new(store: S) -> Self { 16 | RecordValidator { store } 17 | } 18 | } 19 | 20 | #[async_trait] 21 | impl Validator for RecordValidator 22 | where 23 | S: UcanStore, 24 | { 25 | async fn validate(&mut self, record_value: &[u8]) -> bool { 26 | match LinkRecord::try_from(record_value) { 27 | Ok(record) => record.validate(&self.store).await.is_ok(), 28 | _ => false, 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /rust/noosphere-storage/README.md: -------------------------------------------------------------------------------- 1 | ![API Stability: Alpha](https://img.shields.io/badge/API%20Stability-Alpha-red) 2 | 3 | # Noosphere Storage 4 | 5 | The Rust implementation of Noosphere supports pluggable backing storage. This 6 | crate defines the trait that must be implemented by a storage implementation, 7 | and also contains ready-to-use implementations for native file storage (backed 8 | by Sled), in-memory storage and web browser storage (backed by IndexedDB). 9 | -------------------------------------------------------------------------------- /rust/noosphere-storage/src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::storage::Storage; 2 | use anyhow::Result; 3 | use async_trait::async_trait; 4 | use noosphere_common::ConditionalSend; 5 | use std::path::Path; 6 | 7 | /// Generalized configurations for [ConfigurableStorage]. 8 | #[derive(Debug, Clone, Default)] 9 | pub struct StorageConfig { 10 | /// If set, the size limit in bytes of a memory-based cache. 11 | pub memory_cache_limit: Option, 12 | } 13 | 14 | /// [Storage] that can be customized via [StorageConfig]. 15 | /// 16 | /// Configurations are generalized across storage providers, 17 | /// and may have differing underlying semantics. 18 | #[cfg_attr(not(target_arch = "wasm32"), async_trait)] 19 | #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] 20 | pub trait ConfigurableStorage: Storage { 21 | async fn open_with_config + ConditionalSend>( 22 | path: P, 23 | config: StorageConfig, 24 | ) -> Result; 25 | } 26 | -------------------------------------------------------------------------------- /rust/noosphere-storage/src/helpers.rs: -------------------------------------------------------------------------------- 1 | use crate::Storage; 2 | use anyhow::Result; 3 | 4 | #[cfg(not(target_arch = "wasm32"))] 5 | use crate::{SledStorage, SledStore}; 6 | 7 | #[cfg(not(target_arch = "wasm32"))] 8 | pub async fn make_disposable_store() -> Result { 9 | let temp_dir = std::env::temp_dir(); 10 | let temp_name: String = witty_phrase_generator::WPGen::new() 11 | .with_words(3) 12 | .unwrap() 13 | .into_iter() 14 | .map(String::from) 15 | .collect(); 16 | let provider = SledStorage::new(temp_dir.join(temp_name))?; 17 | provider.get_block_store("foo").await 18 | } 19 | 20 | #[cfg(target_arch = "wasm32")] 21 | use crate::{IndexedDbStorage, IndexedDbStore}; 22 | 23 | #[cfg(target_arch = "wasm32")] 24 | pub async fn make_disposable_store() -> Result { 25 | let temp_name: String = witty_phrase_generator::WPGen::new() 26 | .with_words(3) 27 | .unwrap() 28 | .into_iter() 29 | .map(|word| String::from(word)) 30 | .collect(); 31 | 32 | let provider = IndexedDbStorage::new(&temp_name).await?; 33 | provider.get_block_store(crate::db::BLOCK_STORE).await 34 | } 35 | -------------------------------------------------------------------------------- /rust/noosphere-storage/src/implementation/mod.rs: -------------------------------------------------------------------------------- 1 | mod memory; 2 | mod tracking; 3 | 4 | pub use memory::*; 5 | pub use tracking::*; 6 | 7 | #[cfg(not(target_arch = "wasm32"))] 8 | mod sled; 9 | #[cfg(not(target_arch = "wasm32"))] 10 | pub use self::sled::*; 11 | 12 | #[cfg(all(not(target_arch = "wasm32"), feature = "rocksdb"))] 13 | mod rocks_db; 14 | #[cfg(all(not(target_arch = "wasm32"), feature = "rocksdb"))] 15 | pub use rocks_db::*; 16 | 17 | #[cfg(target_arch = "wasm32")] 18 | mod indexed_db; 19 | #[cfg(target_arch = "wasm32")] 20 | pub use indexed_db::*; 21 | -------------------------------------------------------------------------------- /rust/noosphere-storage/src/key_value.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use anyhow::{anyhow, Result}; 4 | use async_trait::async_trait; 5 | use noosphere_common::{ConditionalSend, ConditionalSync}; 6 | use serde::{de::DeserializeOwned, Serialize}; 7 | 8 | /// A [KeyValueStore] is a construct that is suitable for persisting generic 9 | /// key/value data to a storage backend. 10 | #[cfg_attr(not(target_arch = "wasm32"), async_trait)] 11 | #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] 12 | pub trait KeyValueStore: Clone + ConditionalSync { 13 | /// Given some key that can be realized as bytes, persist a serializable 14 | /// value to storage so that it can later be retrieved by that key 15 | async fn set_key(&mut self, key: K, value: V) -> Result<()> 16 | where 17 | K: AsRef<[u8]> + ConditionalSend, 18 | V: Serialize + ConditionalSend; 19 | 20 | /// Given some key that can be realized as bytes, retrieve some data that 21 | /// can be deserialized as the intended data structure 22 | async fn get_key(&self, key: K) -> Result> 23 | where 24 | K: AsRef<[u8]> + ConditionalSend, 25 | V: DeserializeOwned + ConditionalSend; 26 | 27 | /// Given some key that can be realized as bytes, unset the value stored 28 | /// against that key (if any) 29 | async fn unset_key(&mut self, key: K) -> Result<()> 30 | where 31 | K: AsRef<[u8]> + ConditionalSend; 32 | 33 | /// Same as get_key, but returns an error if no value is found to be stored 34 | /// against the key 35 | async fn require_key(&self, key: K) -> Result 36 | where 37 | K: AsRef<[u8]> + ConditionalSend + Display, 38 | V: DeserializeOwned + ConditionalSend, 39 | { 40 | let required = key.to_string(); 41 | 42 | match self.get_key(key).await? { 43 | Some(value) => Ok(value), 44 | None => Err(anyhow!("No value found for '{required}'")), 45 | } 46 | } 47 | 48 | /// Flushes pending writes if there are any 49 | async fn flush(&self) -> Result<()> { 50 | Ok(()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /rust/noosphere-storage/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate contains generic interfaces and concrete implementations to 2 | //! support a common API for data persistance in Noosphere on many different 3 | //! platforms. Current platforms include native targets (via disk-persisted K/V 4 | //! store) and web browsers (via IndexedDB). 5 | 6 | #[macro_use] 7 | extern crate tracing; 8 | 9 | mod block; 10 | mod config; 11 | mod db; 12 | mod encoding; 13 | mod implementation; 14 | mod key_value; 15 | mod retry; 16 | mod storage; 17 | mod store; 18 | mod tap; 19 | mod ucan; 20 | 21 | pub use crate::ucan::*; 22 | pub use block::*; 23 | pub use config::*; 24 | pub use db::*; 25 | pub use encoding::*; 26 | pub use implementation::*; 27 | pub use key_value::*; 28 | pub use retry::*; 29 | pub use storage::*; 30 | pub use store::*; 31 | pub use tap::*; 32 | 33 | mod space; 34 | pub use space::*; 35 | 36 | #[cfg(test)] 37 | pub mod helpers; 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use crate::{block::BlockStore, helpers::make_disposable_store}; 42 | 43 | use libipld_cbor::DagCborCodec; 44 | #[cfg(target_arch = "wasm32")] 45 | use wasm_bindgen_test::wasm_bindgen_test; 46 | 47 | wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); 48 | 49 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 50 | #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] 51 | async fn it_can_store_and_retrieve_bytes() { 52 | let mut storage = make_disposable_store().await.unwrap(); 53 | let bytes = b"I love every kind of cat"; 54 | 55 | let cid = storage.save::(bytes).await.unwrap(); 56 | let retrieved = storage.load::>(&cid).await.unwrap(); 57 | 58 | assert_eq!(retrieved, bytes); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /rust/noosphere-storage/src/space.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use async_trait::async_trait; 3 | use noosphere_common::ConditionalSend; 4 | 5 | /// [Space] is a general trait for a storage provider to provide 6 | /// a the size on disk, used to calculate space amplification. 7 | #[cfg_attr(not(target_arch = "wasm32"), async_trait)] 8 | #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] 9 | pub trait Space: ConditionalSend { 10 | /// Get the underlying (e.g. disk) space usage of a storage provider. 11 | async fn get_space_usage(&self) -> Result; 12 | } 13 | 14 | #[allow(unused)] 15 | #[cfg(not(target_arch = "wasm32"))] 16 | pub(crate) async fn get_dir_size(path: impl Into) -> Result { 17 | use std::{future::Future, pin::Pin}; 18 | 19 | let path = path.into(); 20 | fn dir_size(mut dir: tokio::fs::ReadDir) -> Pin> + Send>> { 21 | Box::pin(async move { 22 | let mut total_size = 0; 23 | while let Some(entry) = dir.next_entry().await? { 24 | let size = match entry.metadata().await? { 25 | data if data.is_dir() => { 26 | dir_size(tokio::fs::read_dir(entry.path()).await?).await? 27 | } 28 | data => data.len(), 29 | }; 30 | total_size += size; 31 | } 32 | Ok(total_size) 33 | }) 34 | } 35 | 36 | dir_size(tokio::fs::read_dir(path).await?).await 37 | } 38 | -------------------------------------------------------------------------------- /rust/noosphere-storage/src/storage.rs: -------------------------------------------------------------------------------- 1 | use crate::block::BlockStore; 2 | use crate::key_value::KeyValueStore; 3 | use anyhow::Result; 4 | use async_trait::async_trait; 5 | use noosphere_common::ConditionalSync; 6 | use std::fmt::Debug; 7 | 8 | /// [Storage] is a general trait for composite storage backends. It is often the 9 | /// case that we are able to use a single storage primitive for all forms of 10 | /// storage, but sometimes block storage and generic key/value storage come from 11 | /// different backends. [Storage] provides a composite interface where both 12 | /// cases well accomodated without creating complexity in the signatures of 13 | /// other Noosphere constructs. 14 | #[cfg_attr(not(target_arch = "wasm32"), async_trait)] 15 | #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] 16 | pub trait Storage: Clone + ConditionalSync + Debug { 17 | type BlockStore: BlockStore; 18 | type KeyValueStore: KeyValueStore; 19 | 20 | /// Get a [BlockStore] where all values stored in it are scoped to the given 21 | /// name 22 | async fn get_block_store(&self, name: &str) -> Result; 23 | 24 | /// Get a [KeyValueStore] where all values stored in it are scoped to the 25 | /// given name 26 | async fn get_key_value_store(&self, name: &str) -> Result; 27 | } 28 | -------------------------------------------------------------------------------- /rust/noosphere-storage/src/ucan.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use async_trait::async_trait; 3 | use cid::Cid; 4 | use core::fmt::Debug; 5 | use libipld_core::{ 6 | codec::{Decode, Encode}, 7 | raw::RawCodec, 8 | }; 9 | use noosphere_ucan::store::{UcanStore as UcanStoreTrait, UcanStoreConditionalSend}; 10 | 11 | use crate::block::BlockStore; 12 | 13 | pub struct UcanStore(pub S); 14 | 15 | #[cfg_attr(not(target_arch = "wasm32"), async_trait)] 16 | #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] 17 | impl UcanStoreTrait for UcanStore { 18 | async fn read>(&self, cid: &Cid) -> Result> { 19 | self.0.get::(cid).await 20 | } 21 | 22 | async fn write + UcanStoreConditionalSend + Debug>( 23 | &mut self, 24 | token: T, 25 | ) -> Result { 26 | self.0.put::(token).await 27 | } 28 | } 29 | 30 | impl Clone for UcanStore { 31 | fn clone(&self) -> Self { 32 | UcanStore(self.0.clone()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rust/noosphere-ucan/README.md: -------------------------------------------------------------------------------- 1 | ![API Stability: Alpha](https://img.shields.io/badge/API%20Stability-Alpha-red) 2 | 3 | # Noosphere Ucan 4 | 5 | This is a Rust library to help the next generation of web applications make use 6 | of UCANs in their authorization flows. To learn more about UCANs and how you 7 | might use them in your application, visit [https://ucan.xyz][ucan website] or 8 | read the [spec][spec]. 9 | 10 | This project was forked from the official [rs-ucan] implementation, pending the finalization and broad adoption of the v1.0.0 spec (which represents a major breaking change from v0.10.0). 11 | 12 | ## Key Materials 13 | 14 | Key material helpers are provided under `noosphere_ucan::key_material::*` and can be enabled via features: 15 | 16 | * `ed25519` 17 | * `rsa` 18 | * `p256` 19 | * `web-crypto-rsa` (`wasm32` only) 20 | 21 | [spec]: https://github.com/ucan-wg/spec 22 | [ucan website]: https://ucan.xyz 23 | [rs-ucan]: https://github.com/ucan-wg/rs-ucan 24 | -------------------------------------------------------------------------------- /rust/noosphere-ucan/src/capability/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod proof; 2 | 3 | mod caveats; 4 | mod data; 5 | mod semantics; 6 | 7 | pub use caveats::*; 8 | pub use data::*; 9 | pub use semantics::*; 10 | -------------------------------------------------------------------------------- /rust/noosphere-ucan/src/crypto/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod did; 2 | mod key; 3 | mod signature; 4 | 5 | pub use key::*; 6 | pub use signature::*; 7 | -------------------------------------------------------------------------------- /rust/noosphere-ucan/src/crypto/signature.rs: -------------------------------------------------------------------------------- 1 | use strum_macros::{Display, EnumString}; 2 | 3 | // See: https://www.rfc-editor.org/rfc/rfc7518 4 | // See: https://www.rfc-editor.org/rfc/rfc8037.html#appendix-A.4 5 | #[derive(Debug, Display, EnumString, Eq, PartialEq)] 6 | pub enum JwtSignatureAlgorithm { 7 | EdDSA, 8 | RS256, 9 | ES256, 10 | ES384, 11 | ES512, 12 | } 13 | -------------------------------------------------------------------------------- /rust/noosphere-ucan/src/ipld/mod.rs: -------------------------------------------------------------------------------- 1 | mod principle; 2 | mod signature; 3 | mod ucan; 4 | 5 | pub use self::ucan::*; 6 | pub use principle::*; 7 | pub use signature::*; 8 | -------------------------------------------------------------------------------- /rust/noosphere-ucan/src/key_material/fixtures/rsa_key.pk8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subconsciousnetwork/noosphere/94b0c5e8a91437e1486e02e29bc1aba1b7b225a8/rust/noosphere-ucan/src/key_material/fixtures/rsa_key.pk8 -------------------------------------------------------------------------------- /rust/noosphere-ucan/src/key_material/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(test, feature = "ed25519"))] 2 | pub mod ed25519; 3 | #[cfg(feature = "p256")] 4 | pub mod p256; 5 | #[cfg(any(feature = "rsa", feature = "web-crypto-rsa"))] 6 | pub mod rsa; 7 | #[cfg(feature = "web-crypto-rsa")] 8 | pub mod web_crypto; 9 | -------------------------------------------------------------------------------- /rust/noosphere-ucan/src/serde.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use base64::Engine; 3 | use libipld_core::{ 4 | codec::{Decode, Encode}, 5 | ipld::Ipld, 6 | serde::{from_ipld, to_ipld}, 7 | }; 8 | use libipld_json::DagJsonCodec; 9 | use serde::{de::DeserializeOwned, Serialize, Serializer}; 10 | use std::io::Cursor; 11 | 12 | /// Utility function to enforce lower-case string values when serializing 13 | pub fn ser_to_lower_case(string: &str, serializer: S) -> Result 14 | where 15 | S: Serializer, 16 | { 17 | serializer.serialize_str(&string.to_lowercase()) 18 | } 19 | 20 | /// Helper trait to ser/de any serde-implementing value to/from DAG-JSON 21 | pub trait DagJson: Serialize + DeserializeOwned { 22 | fn to_dag_json(&self) -> Result> { 23 | let ipld = to_ipld(self)?; 24 | let mut json_bytes = Vec::new(); 25 | 26 | ipld.encode(DagJsonCodec, &mut json_bytes)?; 27 | 28 | Ok(json_bytes) 29 | } 30 | 31 | fn from_dag_json(json_bytes: &[u8]) -> Result { 32 | let ipld = Ipld::decode(DagJsonCodec, &mut Cursor::new(json_bytes))?; 33 | Ok(from_ipld(ipld)?) 34 | } 35 | } 36 | 37 | impl DagJson for T where T: Serialize + DeserializeOwned {} 38 | 39 | /// Helper trait to encode structs as base64 as part of creating a JWT 40 | pub trait Base64Encode: DagJson { 41 | fn jwt_base64_encode(&self) -> Result { 42 | Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(self.to_dag_json()?)) 43 | } 44 | } 45 | 46 | impl Base64Encode for T where T: DagJson {} 47 | -------------------------------------------------------------------------------- /rust/noosphere-ucan/src/tests/crypto.rs: -------------------------------------------------------------------------------- 1 | mod did_from_keypair { 2 | use crate::{crypto::KeyMaterial, key_material::ed25519::bytes_to_ed25519_key}; 3 | use base64::Engine; 4 | 5 | #[cfg(target_arch = "wasm32")] 6 | use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; 7 | 8 | #[cfg(target_arch = "wasm32")] 9 | wasm_bindgen_test_configure!(run_in_browser); 10 | 11 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 12 | #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] 13 | async fn it_handles_ed25519_keys() { 14 | let pub_key = base64::engine::general_purpose::STANDARD 15 | .decode("Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=") 16 | .unwrap(); 17 | let keypair = bytes_to_ed25519_key(pub_key).unwrap(); 18 | let expected_did = "did:key:z6MkgYGF3thn8k1Fv4p4dWXKtsXCnLH7q9yw4QgNPULDmDKB"; 19 | let result_did = keypair.get_did().await.unwrap(); 20 | 21 | assert_eq!(expected_did, result_did.as_str()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /rust/noosphere-ucan/src/tests/fixtures/capabilities/email.rs: -------------------------------------------------------------------------------- 1 | use crate::capability::{Ability, CapabilitySemantics, Scope}; 2 | use anyhow::{anyhow, Result}; 3 | use url::Url; 4 | 5 | #[derive(Clone, PartialEq)] 6 | pub struct EmailAddress(String); 7 | 8 | impl Scope for EmailAddress { 9 | fn contains(&self, other: &Self) -> bool { 10 | return self.0 == other.0; 11 | } 12 | } 13 | 14 | impl ToString for EmailAddress { 15 | fn to_string(&self) -> String { 16 | format!("mailto:{}", self.0.clone()) 17 | } 18 | } 19 | 20 | impl TryFrom for EmailAddress { 21 | type Error = anyhow::Error; 22 | 23 | fn try_from(value: Url) -> Result { 24 | match value.scheme() { 25 | "mailto" => Ok(EmailAddress(String::from(value.path()))), 26 | _ => Err(anyhow!( 27 | "Could not interpret URI as an email address: {}", 28 | value 29 | )), 30 | } 31 | } 32 | } 33 | 34 | #[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] 35 | pub enum EmailAction { 36 | Send, 37 | } 38 | 39 | impl Ability for EmailAction {} 40 | 41 | impl ToString for EmailAction { 42 | fn to_string(&self) -> String { 43 | match self { 44 | EmailAction::Send => "email/send", 45 | } 46 | .into() 47 | } 48 | } 49 | 50 | impl TryFrom for EmailAction { 51 | type Error = anyhow::Error; 52 | 53 | fn try_from(value: String) -> Result { 54 | match value.as_str() { 55 | "email/send" => Ok(EmailAction::Send), 56 | _ => Err(anyhow!("Unrecognized action: {}", value)), 57 | } 58 | } 59 | } 60 | 61 | pub struct EmailSemantics {} 62 | 63 | impl CapabilitySemantics for EmailSemantics {} 64 | -------------------------------------------------------------------------------- /rust/noosphere-ucan/src/tests/fixtures/capabilities/mod.rs: -------------------------------------------------------------------------------- 1 | mod email; 2 | mod wnfs; 3 | 4 | pub use email::*; 5 | pub use wnfs::*; 6 | -------------------------------------------------------------------------------- /rust/noosphere-ucan/src/tests/fixtures/crypto.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | crypto::did::{KeyConstructorSlice, ED25519_MAGIC_BYTES}, 3 | key_material::ed25519::bytes_to_ed25519_key, 4 | }; 5 | 6 | pub const SUPPORTED_KEYS: &KeyConstructorSlice = &[ 7 | // https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L94 8 | (ED25519_MAGIC_BYTES, bytes_to_ed25519_key), 9 | ]; 10 | -------------------------------------------------------------------------------- /rust/noosphere-ucan/src/tests/fixtures/mod.rs: -------------------------------------------------------------------------------- 1 | mod capabilities; 2 | mod crypto; 3 | mod identities; 4 | mod store; 5 | 6 | pub use capabilities::*; 7 | pub use crypto::*; 8 | pub use identities::*; 9 | pub use store::*; 10 | -------------------------------------------------------------------------------- /rust/noosphere-ucan/src/tests/fixtures/store.rs: -------------------------------------------------------------------------------- 1 | use crate::store::{UcanStore, UcanStoreConditionalSend}; 2 | use anyhow::{anyhow, Result}; 3 | use async_trait::async_trait; 4 | use cid::{ 5 | multihash::{Code, MultihashDigest}, 6 | Cid, 7 | }; 8 | use libipld_core::{ 9 | codec::{Codec, Decode, Encode}, 10 | raw::RawCodec, 11 | }; 12 | use std::{ 13 | collections::HashMap, 14 | io::Cursor, 15 | sync::{Arc, Mutex}, 16 | }; 17 | 18 | #[derive(Clone, Default, Debug)] 19 | pub struct Blake2bMemoryStore { 20 | dags: Arc>>>, 21 | } 22 | 23 | #[cfg_attr(not(target_arch = "wasm32"), async_trait)] 24 | #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] 25 | impl UcanStore for Blake2bMemoryStore { 26 | async fn read>(&self, cid: &Cid) -> Result> { 27 | let dags = self.dags.lock().map_err(|_| anyhow!("poisoned mutex!"))?; 28 | 29 | Ok(match dags.get(cid) { 30 | Some(bytes) => Some(T::decode(RawCodec, &mut Cursor::new(bytes))?), 31 | None => None, 32 | }) 33 | } 34 | 35 | async fn write + UcanStoreConditionalSend + core::fmt::Debug>( 36 | &mut self, 37 | token: T, 38 | ) -> Result { 39 | let codec = RawCodec; 40 | let block = codec.encode(&token)?; 41 | let cid = Cid::new_v1(codec.into(), Code::Blake2b256.digest(&block)); 42 | 43 | let mut dags = self.dags.lock().map_err(|_| anyhow!("poisoned mutex!"))?; 44 | dags.insert(cid, block); 45 | 46 | Ok(cid) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /rust/noosphere-ucan/src/tests/helpers.rs: -------------------------------------------------------------------------------- 1 | use super::fixtures::{EmailSemantics, Identities}; 2 | use crate::{ 3 | builder::UcanBuilder, capability::CapabilitySemantics, 4 | key_material::ed25519::Ed25519KeyMaterial, 5 | }; 6 | use anyhow::Result; 7 | use serde::{de::DeserializeOwned, Serialize}; 8 | use serde_ipld_dagcbor::{from_slice, to_vec}; 9 | 10 | pub fn dag_cbor_roundtrip(data: &T) -> Result 11 | where 12 | T: Serialize + DeserializeOwned, 13 | { 14 | Ok(from_slice(&to_vec(data)?)?) 15 | } 16 | 17 | pub async fn scaffold_ucan_builder( 18 | identities: &Identities, 19 | ) -> Result> { 20 | let email_semantics = EmailSemantics {}; 21 | let send_email_as_bob = email_semantics 22 | .parse("mailto:bob@email.com".into(), "email/send".into(), None) 23 | .unwrap(); 24 | let send_email_as_alice = email_semantics 25 | .parse("mailto:alice@email.com".into(), "email/send".into(), None) 26 | .unwrap(); 27 | 28 | let leaf_ucan_alice = UcanBuilder::default() 29 | .issued_by(&identities.alice_key) 30 | .for_audience(identities.mallory_did.as_str()) 31 | .with_expiration(1664232146010) 32 | .claiming_capability(&send_email_as_alice) 33 | .build() 34 | .unwrap() 35 | .sign() 36 | .await 37 | .unwrap(); 38 | 39 | let leaf_ucan_bob = UcanBuilder::default() 40 | .issued_by(&identities.bob_key) 41 | .for_audience(identities.mallory_did.as_str()) 42 | .with_expiration(1664232146010) 43 | .claiming_capability(&send_email_as_bob) 44 | .build() 45 | .unwrap() 46 | .sign() 47 | .await 48 | .unwrap(); 49 | 50 | let builder = UcanBuilder::default() 51 | .issued_by(&identities.mallory_key) 52 | .for_audience(identities.alice_did.as_str()) 53 | .with_expiration(1664232146010) 54 | .witnessed_by(&leaf_ucan_alice, None) 55 | .witnessed_by(&leaf_ucan_bob, None) 56 | .claiming_capability(&send_email_as_alice) 57 | .claiming_capability(&send_email_as_bob); 58 | 59 | Ok(builder) 60 | } 61 | -------------------------------------------------------------------------------- /rust/noosphere-ucan/src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod attenuation; 2 | mod builder; 3 | mod capability; 4 | mod chain; 5 | mod crypto; 6 | pub mod fixtures; 7 | pub mod helpers; 8 | mod ucan; 9 | -------------------------------------------------------------------------------- /rust/noosphere-ucan/src/time.rs: -------------------------------------------------------------------------------- 1 | use instant::SystemTime; 2 | 3 | pub fn now() -> u64 { 4 | SystemTime::now() 5 | .duration_since(SystemTime::UNIX_EPOCH) 6 | .unwrap() 7 | .as_secs() 8 | } 9 | -------------------------------------------------------------------------------- /rust/noosphere/.gitignore: -------------------------------------------------------------------------------- 1 | noosphere.h -------------------------------------------------------------------------------- /rust/noosphere/README.md: -------------------------------------------------------------------------------- 1 | ![API Stability: Alpha](https://img.shields.io/badge/API%20Stability-Alpha-red) 2 | 3 | # Noosphere 4 | 5 | This is the entrypoint for most language-specific distributable packages the 6 | deal with accessing the Noosphere. It contains language-specific bindings and 7 | platform-sensitive behavior to provide a common set of high-level APIs. 8 | 9 | ## Building Headers 10 | 11 | To build the C FFI headers: 12 | 13 | ``` 14 | cargo run --features=headers --example generate_header 15 | ``` 16 | -------------------------------------------------------------------------------- /rust/noosphere/build.rs: -------------------------------------------------------------------------------- 1 | use cfg_aliases::cfg_aliases; 2 | 3 | fn main() { 4 | println!("cargo:rerun-if-changed=build.rs"); 5 | 6 | cfg_aliases! { 7 | // Platforms 8 | wasm: { target_arch = "wasm32" }, 9 | native: { not(target_arch = "wasm32") }, 10 | apple: { 11 | all( 12 | target_vendor = "apple", 13 | any(target_arch = "aarch64", target_arch = "x86_64") 14 | ) 15 | }, 16 | 17 | // Backends 18 | rocksdb: { all(feature = "rocksdb", native) }, 19 | sled: { all(not(any(rocksdb)), native) }, 20 | indexeddb: { wasm }, 21 | 22 | // Other 23 | ipfs_storage: { feature = "ipfs-storage" }, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /rust/noosphere/examples/generate_header.rs: -------------------------------------------------------------------------------- 1 | //! This example is used to generate the FFI interface C header. You can run 2 | //! it locally to generate a noosphere.h that represents the FFI interface 3 | //! exposed by this crate at any given revision. 4 | use anyhow::{anyhow, Result}; 5 | 6 | #[cfg(feature = "headers")] 7 | fn main() -> Result<()> { 8 | noosphere::ffi::generate_headers().map_err(|e| anyhow!(e.to_string()))?; 9 | 10 | Ok(()) 11 | } 12 | 13 | #[cfg(not(feature = "headers"))] 14 | fn main() -> Result<()> { 15 | Err(anyhow!("Must run with \"headers\" feature.")) 16 | } 17 | -------------------------------------------------------------------------------- /rust/noosphere/include/noosphere/module.modulemap: -------------------------------------------------------------------------------- 1 | module Noosphere { 2 | header "noosphere.h" 3 | export * 4 | } -------------------------------------------------------------------------------- /rust/noosphere/src/ffi/headers.rs: -------------------------------------------------------------------------------- 1 | use safer_ffi::prelude::*; 2 | 3 | #[derive_ReprC(rename = "ns_headers")] 4 | #[repr(opaque)] 5 | /// @class ns_headers_t 6 | /// An opaque struct representing name/value headers. 7 | /// 8 | /// Headers are used in ns_sphere_file_t to assign metadata, like content type. 9 | pub struct NsHeaders { 10 | inner: Vec<(String, String)>, 11 | } 12 | 13 | impl NsHeaders { 14 | pub(crate) fn inner(&self) -> &Vec<(String, String)> { 15 | &self.inner 16 | } 17 | } 18 | 19 | #[ffi_export] 20 | /// @memberof ns_headers_t 21 | /// Allocate and initialize a ns_headers_t instance with no values. 22 | /// 23 | /// Used for the purpose of building up a set of headers 24 | /// intended to be added to a memo before it is written to a sphere 25 | pub fn ns_headers_create() -> repr_c::Box { 26 | Box::new(NsHeaders { inner: Vec::new() }).into() 27 | } 28 | 29 | #[ffi_export] 30 | /// @memberof ns_headers_t 31 | /// Add a name/value pair to a ns_headers_t instance. 32 | pub fn ns_headers_add(headers: &mut NsHeaders, name: char_p::Ref<'_>, value: char_p::Ref<'_>) { 33 | headers.inner.push((name.to_string(), value.to_string())) 34 | } 35 | 36 | #[ffi_export] 37 | /// @memberof ns_headers_t 38 | /// Deallocate a ns_headers_t instance. 39 | pub fn ns_headers_free(headers: repr_c::Box) { 40 | drop(headers) 41 | } 42 | -------------------------------------------------------------------------------- /rust/noosphere/src/ffi/key.rs: -------------------------------------------------------------------------------- 1 | use safer_ffi::prelude::*; 2 | 3 | use crate::ffi::{NsError, NsNoosphere, TryOrInitialize}; 4 | 5 | #[ffi_export] 6 | /// Create a key with the given name in the current platform's support key 7 | /// storage mechanism. 8 | pub fn ns_key_create( 9 | noosphere: &NsNoosphere, 10 | name: char_p::Ref<'_>, 11 | error_out: Option>>, 12 | ) { 13 | error_out.try_or_initialize(|| { 14 | noosphere 15 | .async_runtime() 16 | .block_on(noosphere.inner().create_key(name.to_str())) 17 | .map_err(|error| error.into()) 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /rust/noosphere/src/ffi/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module defines a C FFI for Noosphere, suitable for cross-language 2 | //! embedding on many different targets 3 | 4 | mod authority; 5 | mod context; 6 | mod error; 7 | mod headers; 8 | mod key; 9 | mod noosphere; 10 | mod petname; 11 | mod sphere; 12 | mod tracing; 13 | 14 | pub use crate::ffi::noosphere::*; 15 | pub use crate::ffi::tracing::*; 16 | pub use authority::*; 17 | pub use context::*; 18 | pub use error::*; 19 | pub use headers::*; 20 | pub use key::*; 21 | pub use petname::*; 22 | pub use sphere::*; 23 | 24 | #[cfg(feature = "headers")] 25 | pub fn generate_headers() -> std::io::Result<()> { 26 | safer_ffi::headers::builder() 27 | .to_file("noosphere.h")? 28 | .generate() 29 | } 30 | -------------------------------------------------------------------------------- /rust/noosphere/src/key/interface.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use async_trait::async_trait; 3 | use noosphere_ucan::crypto::KeyMaterial; 4 | 5 | /// A trait that represents access to arbitrary key storage backends. 6 | #[cfg_attr(not(target_arch = "wasm32"), async_trait)] 7 | #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] 8 | pub trait KeyStorage: Clone 9 | where 10 | K: KeyMaterial, 11 | { 12 | /// Read a key by name from key storage. 13 | async fn read_key(&self, name: &str) -> Result>; 14 | /// Read a key by name from key storage, but return an error if no key is 15 | /// found by that name. 16 | async fn require_key(&self, name: &str) -> Result { 17 | match self.read_key(name).await? { 18 | Some(key) => Ok(key), 19 | None => Err(anyhow!("No key named {} found!", name)), 20 | } 21 | } 22 | /// Create a key associated with the given name in key storage. 23 | async fn create_key(&self, name: &str) -> Result; 24 | } 25 | -------------------------------------------------------------------------------- /rust/noosphere/src/key/mod.rs: -------------------------------------------------------------------------------- 1 | //! Key management is a critical part of working with the Noosphere protocol. 2 | //! This module offers various backing storage mechanisms for key storage, 3 | //! including both insecure and secure options. 4 | mod interface; 5 | pub use interface::*; 6 | 7 | #[cfg(not(target_arch = "wasm32"))] 8 | mod insecure; 9 | 10 | #[cfg(not(target_arch = "wasm32"))] 11 | pub use insecure::InsecureKeyStorage; 12 | 13 | #[cfg(target_arch = "wasm32")] 14 | mod web; 15 | 16 | #[cfg(target_arch = "wasm32")] 17 | pub use web::WebCryptoKeyStorage; 18 | -------------------------------------------------------------------------------- /rust/noosphere/src/sphere/builder/join.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use anyhow::{anyhow, Result}; 4 | use cid::Cid; 5 | use noosphere_core::{ 6 | authority::Author, 7 | context::{SphereContext, SphereContextKey, AUTHORIZATION, IDENTITY, USER_KEY_NAME}, 8 | data::Did, 9 | }; 10 | use noosphere_storage::KeyValueStore; 11 | 12 | use crate::{ 13 | key::KeyStorage, 14 | sphere::{generate_db, SphereContextBuilder, SphereContextBuilderArtifacts}, 15 | }; 16 | 17 | pub async fn join_a_sphere( 18 | builder: SphereContextBuilder, 19 | sphere_identity: Did, 20 | ) -> Result { 21 | let key_storage = builder 22 | .key_storage 23 | .as_ref() 24 | .ok_or_else(|| anyhow!("No key storage configured!"))?; 25 | let key_name = builder 26 | .key_name 27 | .as_ref() 28 | .ok_or_else(|| anyhow!("No key name configured!"))?; 29 | let storage_path = builder.require_storage_path()?.to_owned(); 30 | 31 | let user_key: SphereContextKey = Arc::new(Box::new(key_storage.require_key(key_name).await?)); 32 | 33 | let mut db = generate_db( 34 | storage_path, 35 | builder.scoped_storage_layout, 36 | Some(sphere_identity.clone()), 37 | builder.ipfs_gateway_url.clone(), 38 | builder.storage_config.clone(), 39 | ) 40 | .await?; 41 | 42 | db.set_key(IDENTITY, &sphere_identity).await?; 43 | db.set_key(USER_KEY_NAME, key_name.to_owned()).await?; 44 | 45 | if let Some(authorization) = &builder.authorization { 46 | db.set_key(AUTHORIZATION, Cid::try_from(authorization)?) 47 | .await?; 48 | } 49 | 50 | debug!("Initializing context..."); 51 | 52 | let mut context = SphereContext::new( 53 | sphere_identity, 54 | Author { 55 | key: user_key, 56 | authorization: builder.authorization.clone(), 57 | }, 58 | db, 59 | None, 60 | ) 61 | .await?; 62 | 63 | debug!("Configuring gateway URL..."); 64 | 65 | if builder.gateway_api.is_some() { 66 | context 67 | .configure_gateway_url(builder.gateway_api.as_ref()) 68 | .await?; 69 | } 70 | 71 | Ok(SphereContextBuilderArtifacts::SphereOpened(context)) 72 | } 73 | -------------------------------------------------------------------------------- /rust/noosphere/src/sphere/builder/open.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use anyhow::{anyhow, Result}; 4 | use noosphere_core::{ 5 | authority::{Author, Authorization}, 6 | context::{SphereContext, SphereContextKey, AUTHORIZATION, IDENTITY, USER_KEY_NAME}, 7 | data::Did, 8 | }; 9 | use noosphere_storage::KeyValueStore; 10 | 11 | use crate::{ 12 | key::KeyStorage, 13 | sphere::{generate_db, SphereContextBuilder, SphereContextBuilderArtifacts}, 14 | }; 15 | 16 | pub async fn open_a_sphere( 17 | builder: SphereContextBuilder, 18 | sphere_identity: Option, 19 | ) -> Result { 20 | let storage_path = builder.require_storage_path()?.to_owned(); 21 | let db = generate_db( 22 | storage_path, 23 | builder.scoped_storage_layout, 24 | sphere_identity, 25 | builder.ipfs_gateway_url, 26 | builder.storage_config, 27 | ) 28 | .await?; 29 | 30 | let user_key_name: String = db.require_key(USER_KEY_NAME).await?; 31 | let authorization = db.get_key(AUTHORIZATION).await?.map(Authorization::Cid); 32 | 33 | let author = match builder.key_storage { 34 | Some(key_storage) => { 35 | let key: SphereContextKey = 36 | Arc::new(Box::new(key_storage.require_key(&user_key_name).await?)); 37 | 38 | Author { key, authorization } 39 | } 40 | _ => return Err(anyhow!("Unable to resolve sphere author")), 41 | }; 42 | 43 | let sphere_identity = db.require_key(IDENTITY).await?; 44 | let mut context = SphereContext::new(sphere_identity, author, db, None).await?; 45 | 46 | if builder.gateway_api.is_some() { 47 | context 48 | .configure_gateway_url(builder.gateway_api.as_ref()) 49 | .await?; 50 | } 51 | 52 | Ok(SphereContextBuilderArtifacts::SphereOpened(context)) 53 | } 54 | -------------------------------------------------------------------------------- /rust/noosphere/src/sphere/mod.rs: -------------------------------------------------------------------------------- 1 | //! Constructs that describe high-level operations related to spheres, such 2 | //! as creation, joining and recovery. 3 | 4 | mod builder; 5 | mod channel; 6 | mod receipt; 7 | 8 | pub use builder::*; 9 | pub use channel::*; 10 | pub use receipt::*; 11 | -------------------------------------------------------------------------------- /rust/noosphere/src/sphere/receipt.rs: -------------------------------------------------------------------------------- 1 | use noosphere_core::data::{Did, Mnemonic}; 2 | 3 | /// The result of creating a sphere is a [SphereReceipt], which reports both the 4 | /// sphere identity (it's [Did]) and a [Mnemonic] which must be saved in some 5 | /// secure storage medium on the side (it will not be recorded in the user's 6 | /// sphere data). 7 | pub struct SphereReceipt { 8 | /// The identity of the newly created sphere 9 | pub identity: Did, 10 | /// The recovery [Mnemonic] of the newly created sphere 11 | pub mnemonic: Mnemonic, 12 | } 13 | -------------------------------------------------------------------------------- /rust/noosphere/src/wasm/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | 3 | //! This module defines a [wasm-bindgen]-based FFI for `wasm32-unknown-unknown` 4 | //! targets 5 | 6 | mod file; 7 | mod fs; 8 | mod noosphere; 9 | mod sphere; 10 | 11 | pub use file::*; 12 | pub use fs::*; 13 | pub use noosphere::*; 14 | pub use sphere::*; 15 | -------------------------------------------------------------------------------- /rust/noosphere/src/wasm/sphere.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use cid::Cid; 3 | 4 | use crate::{platform::PlatformSphereChannel, wasm::SphereFs}; 5 | use noosphere_core::context::SphereCursor; 6 | use wasm_bindgen::prelude::*; 7 | 8 | #[wasm_bindgen] 9 | /// A `SphereContext` is a view into all of a sphere's data, that also 10 | /// encapsulates handles to local storage and a user's authority relative to the 11 | /// sphere. If a user is appropriately authorized, they may use a 12 | /// `SphereContext` to modify a sphere. Otherwise, they may only read a sphere's 13 | /// publicly visible content. 14 | pub struct SphereContext { 15 | #[wasm_bindgen(skip)] 16 | pub inner: PlatformSphereChannel, 17 | } 18 | 19 | #[wasm_bindgen] 20 | impl SphereContext { 21 | #[wasm_bindgen] 22 | /// Get a `SphereFs` that gives you access to sphere content at the latest 23 | /// version of the sphere. 24 | pub async fn fs(&mut self) -> Result { 25 | Ok(SphereFs { 26 | inner: SphereCursor::latest(self.inner.mutable().clone()), 27 | }) 28 | } 29 | 30 | #[wasm_bindgen(js_name = "fsAt")] 31 | /// Get a `SphereFs` that gives you access to sphere content at the version 32 | /// specified. The version must be a base32 33 | /// [CID](https://docs.ipfs.tech/concepts/content-addressing/#identifier-formats) 34 | /// string. 35 | pub async fn fs_at(&mut self, version: String) -> Result { 36 | let cid = Cid::try_from(version).map_err(|error| format!("{:?}", error))?; 37 | 38 | Ok(SphereFs { 39 | inner: SphereCursor::mounted_at(self.inner.mutable().clone(), &cid.into()), 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /rust/noosphere/tests/utils/mod.rs: -------------------------------------------------------------------------------- 1 | mod test_utils; 2 | 3 | pub use test_utils::*; 4 | -------------------------------------------------------------------------------- /rust/noosphere/tests/utils/test_utils.rs: -------------------------------------------------------------------------------- 1 | // TODO(#629): Remove this when we migrate off of `release-please` 2 | extern crate noosphere_cli_dev as noosphere_cli; 3 | extern crate noosphere_ns_dev as noosphere_ns; 4 | 5 | use anyhow::{anyhow, Result}; 6 | use noosphere_cli::workspace::CliSphereContext; 7 | use noosphere_core::{ 8 | context::{SpherePetnameRead, SphereSync}, 9 | data::{Link, MemoIpld}, 10 | }; 11 | use std::sync::Arc; 12 | use tokio::{sync::Mutex, time}; 13 | 14 | /// After adding a petname and pushing changes, the gateway will queue 15 | /// up a job to resolve the petname and add the resolved value 16 | /// to the gateway sphere. Afterwards, a sync will pull the resolved 17 | /// petname. This function repeatedly polls until the petname is resolved 18 | /// to the `expected` value, until `timeout` in seconds has been reached (default 5 seconds). 19 | pub async fn wait_for_petname( 20 | mut ctx: Arc>, 21 | petname: &str, 22 | expected: Option>, 23 | timeout: Option, 24 | ) -> Result<()> { 25 | let timeout = timeout.unwrap_or(5); 26 | let mut attempts = timeout; 27 | loop { 28 | if attempts < 1 { 29 | return Err(anyhow!( 30 | "Context failed to resolve \"{}\" to expected value after {} seconds.", 31 | petname, 32 | timeout 33 | )); 34 | } 35 | 36 | let resolved = { 37 | time::sleep(time::Duration::from_secs(1)).await; 38 | ctx.sync().await?; 39 | ctx.resolve_petname(petname).await? 40 | }; 41 | 42 | if resolved == expected { 43 | return Ok(()); 44 | } 45 | attempts -= 1; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /scripts/generate-headers.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) 5 | PROJECT_DIR="$SCRIPT_DIR/../" 6 | cd $PROJECT_DIR 7 | 8 | DEFAULT_OUT="$PROJECT_DIR/target/headers" 9 | OUT="$DEFAULT_OUT" 10 | 11 | function usage() { 12 | echo "generate-headers.sh: a utility to generate Noosphere's headers." 13 | echo "" 14 | echo "Usage:" 15 | echo " generate-headers.sh [options]" 16 | echo "" 17 | echo "Options:" 18 | echo " -o , --output Output directory." 19 | echo " [default: \$ROOT/target/headers]" 20 | echo " -h --help Show usage." 21 | exit 0 22 | } 23 | 24 | while [[ $# -gt 0 ]]; do 25 | case $1 in 26 | -o|--output) 27 | OUT="$2" 28 | shift 29 | shift 30 | ;; 31 | -h|--help) 32 | usage 33 | ;; 34 | -*|--*) 35 | echo "Unknown option $1" 36 | exit 1 37 | ;; 38 | esac 39 | done 40 | 41 | if [[ -f "$OUT" ]]; then 42 | echo "Output directory must be non-existant or a directory: $OUT" 43 | exit 1 44 | fi 45 | 46 | mkdir -p "$OUT" 47 | cp -r ./rust/noosphere/include "$OUT" 48 | 49 | if [[ "$OSTYPE" == "darwin"* ]]; then 50 | # macos linker fails to generate all exports without this flag 51 | # https://github.com/subconsciousnetwork/noosphere/issues/473 52 | CARGO_PROFILE_DEV_CODEGEN_UNITS=1 cargo run --verbose --package noosphere --example generate_header --features headers --locked 53 | else 54 | cargo run --verbose --package noosphere --example generate_header --features headers --locked 55 | fi 56 | 57 | mv ./noosphere.h "$OUT/include/noosphere/noosphere.h" 58 | -------------------------------------------------------------------------------- /scripts/swift-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | SCRIPT_DIR=$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd) 5 | PROJECT_DIR="$SCRIPT_DIR/../" 6 | cd $PROJECT_DIR 7 | 8 | PLACEHOLDER=./_Package.swift 9 | PROFILE="debug" 10 | SANITIZE="" 11 | 12 | function usage() { 13 | echo "swift-test.sh: a utility to run the Swift Noosphere module tests locally." 14 | echo "" 15 | echo "Usage:" 16 | echo " swift-test.sh [options]" 17 | echo "" 18 | echo "Options:" 19 | echo " --release Use release build." 20 | echo " --sanitize {address,thread} Test with a sanitizer." 21 | echo " -h --help Show usage." 22 | exit 0 23 | } 24 | 25 | while [[ $# -gt 0 ]]; do 26 | case $1 in 27 | --sanitize) 28 | SANITIZE="$2" 29 | shift 30 | shift 31 | ;; 32 | --release) 33 | PROFILE="release" 34 | shift 35 | ;; 36 | -h|--help) 37 | usage 38 | ;; 39 | -*|--*) 40 | echo "Unknown option $1" 41 | exit 1 42 | ;; 43 | esac 44 | done 45 | 46 | trap "mv $PLACEHOLDER Package.swift" EXIT 47 | 48 | cp Package.swift $PLACEHOLDER 49 | 50 | FRAMEWORK_PATH="./target/framework/$PROFILE/LibNoosphere.xcframework" 51 | sed -i '' -e "s#url: \"[^\"]*\",#path: \"$FRAMEWORK_PATH\"),#" ./Package.swift 52 | sed -i '' -e "s#checksum: \"[^\"]*\"),##" ./Package.swift 53 | 54 | # Enable malloc debugging features 55 | # https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/MallocDebug.html 56 | if [[ ! -n ${SANITIZE} ]]; then 57 | swift test -c $PROFILE 58 | else 59 | MallocPreScribble=1 MallocScribble=1 swift test -c $PROFILE --sanitize=$SANITIZE 60 | fi 61 | exit $? 62 | -------------------------------------------------------------------------------- /typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@subconsciousnetwork/noosphere-packages", 3 | "private": true, 4 | "author": "Subconscious Inc.", 5 | "license": "Apache-2.0 OR MIT", 6 | "version": "0.1.0", 7 | "description": "", 8 | "workspaces": ["./packages/*"], 9 | "scripts": { 10 | "build": "wireit", 11 | "clean": "wireit", 12 | "serve": "wireit", 13 | "test": "wireit" 14 | }, 15 | "dependencies": { 16 | "wireit": "^0.14.4", 17 | "typescript": "~5.4.5", 18 | "esbuild": "^0.20.2", 19 | "@web/dev-server": "^0.4.4" 20 | }, 21 | "wireit": { 22 | "build": { 23 | "dependencies": [ 24 | "./packages/orb:build", 25 | "./packages/sphere-viewer:build", 26 | "./packages/noosphere-guide:build" 27 | ] 28 | }, 29 | "serve": { 30 | "dependencies": [ 31 | "./packages/sphere-viewer:serve", 32 | "./packages/noosphere-guide:serve" 33 | ] 34 | }, 35 | "clean": { 36 | "dependencies": [ 37 | "./packages/orb:clean", 38 | "./packages/sphere-viewer:clean", 39 | "./packages/noosphere-guide:clean" 40 | ] 41 | }, 42 | "test": { 43 | "dependencies": [ 44 | "./packages/orb:test" 45 | ] 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /typescript/packages/noosphere-guide/.gitignore: -------------------------------------------------------------------------------- 1 | _site -------------------------------------------------------------------------------- /typescript/packages/noosphere-guide/_static/images/database-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /typescript/packages/noosphere-guide/_static/images/discord-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /typescript/packages/noosphere-guide/_static/images/droplet-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /typescript/packages/noosphere-guide/_static/images/earth-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /typescript/packages/noosphere-guide/_static/images/github-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /typescript/packages/noosphere-guide/_static/images/hamburger-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /typescript/packages/noosphere-guide/_static/images/squiggle-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /typescript/packages/noosphere-guide/_static/images/substack-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /typescript/packages/noosphere-guide/_static/styles/common.css: -------------------------------------------------------------------------------- 1 | html { 2 | --serif: "EB Garamond", serif; 3 | --sans-serif: 'IBM Plex Sans', sans-serif; 4 | 5 | --light-primary-color: oklch(99% 0.085 319); 6 | --light-secondary-color: oklch(98% 0.01 319); 7 | --dark-primary-color: oklch(20% 0.15 319); 8 | --dark-secondary-color: oklch(47% 0.19 319); 9 | --dark-tertiary-color: oklch(60% 0.05 319); 10 | 11 | --text-color-light: var(--light-primary-color); 12 | --text-color-dark: var(--dark-primary-color); 13 | --text-color-anchor: var(--dark-secondary-color); 14 | } 15 | 16 | html, 17 | body { 18 | margin: 0; 19 | padding: 0; 20 | } 21 | 22 | body { 23 | font-family: var(--sans-serif); 24 | color: var(--text-color-dark); 25 | } 26 | 27 | * { 28 | box-sizing: border-box; 29 | } 30 | 31 | p { 32 | text-indent: 1em; 33 | } 34 | 35 | b { 36 | font-weight: 500; 37 | } 38 | 39 | a { 40 | font-weight: 500; 41 | color: var(--text-color-anchor); 42 | } 43 | 44 | blockquote { 45 | color: oklch(50% 0.15 319); 46 | background: var(--light-secondary-color); 47 | font-style: italic; 48 | padding: 1em; 49 | margin: 0; 50 | } -------------------------------------------------------------------------------- /typescript/packages/noosphere-guide/get-involved.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/docs.njk 3 | --- 4 | 5 | # Get involved 6 | -------------------------------------------------------------------------------- /typescript/packages/noosphere-guide/get-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/docs.njk 3 | --- 4 | 5 | # Get started with Noosphere 6 | 7 | Noosphere supports a few different programming environments, including: 8 | 9 | - [Rust][noosphere-rust-docs] 10 | - TypeScript (web, via NPM) 11 | - Swift 12 | - C 13 | 14 | [noosphere-rust-docs]: https://docs.rs/noosphere/ 15 | -------------------------------------------------------------------------------- /typescript/packages/noosphere-guide/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@subconsciousnetwork/noosphere-guide", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "wireit", 8 | "serve": "wireit", 9 | "clean": "wireit" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "Apache-2.0 OR MIT", 14 | "devDependencies": { 15 | "@11ty/eleventy": "^2.0.1", 16 | "markdown-it": "^14.1.0", 17 | "markdown-it-anchor": "^8.6.7", 18 | "slugify": "^1.6.6", 19 | "wireit": "^0.14.4" 20 | }, 21 | "wireit": { 22 | "build": { 23 | "command": "npx @11ty/eleventy" 24 | }, 25 | "serve": { 26 | "service": true, 27 | "command": "npx @11ty/eleventy --serve" 28 | }, 29 | "clean": { 30 | "command": "rm -rf ./_site" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /typescript/packages/orb/scripts/generate-wasm-artifacts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | # See: https://stackoverflow.com/a/246128 5 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 6 | CARGO_WORKSPACE_DIR="$SCRIPT_DIR/../../../.." 7 | NOOSPHERE_DIR="$CARGO_WORKSPACE_DIR/rust/noosphere" 8 | CARGO_TARGET_DIR="$CARGO_WORKSPACE_DIR/target" 9 | CARGO_TARGET_NOOSPHERE_WASM="$CARGO_TARGET_DIR/wasm32-unknown-unknown/release/noosphere.wasm" 10 | ARTIFACT_OUTPUT_DIR="$SCRIPT_DIR/../lib" 11 | 12 | # Build Wasm target from Rust crates 13 | pushd $NOOSPHERE_DIR 14 | cargo build --release --target wasm32-unknown-unknown --features ipfs-storage 15 | popd 16 | 17 | # Generate web artifacts, including TypeScript types and JS shims 18 | mkdir -p $ARTIFACT_OUTPUT_DIR 19 | wasm-bindgen $CARGO_TARGET_NOOSPHERE_WASM --out-dir $ARTIFACT_OUTPUT_DIR --target web 20 | 21 | # Optimize the Wasm blob; this step reduces the size by ~25% 22 | pushd $ARTIFACT_OUTPUT_DIR 23 | wasm-opt -Oz --vacuum --strip-debug ./noosphere_bg.wasm -o ./noosphere_bg.wasm 24 | popd 25 | 26 | set +e 27 | -------------------------------------------------------------------------------- /typescript/packages/orb/src/index.ts: -------------------------------------------------------------------------------- 1 | import init from './noosphere.js'; 2 | 3 | await init(); 4 | 5 | export * from './noosphere.js'; 6 | -------------------------------------------------------------------------------- /typescript/packages/orb/src/noosphere.d.ts: -------------------------------------------------------------------------------- 1 | ../lib/noosphere.d.ts -------------------------------------------------------------------------------- /typescript/packages/orb/src/noosphere_bg.wasm.d.ts: -------------------------------------------------------------------------------- 1 | ../lib/noosphere_bg.wasm.d.ts -------------------------------------------------------------------------------- /typescript/packages/orb/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./lib", 6 | "tsBuildInfoFile": "tsconfig.tsbuildinfo", 7 | "useDefineForClassFields": false 8 | }, 9 | "include": ["src/**/*.ts"], 10 | "exclude": [] 11 | } 12 | -------------------------------------------------------------------------------- /typescript/packages/orb/web-test-runner.config.js: -------------------------------------------------------------------------------- 1 | // import { esbuildPlugin } from '@web/dev-server-esbuild'; 2 | 3 | export default { 4 | files: ['lib/**/*.test.js'], 5 | // plugins: [esbuildPlugin({ ts: true })], 6 | }; -------------------------------------------------------------------------------- /typescript/packages/sphere-viewer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Sphere Viewer 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /typescript/packages/sphere-viewer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@subconsciousnetwork/sphere-viewer", 3 | "author": "Subconscious Inc.", 4 | "license": "Apache-2.0 OR MIT", 5 | "version": "0.1.0", 6 | "description": "A demo of Orb.js that renders Noosphere content from any IPFS gateway", 7 | "private": true, 8 | "type": "module", 9 | "scripts": { 10 | "build": "wireit", 11 | "build:tsc": "wireit", 12 | "clean": "wireit", 13 | "serve": "wireit" 14 | }, 15 | "dependencies": { 16 | "@reduxjs/toolkit": "^2.2.2", 17 | "@subconsciousnetwork/orb": "*", 18 | "@web/dev-server": "^0.4.4", 19 | "lit": "^3.1.2", 20 | "lit-redux-watch": "^0.3.8", 21 | "reselect": "^5.1.0", 22 | "typescript": "~5.4.5", 23 | "vite": "^5.2.8", 24 | "vite-plugin-wasm": "^3.3.0", 25 | "vite-plugin-top-level-await": "^1.4.1", 26 | "wireit": "^0.14.4" 27 | }, 28 | "wireit": { 29 | "build": { 30 | "dependencies": [ 31 | "build:tsc" 32 | ], 33 | "command": "vite build && ./scripts/finalize-index-html.sh", 34 | "files": [ 35 | "./lib/**", 36 | "./public/**", 37 | "./vite.config.js", 38 | "./scripts/finalize-index-html.sh" 39 | ], 40 | "output": [ 41 | "./dist/**" 42 | ] 43 | }, 44 | "build:tsc": { 45 | "dependencies": [ 46 | "../orb:build" 47 | ], 48 | "command": "tsc --pretty", 49 | "clean": "if-file-deleted", 50 | "files": [ 51 | "../../tsconfig.base.json", 52 | "tsconfig.json", 53 | "./src/**/*.ts" 54 | ], 55 | "output": [ 56 | "./lib/**", 57 | "./tsconfig.tsbuildinfo" 58 | ] 59 | }, 60 | "serve": { 61 | "dependencies": [ 62 | { 63 | "script": "build", 64 | "cascade": false 65 | } 66 | ], 67 | "service": true, 68 | "command": "web-dev-server -r ./dist" 69 | }, 70 | "clean": { 71 | "command": "rm -rf ./dist ./lib && rm -f ./tsconfig.tsbuildinfo" 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /typescript/packages/sphere-viewer/public/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-purple: #842A9C; 3 | --color-tan: #EBEBDA; 4 | --color-black: #231F20; 5 | --color-grey: #DFDBEB; 6 | --color-blush: #EDCFEA; 7 | --color-border: rgba(0, 0, 0, 0.1); 8 | --color-text: var(--color-black); 9 | --color-text-secondary: rgba(0, 0, 0, 0.4); 10 | --color-background-tertiary: #f7f7f7; 11 | --color-selected: rgba(0, 0, 0, 0.02); 12 | --color-light-gradient: linear-gradient(140deg, rgba(237,207,234,1) 0%, rgba(223,219,235,1) 20%, rgba(235,235,218,1) 80%); 13 | --color-dark-text: var(--color-tan); 14 | --font-sans: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, sans-serif; 15 | --font-mono: "IBM Plex Sans", Menlo, monospace; 16 | --radius-sm: 8px; 17 | --radius-md: 16px; 18 | --radius-lg: 24px; 19 | --text-h1-size: 32px; 20 | --text-h1-line: 32px; 21 | --text-caption-size: 16px; 22 | --text-caption-line: 24px; 23 | --text-label-size: 14px; 24 | --text-label-line: 20px; 25 | --text-body-size: 20px; 26 | --text-body-line: 28px; 27 | --pad: 24px; 28 | --pad-sm: 8px; 29 | --pad-xs: 4px; 30 | --pad-block: 16px; 31 | } 32 | 33 | * { 34 | border: 0; 35 | margin: 0; 36 | padding: 0; 37 | box-sizing: border-box; 38 | list-style: none; 39 | } 40 | 41 | html { 42 | background-color: var(--color-grey); 43 | background-image: var(--color-light-gradient); 44 | background-repeat: no-repeat; 45 | background-size: cover; 46 | background-attachment: fixed; 47 | color: var(--color-text); 48 | font-family: var(--font-sans); 49 | font-size: var(--text-body-size); 50 | line-height: var(--text-body-line); 51 | min-height: 100vh; 52 | } -------------------------------------------------------------------------------- /typescript/packages/sphere-viewer/sample-content/the-web-began-as-a-tool-for-thought.subtext: -------------------------------------------------------------------------------- 1 | # The web began as a tool for thought 2 | 3 | I’m building something new. It’s kind of… 4 | 5 | - An autopoietic feedback loop between your past and future self: https://twitter.com/startuployalist/status/1212822312531058689 6 | - A knowledge garden: https://twitter.com/yoshikischmitz/status/1217059690133086209?s=20 7 | - A particle collider for your thoughts 8 | - A seed crystal for a new multiplayer computing platform 9 | - An open-ended and evolvable distributed system 10 | 11 | I hope it works. -------------------------------------------------------------------------------- /typescript/packages/sphere-viewer/scripts/finalize-index-html.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # See: https://stackoverflow.com/a/246128 6 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 7 | PACKAGE_JSON_FILE="$SCRIPT_DIR/../package.json" 8 | INDEX_HTML_FILE="$SCRIPT_DIR/../dist/index.html" 9 | SPHERE_VIEWER_VERSION=`cat $PACKAGE_JSON_FILE | jq .version | xargs echo` 10 | SPHERE_VIEWER_SHA=`git rev-parse HEAD` 11 | 12 | sed -i -e "s#SPHERE_VIEWER_VERSION = \"0.0.0\"#SPHERE_VIEWER_VERSION = \"$SPHERE_VIEWER_VERSION\"#" $INDEX_HTML_FILE 13 | sed -i -e "s#SPHERE_VIEWER_SHA = \"abcdef\"#SPHERE_VIEWER_SHA = \"${SPHERE_VIEWER_SHA:0:6}\"#" $INDEX_HTML_FILE 14 | 15 | set +e -------------------------------------------------------------------------------- /typescript/packages/sphere-viewer/src/components/sv-content.ts: -------------------------------------------------------------------------------- 1 | import { css, html, LitElement } from 'lit'; 2 | import { connect, watch } from 'lit-redux-watch'; 3 | import { customElement } from 'lit/decorators.js'; 4 | import { unsafeHTML } from 'lit/directives/unsafe-html.js'; 5 | import { sharedStyles } from '../styles/shared.js'; 6 | import { store } from '../state/store.js'; 7 | import { subtextStyles } from '../styles/subtext.js'; 8 | import { until } from 'lit/directives/until.js'; 9 | import { loadingIndicator } from '../styles/loading-indicator.js'; 10 | 11 | @customElement('sv-content') 12 | export class SVContent extends connect(store)(LitElement) { 13 | @watch('sphereViewer.sphereId') 14 | sphereId?: string; 15 | 16 | @watch('sphereViewer.sphereVersion') 17 | sphereVersion?: string; 18 | 19 | @watch('sphereViewer.slug') 20 | slug?: string; 21 | 22 | @watch('sphereViewer.fileVersion') 23 | fileVersion?: string; 24 | 25 | @watch('sphereViewer.fileContents') 26 | fileContents?: Promise | null; 27 | 28 | static styles = [ 29 | sharedStyles, 30 | subtextStyles, 31 | loadingIndicator, 32 | css` 33 | .empty { 34 | display: flex; 35 | min-height: 8em; 36 | flex-direction: column; 37 | align-items: center; 38 | justify-content: center; 39 | } 40 | .slug { 41 | color: var(--color-text-secondary); 42 | } 43 | `, 44 | ]; 45 | 46 | render() { 47 | const bodyContent = until( 48 | Promise.resolve(this.fileContents).then((contents) => { 49 | if (contents) { 50 | return html` ${unsafeHTML(contents)} `; 51 | } else { 52 | return html`
53 |

No body content found

54 |
`; 55 | } 56 | }), 57 | html` 58 |
59 |
Loading...
60 |
61 | ` 62 | ); 63 | 64 | return html`
${bodyContent}
`; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /typescript/packages/sphere-viewer/src/components/sv-index.ts: -------------------------------------------------------------------------------- 1 | import { css, html, LitElement } from 'lit'; 2 | import { connect, watch } from 'lit-redux-watch'; 3 | import { customElement } from 'lit/decorators.js'; 4 | import { sharedStyles } from '../styles/shared.js'; 5 | import { store } from '../state/store.js'; 6 | import { loadingIndicator } from '../styles/loading-indicator.js'; 7 | import { until } from 'lit/directives/until.js'; 8 | 9 | @customElement('sv-index') 10 | export class SVIndex extends connect(store)(LitElement) { 11 | @watch('sphereViewer.sphereId') 12 | sphereId?: string; 13 | 14 | @watch('sphereViewer.sphereVersion') 15 | sphereVersion?: string; 16 | 17 | @watch('sphereViewer.sphereIndex') 18 | sphereIndex?: Promise; 19 | 20 | static styles = [sharedStyles, loadingIndicator]; 21 | 22 | render() { 23 | const bodyContent = until( 24 | this.sphereIndex?.then((index) => { 25 | if (index) { 26 | let entries = index.map( 27 | (entry) => html` 28 |
  • 29 | /${entry} 35 |
  • 36 | ` 37 | ); 38 | return html`

    Sphere index

    39 |
      40 | ${entries} 41 |
    `; 42 | } else { 43 | return html`

    44 | This sphere doesn't have any entries yet 45 |

    `; 46 | } 47 | }), 48 | html`
    Loading...
    ` 49 | ); 50 | 51 | return html`
    ${bodyContent}
    `; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /typescript/packages/sphere-viewer/src/state/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import sphereViewerReducer from './state.js'; 3 | 4 | export const store = configureStore({ 5 | reducer: { 6 | sphereViewer: sphereViewerReducer, 7 | }, 8 | }); 9 | 10 | export type RootState = ReturnType; 11 | export type AppDispatch = typeof store.dispatch; 12 | -------------------------------------------------------------------------------- /typescript/packages/sphere-viewer/src/styles/loading-indicator.ts: -------------------------------------------------------------------------------- 1 | import { css } from 'lit'; 2 | 3 | export const loadingIndicator = css` 4 | .loading-indicator { 5 | display: flex; 6 | width: 100%; 7 | height: 100%; 8 | flex-direction: row; 9 | justify-content: center; 10 | align-items: center; 11 | gap: 0.5em; 12 | } 13 | .loading-indicator:before, 14 | .loading-indicator:after, 15 | .loading-indicator > span { 16 | content: ''; 17 | display: block; 18 | width: 0.75em; 19 | height: 0.75em; 20 | border-radius: 0.75em; 21 | color: transparent; 22 | -webkit-user-select: none; 23 | -moz-user-select: none; 24 | -ms-user-select: none; 25 | user-select: none; 26 | animation: oscillate 1s infinite; 27 | } 28 | .loading-indicator:before { 29 | animation: oscillate 1s infinite 0s, color-wheel 10s infinite, fade-in 1s; 30 | } 31 | .loading-indicator > span { 32 | animation: oscillate 1s infinite -0.33s, color-wheel 10s infinite, 33 | fade-in 1s; 34 | } 35 | .loading-indicator:after { 36 | animation: oscillate 1s infinite -0.66s, color-wheel 10s infinite, 37 | fade-in 1s; 38 | } 39 | @keyframes fade-in { 40 | 0% { 41 | opacity: 0; 42 | } 43 | 100% { 44 | opacity: 1; 45 | } 46 | } 47 | @keyframes color-wheel { 48 | 0% { 49 | background: #67fff5; 50 | } 51 | 33% { 52 | background: #8557b3; 53 | } 54 | 66% { 55 | background: #f197c1; 56 | } 57 | 100% { 58 | background: #67fff5; 59 | } 60 | } 61 | @keyframes oscillate { 62 | 0% { 63 | transform: translateY(-30%); 64 | } 65 | 50% { 66 | transform: translateY(30%); 67 | } 68 | 100% { 69 | transform: translateY(-30%); 70 | } 71 | } 72 | `; 73 | -------------------------------------------------------------------------------- /typescript/packages/sphere-viewer/src/styles/subtext.ts: -------------------------------------------------------------------------------- 1 | import { css } from 'lit'; 2 | 3 | export const subtextStyles = css` 4 | .subtext { 5 | display: flex; 6 | flex-direction: column; 7 | gap: var(--pad-block); 8 | } 9 | 10 | .subtext blockquote { 11 | font-style: italic; 12 | } 13 | 14 | .subtext .block-list { 15 | padding-left: 1em; 16 | } 17 | 18 | .subtext .block-list:before { 19 | color: var(--color-text-secondary); 20 | content: '-'; 21 | position: absolute; 22 | margin-left: -1em; 23 | } 24 | 25 | .subtext .block-transcludes:not(:first-child) { 26 | margin-top: 1em; 27 | } 28 | 29 | .subtext .transclude { 30 | border: 1px solid var(--color-border); 31 | border-radius: var(--radius-md); 32 | display: block; 33 | padding: 1em; 34 | } 35 | 36 | .subtext .block:last-child .block-transcludes { 37 | margin-bottom: 0; 38 | } 39 | 40 | .subtext .block-transcludes > .transclude-item { 41 | padding: 16px; 42 | border-radius: 16px; 43 | background-color: var(--color-background-tertiary); 44 | } 45 | 46 | .subtext .block-transcludes { 47 | display: flex; 48 | flex-direction: column; 49 | gap: var(--pad-block); 50 | } 51 | 52 | .subtext .transclude-format-text { 53 | display: flex; 54 | flex-direction: column; 55 | gap: var(--pad-xs); 56 | } 57 | 58 | .subtext .transclude-format-text > .excerpt { 59 | color: var(--color-text); 60 | } 61 | 62 | .subtext .transclude-format-text > .link-text { 63 | color: var(--color-text-secondary); 64 | } 65 | 66 | .subtext .transclude-format-text > .title { 67 | font-weight: bold; 68 | } 69 | 70 | .subtext .block-blank { 71 | display: block; 72 | position: relative; 73 | margin-bottom: 1em; 74 | } 75 | 76 | .subtext .block-header { 77 | font-size: 1em; 78 | font-weight: bold; 79 | margin: 0; 80 | } 81 | `; 82 | -------------------------------------------------------------------------------- /typescript/packages/sphere-viewer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./lib", 6 | "tsBuildInfoFile": "tsconfig.tsbuildinfo", 7 | "useDefineForClassFields": false, 8 | "declaration": false 9 | }, 10 | "include": ["src/**/*.ts"], 11 | "exclude": [] 12 | } 13 | -------------------------------------------------------------------------------- /typescript/packages/sphere-viewer/vite.config.js: -------------------------------------------------------------------------------- 1 | import wasm from 'vite-plugin-wasm'; 2 | import topLevelAwait from "vite-plugin-top-level-await"; 3 | import { defineConfig } from 'vite'; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | wasm(), 8 | topLevelAwait() 9 | ], 10 | build: { 11 | target: 'modules' 12 | }, 13 | base: './' 14 | }); -------------------------------------------------------------------------------- /typescript/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "incremental": true, 7 | "sourceMap": true, 8 | "esModuleInterop": true, 9 | "strict": true, 10 | "noUnusedLocals": false, 11 | "noUnusedParameters": true, 12 | "noImplicitReturns": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "noUncheckedIndexedAccess": true, 15 | "noPropertyAccessFromIndexSignature": true, 16 | "experimentalDecorators": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "skipLibCheck": true, 19 | "declaration": true 20 | } 21 | } 22 | --------------------------------------------------------------------------------