basic network established
This commit is contained in:
23
crates/gsio-node/Cargo.toml
Normal file
23
crates/gsio-node/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "gsio-node"
|
||||
version = "0.1.0"
|
||||
publish = false
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
|
||||
|
||||
[dependencies]
|
||||
futures = { version = "0.3.31" }
|
||||
libp2p = { version = "0.55.0", features = ["identify", "macros", "noise", "ping", "rendezvous", "tcp", "tokio", "yamux"] }
|
||||
tokio = { version = "1.45.1", features = ["rt-multi-thread", "macros", "time", "net"] }
|
||||
tracing = { version = "0.1.41" }
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
axum = { version = "0.8.4", features = ["json", "tracing"] }
|
||||
socketioxide = { version = "0.17.2", features = ["tracing", "v4", "extensions"] }
|
||||
rmpv = { version = "1.3.0", features = ["serde"] }
|
||||
tower-http = { version = "0.6.6", features = ["trace"]}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
uuid = { version = "1.7.0", features = ["v4", "serde"] }
|
||||
chrono = { version = "0.4.35", features = ["serde"] }
|
||||
sha2 = "0.10.8"
|
133
crates/gsio-node/README.md
Normal file
133
crates/gsio-node/README.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# gsio-node
|
||||
|
||||
A Rust-based Socket.IO server that serves as a node in the GSIO-Net distributed ledger system.
|
||||
|
||||
## Overview
|
||||
|
||||
The gsio-node component is a Socket.IO server that:
|
||||
- Maintains a distributed ledger with cryptographic verification
|
||||
- Handles real-time communication with clients
|
||||
- Participates in a peer-to-peer network with other nodes
|
||||
- Provides APIs for adding and retrieving ledger entries
|
||||
|
||||
## Features
|
||||
|
||||
- **Socket.IO Server**: Provides real-time bidirectional communication
|
||||
- **Distributed Ledger**: Maintains a chain of cryptographically linked entries
|
||||
- **P2P Networking**: Communicates with other nodes to synchronize the ledger
|
||||
- **Node Discovery**: Automatically discovers and connects to other nodes
|
||||
- **Consensus Mechanism**: Ensures all nodes converge to the same ledger state
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Rust (latest stable version)
|
||||
- Cargo (comes with Rust)
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
# Clone the repository (if you haven't already)
|
||||
git clone <repository-url>
|
||||
cd gsio-net
|
||||
|
||||
# Build the gsio-node component
|
||||
cd crates/gsio-node
|
||||
cargo build
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Running the Server
|
||||
|
||||
```bash
|
||||
# Run in development mode
|
||||
cargo run
|
||||
|
||||
# Run in release mode
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
The server will start on port 3000 by default.
|
||||
|
||||
### API Endpoints
|
||||
|
||||
The gsio-node server provides the following Socket.IO events:
|
||||
|
||||
#### Client Events (Namespace: "/")
|
||||
|
||||
| Event | Description | Parameters | Response Event |
|
||||
|-------|-------------|------------|----------------|
|
||||
| `add_ledger_entry` | Add a new entry to the ledger | JSON data to store | `ledger_entry_added` |
|
||||
| `get_ledger` | Get all entries in the ledger | None | `ledger_entries` |
|
||||
| `get_known_nodes` | Get all known nodes in the network | None | `known_nodes` |
|
||||
| `ping` | Simple ping to check connection | Any data | `pong` |
|
||||
| `message` | Send a message to the server | Any data | `message-back` |
|
||||
| `message-with-ack` | Send a message with acknowledgement | Any data | Acknowledgement with same data |
|
||||
|
||||
#### P2P Events (Namespace: "/p2p")
|
||||
|
||||
| Event | Description | Parameters | Response Event |
|
||||
|-------|-------------|------------|----------------|
|
||||
| `p2p_message` | Send a message to other nodes | P2P message object | Varies based on message type |
|
||||
|
||||
## Examples
|
||||
|
||||
### Adding a Ledger Entry
|
||||
|
||||
```javascript
|
||||
// Using Socket.IO client
|
||||
socket.emit("add_ledger_entry", {
|
||||
message: "Hello, ledger!",
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// Handle the response
|
||||
socket.on("ledger_entry_added", (entry) => {
|
||||
console.log("Entry added:", entry);
|
||||
});
|
||||
```
|
||||
|
||||
### Getting Ledger Entries
|
||||
|
||||
```javascript
|
||||
// Using Socket.IO client
|
||||
socket.emit("get_ledger");
|
||||
|
||||
// Handle the response
|
||||
socket.on("ledger_entries", (entries) => {
|
||||
console.log("Ledger entries:", entries);
|
||||
});
|
||||
```
|
||||
|
||||
### Getting Known Nodes
|
||||
|
||||
```javascript
|
||||
// Using Socket.IO client
|
||||
socket.emit("get_known_nodes");
|
||||
|
||||
// Handle the response
|
||||
socket.on("known_nodes", (data) => {
|
||||
console.log("Known nodes:", data.nodes);
|
||||
});
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
The gsio-node component consists of the following modules:
|
||||
|
||||
- **main.rs**: Entry point and Socket.IO server setup
|
||||
- **ledger.rs**: Implementation of the distributed ledger
|
||||
- **p2p.rs**: Implementation of peer-to-peer communication
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Run tests
|
||||
cargo test
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[Add license information here]
|
242
crates/gsio-node/src/ledger.rs
Normal file
242
crates/gsio-node/src/ledger.rs
Normal file
@@ -0,0 +1,242 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Sha256, Digest};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
/// Represents a single entry in the distributed ledger
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LedgerEntry {
|
||||
/// Unique identifier for the entry
|
||||
pub id: String,
|
||||
/// Timestamp when the entry was created
|
||||
pub timestamp: DateTime<Utc>,
|
||||
/// The actual data stored in the entry
|
||||
pub data: serde_json::Value,
|
||||
/// Hash of the previous entry in the chain
|
||||
pub previous_hash: String,
|
||||
/// Hash of this entry
|
||||
pub hash: String,
|
||||
/// Node ID that created this entry
|
||||
pub creator_node_id: String,
|
||||
/// Signatures from nodes that have validated this entry
|
||||
pub signatures: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl LedgerEntry {
|
||||
/// Create a new ledger entry
|
||||
pub fn new(
|
||||
data: serde_json::Value,
|
||||
previous_hash: String,
|
||||
creator_node_id: String,
|
||||
) -> Self {
|
||||
let timestamp = Utc::now();
|
||||
let id = format!("{}-{}", creator_node_id, timestamp.timestamp_millis());
|
||||
|
||||
let mut entry = Self {
|
||||
id,
|
||||
timestamp,
|
||||
data,
|
||||
previous_hash,
|
||||
hash: String::new(),
|
||||
creator_node_id,
|
||||
signatures: HashMap::new(),
|
||||
};
|
||||
|
||||
// Calculate the hash of this entry
|
||||
entry.hash = entry.calculate_hash();
|
||||
|
||||
entry
|
||||
}
|
||||
|
||||
/// Calculate the hash of this entry
|
||||
pub fn calculate_hash(&self) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
// Hash the entry fields
|
||||
hasher.update(self.id.as_bytes());
|
||||
hasher.update(self.timestamp.to_rfc3339().as_bytes());
|
||||
hasher.update(self.data.to_string().as_bytes());
|
||||
hasher.update(self.previous_hash.as_bytes());
|
||||
hasher.update(self.creator_node_id.as_bytes());
|
||||
|
||||
// Convert the hash to a hex string
|
||||
format!("{:x}", hasher.finalize())
|
||||
}
|
||||
|
||||
/// Add a signature from a node that has validated this entry
|
||||
pub fn add_signature(&mut self, node_id: String, signature: String) {
|
||||
self.signatures.insert(node_id, signature);
|
||||
}
|
||||
|
||||
/// Verify that this entry is valid
|
||||
pub fn is_valid(&self) -> bool {
|
||||
// Check that the hash is correct
|
||||
self.hash == self.calculate_hash()
|
||||
}
|
||||
}
|
||||
|
||||
/// The distributed ledger
|
||||
#[derive(Debug)]
|
||||
pub struct Ledger {
|
||||
/// The chain of entries in the ledger
|
||||
entries: Vec<LedgerEntry>,
|
||||
/// The ID of this node
|
||||
node_id: String,
|
||||
/// Pending entries that have been received but not yet added to the chain
|
||||
pending_entries: HashMap<String, LedgerEntry>,
|
||||
/// Set of node IDs that are known to this node
|
||||
known_nodes: HashSet<String>,
|
||||
}
|
||||
|
||||
impl Ledger {
|
||||
/// Create a new ledger
|
||||
pub fn new(node_id: String) -> Self {
|
||||
let mut known_nodes = HashSet::new();
|
||||
known_nodes.insert(node_id.clone());
|
||||
|
||||
Self {
|
||||
entries: Vec::new(),
|
||||
node_id,
|
||||
pending_entries: HashMap::new(),
|
||||
known_nodes,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a new entry to the ledger
|
||||
pub fn add_entry(&mut self, data: serde_json::Value) -> Result<LedgerEntry, String> {
|
||||
let previous_hash = match self.entries.last() {
|
||||
Some(entry) => entry.hash.clone(),
|
||||
None => "0".repeat(64), // Genesis block has a hash of all zeros
|
||||
};
|
||||
|
||||
let entry = LedgerEntry::new(data, previous_hash, self.node_id.clone());
|
||||
|
||||
// Add the entry to the chain
|
||||
self.entries.push(entry.clone());
|
||||
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
/// Get all entries in the ledger
|
||||
pub fn get_entries(&self) -> &Vec<LedgerEntry> {
|
||||
&self.entries
|
||||
}
|
||||
|
||||
/// Get the last entry in the ledger
|
||||
pub fn get_last_entry(&self) -> Option<&LedgerEntry> {
|
||||
self.entries.last()
|
||||
}
|
||||
|
||||
/// Add a pending entry that has been received from another node
|
||||
pub fn add_pending_entry(&mut self, entry: LedgerEntry) {
|
||||
self.pending_entries.insert(entry.id.clone(), entry);
|
||||
}
|
||||
|
||||
/// Process pending entries and add them to the chain if they are valid
|
||||
pub fn process_pending_entries(&mut self) -> Vec<LedgerEntry> {
|
||||
let mut added_entries = Vec::new();
|
||||
|
||||
// Get the current last entry in the chain
|
||||
let last_entry = match self.entries.last() {
|
||||
Some(entry) => entry.clone(),
|
||||
None => return added_entries,
|
||||
};
|
||||
|
||||
// Find pending entries that link to the last entry
|
||||
let mut entries_to_process: Vec<LedgerEntry> = self.pending_entries
|
||||
.values()
|
||||
.filter(|e| e.previous_hash == last_entry.hash)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
// Sort by timestamp to ensure deterministic ordering
|
||||
entries_to_process.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
|
||||
|
||||
// Process each entry
|
||||
for entry in entries_to_process {
|
||||
if entry.is_valid() {
|
||||
// Add the entry to the chain
|
||||
self.entries.push(entry.clone());
|
||||
// Remove from pending
|
||||
self.pending_entries.remove(&entry.id);
|
||||
// Add to the list of added entries
|
||||
added_entries.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
added_entries
|
||||
}
|
||||
|
||||
/// Add a known node to the network
|
||||
pub fn add_known_node(&mut self, node_id: String) {
|
||||
self.known_nodes.insert(node_id);
|
||||
}
|
||||
|
||||
/// Get all known nodes in the network
|
||||
pub fn get_known_nodes(&self) -> &HashSet<String> {
|
||||
&self.known_nodes
|
||||
}
|
||||
}
|
||||
|
||||
/// Thread-safe wrapper around the ledger
|
||||
#[derive(Clone)]
|
||||
pub struct SharedLedger {
|
||||
ledger: Arc<Mutex<Ledger>>,
|
||||
}
|
||||
|
||||
impl SharedLedger {
|
||||
/// Create a new shared ledger
|
||||
pub fn new(node_id: String) -> Self {
|
||||
Self {
|
||||
ledger: Arc::new(Mutex::new(Ledger::new(node_id))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a clone of the ledger Arc
|
||||
pub fn clone_ledger(&self) -> Arc<Mutex<Ledger>> {
|
||||
self.ledger.clone()
|
||||
}
|
||||
|
||||
/// Add a new entry to the ledger
|
||||
pub fn add_entry(&self, data: serde_json::Value) -> Result<LedgerEntry, String> {
|
||||
let mut ledger = self.ledger.lock().unwrap();
|
||||
ledger.add_entry(data)
|
||||
}
|
||||
|
||||
/// Get all entries in the ledger
|
||||
pub fn get_entries(&self) -> Vec<LedgerEntry> {
|
||||
let ledger = self.ledger.lock().unwrap();
|
||||
ledger.get_entries().clone()
|
||||
}
|
||||
|
||||
/// Get the last entry in the ledger
|
||||
pub fn get_last_entry(&self) -> Option<LedgerEntry> {
|
||||
let ledger = self.ledger.lock().unwrap();
|
||||
ledger.get_last_entry().cloned()
|
||||
}
|
||||
|
||||
/// Add a pending entry that has been received from another node
|
||||
pub fn add_pending_entry(&self, entry: LedgerEntry) {
|
||||
let mut ledger = self.ledger.lock().unwrap();
|
||||
ledger.add_pending_entry(entry);
|
||||
}
|
||||
|
||||
/// Process pending entries and add them to the chain if they are valid
|
||||
pub fn process_pending_entries(&self) -> Vec<LedgerEntry> {
|
||||
let mut ledger = self.ledger.lock().unwrap();
|
||||
ledger.process_pending_entries()
|
||||
}
|
||||
|
||||
/// Add a known node to the network
|
||||
pub fn add_known_node(&self, node_id: String) {
|
||||
let mut ledger = self.ledger.lock().unwrap();
|
||||
ledger.add_known_node(node_id);
|
||||
}
|
||||
|
||||
/// Get all known nodes in the network
|
||||
pub fn get_known_nodes(&self) -> HashSet<String> {
|
||||
let ledger = self.ledger.lock().unwrap();
|
||||
ledger.get_known_nodes().clone()
|
||||
}
|
||||
}
|
2
crates/gsio-node/src/lib.rs
Normal file
2
crates/gsio-node/src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod ledger;
|
||||
pub mod p2p;
|
137
crates/gsio-node/src/main.rs
Normal file
137
crates/gsio-node/src/main.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
use axum::routing::get;
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
use socketioxide::{
|
||||
extract::{AckSender, Data, SocketRef},
|
||||
SocketIo,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tracing::info;
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
use uuid::Uuid;
|
||||
|
||||
mod ledger;
|
||||
mod p2p;
|
||||
|
||||
use ledger::SharedLedger;
|
||||
use p2p::P2PManager;
|
||||
|
||||
// Handle regular client connections
|
||||
async fn on_connect(socket: SocketRef, Data(data): Data<JsonValue>, p2p_manager: Arc<P2PManager>) {
|
||||
info!(ns = socket.ns(), ?socket.id, "Socket.IO client connected");
|
||||
socket.emit("auth", &data).ok();
|
||||
|
||||
// Set up basic message handlers
|
||||
socket.on("message", |socket: SocketRef, Data(data): Data<JsonValue>| async move {
|
||||
info!(?data, "Received event:");
|
||||
socket.emit("message-back", &data).ok();
|
||||
});
|
||||
|
||||
socket.on("ping", |socket: SocketRef, Data(data): Data<JsonValue>| async move {
|
||||
socket.emit("pong", &data).ok();
|
||||
});
|
||||
|
||||
socket.on(
|
||||
"message-with-ack",
|
||||
|Data(data): Data<JsonValue>, ack: AckSender| async move {
|
||||
info!(?data, "Received event");
|
||||
ack.send(&data).ok();
|
||||
},
|
||||
);
|
||||
|
||||
// Set up ledger-related handlers
|
||||
let p2p_manager_clone = p2p_manager.clone();
|
||||
socket.on(
|
||||
"add_ledger_entry",
|
||||
move |socket: SocketRef, Data(data): Data<JsonValue>| {
|
||||
let p2p_manager = p2p_manager_clone.clone();
|
||||
async move {
|
||||
info!(?data, "Adding ledger entry");
|
||||
|
||||
// Add the entry to the ledger
|
||||
match p2p_manager.ledger.add_entry(data) {
|
||||
Ok(entry) => {
|
||||
// Broadcast the entry to all connected nodes
|
||||
p2p_manager.broadcast_entry(entry.clone());
|
||||
|
||||
// Send the entry back to the client
|
||||
socket.emit("ledger_entry_added", &serde_json::to_value(entry).unwrap()).ok();
|
||||
},
|
||||
Err(e) => {
|
||||
socket.emit("error", &json!({ "error": e })).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let p2p_manager_clone = p2p_manager.clone();
|
||||
socket.on("get_ledger", move |socket: SocketRef| {
|
||||
let p2p_manager = p2p_manager_clone.clone();
|
||||
async move {
|
||||
info!("Getting ledger entries");
|
||||
|
||||
// Get all entries in the ledger
|
||||
let entries = p2p_manager.ledger.get_entries();
|
||||
|
||||
// Send the entries to the client
|
||||
socket.emit("ledger_entries", &serde_json::to_value(entries).unwrap()).ok();
|
||||
}
|
||||
});
|
||||
|
||||
let p2p_manager_clone = p2p_manager.clone();
|
||||
socket.on("get_known_nodes", move |socket: SocketRef| {
|
||||
let p2p_manager = p2p_manager_clone.clone();
|
||||
async move {
|
||||
info!("Getting known nodes");
|
||||
|
||||
// Get all known nodes
|
||||
let nodes = p2p_manager.ledger.get_known_nodes();
|
||||
|
||||
// Send the nodes to the client
|
||||
socket.emit("known_nodes", &json!({ "nodes": nodes })).ok();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle p2p node connections
|
||||
async fn on_p2p_connect(socket: SocketRef, Data(data): Data<JsonValue>, p2p_manager: Arc<P2PManager>) {
|
||||
info!(ns = socket.ns(), ?socket.id, "P2P node connected");
|
||||
|
||||
// Handle the connection in the p2p manager
|
||||
p2p_manager.handle_connection(socket, data);
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing::subscriber::set_global_default(FmtSubscriber::default())?;
|
||||
|
||||
// Generate a unique ID for this node
|
||||
let node_id = Uuid::new_v4().to_string();
|
||||
info!("Starting node with ID: {}", node_id);
|
||||
|
||||
// Create the shared ledger
|
||||
let ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
// Create the p2p manager
|
||||
let p2p_manager = Arc::new(P2PManager::new(node_id, ledger));
|
||||
|
||||
let (layer, io) = SocketIo::new_layer();
|
||||
|
||||
// Set up namespaces
|
||||
let p2p_manager_clone = p2p_manager.clone();
|
||||
io.ns("/", move |s, d| on_connect(s, d, p2p_manager_clone.clone()));
|
||||
|
||||
let p2p_manager_clone = p2p_manager.clone();
|
||||
io.ns("/p2p", move |s, d| on_p2p_connect(s, d, p2p_manager_clone.clone()));
|
||||
|
||||
let app = axum::Router::new()
|
||||
.route("/", get(|| async { "GSIO-Net Distributed Ledger Node" }))
|
||||
.layer(layer);
|
||||
|
||||
info!("Starting server on port 3000");
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
332
crates/gsio-node/src/p2p.rs
Normal file
332
crates/gsio-node/src/p2p.rs
Normal file
@@ -0,0 +1,332 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
use socketioxide::extract::{Data, SocketRef};
|
||||
use tracing::info;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::ledger::{LedgerEntry, SharedLedger};
|
||||
|
||||
/// Types of messages that can be sent between nodes
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum MessageType {
|
||||
/// Announce a new node joining the network
|
||||
NodeAnnounce,
|
||||
/// Request the list of known nodes
|
||||
NodeListRequest,
|
||||
/// Response with the list of known nodes
|
||||
NodeListResponse,
|
||||
/// Announce a new ledger entry
|
||||
EntryAnnounce,
|
||||
/// Request a specific ledger entry
|
||||
EntryRequest,
|
||||
/// Response with a requested ledger entry
|
||||
EntryResponse,
|
||||
/// Request all ledger entries
|
||||
LedgerSyncRequest,
|
||||
/// Response with all ledger entries
|
||||
LedgerSyncResponse,
|
||||
}
|
||||
|
||||
/// A message sent between nodes in the p2p network
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct P2PMessage {
|
||||
/// Type of message
|
||||
pub message_type: MessageType,
|
||||
/// Unique ID for this message
|
||||
pub message_id: String,
|
||||
/// ID of the node that sent this message
|
||||
pub sender_id: String,
|
||||
/// ID of the node that should receive this message (empty for broadcast)
|
||||
pub recipient_id: String,
|
||||
/// The actual message payload
|
||||
pub payload: JsonValue,
|
||||
}
|
||||
|
||||
impl P2PMessage {
|
||||
/// Create a new p2p message
|
||||
pub fn new(
|
||||
message_type: MessageType,
|
||||
sender_id: String,
|
||||
recipient_id: String,
|
||||
payload: JsonValue,
|
||||
) -> Self {
|
||||
Self {
|
||||
message_type,
|
||||
message_id: Uuid::new_v4().to_string(),
|
||||
sender_id,
|
||||
recipient_id,
|
||||
payload,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages p2p communication between nodes
|
||||
pub struct P2PManager {
|
||||
/// The ID of this node
|
||||
node_id: String,
|
||||
/// The shared ledger
|
||||
pub ledger: SharedLedger,
|
||||
/// Connected sockets by node ID
|
||||
connected_nodes: Arc<Mutex<HashMap<String, SocketRef>>>,
|
||||
}
|
||||
|
||||
impl P2PManager {
|
||||
/// Create a new p2p manager
|
||||
pub fn new(node_id: String, ledger: SharedLedger) -> Self {
|
||||
Self {
|
||||
node_id,
|
||||
ledger,
|
||||
connected_nodes: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the node ID
|
||||
pub fn node_id(&self) -> &str {
|
||||
&self.node_id
|
||||
}
|
||||
|
||||
/// Get a clone of the connected nodes Arc
|
||||
pub fn clone_connected_nodes(&self) -> Arc<Mutex<HashMap<String, SocketRef>>> {
|
||||
self.connected_nodes.clone()
|
||||
}
|
||||
|
||||
/// Handle a new connection from another node
|
||||
pub fn handle_connection(&self, socket: SocketRef, data: JsonValue) {
|
||||
info!(ns = socket.ns(), ?socket.id, "P2P node connected");
|
||||
|
||||
// Extract the node ID from the connection data
|
||||
let node_id = match data.get("node_id") {
|
||||
Some(id) => id.as_str().unwrap_or("unknown").to_string(),
|
||||
None => "unknown".to_string(),
|
||||
};
|
||||
|
||||
// Add the node to the connected nodes
|
||||
{
|
||||
let mut connected_nodes = self.connected_nodes.lock().unwrap();
|
||||
connected_nodes.insert(node_id.clone(), socket.clone());
|
||||
}
|
||||
|
||||
// Add the node to the known nodes in the ledger
|
||||
self.ledger.add_known_node(node_id.clone());
|
||||
|
||||
// Send a node announce message to all other nodes
|
||||
self.broadcast_message(P2PMessage::new(
|
||||
MessageType::NodeAnnounce,
|
||||
self.node_id.clone(),
|
||||
"".to_string(),
|
||||
json!({ "node_id": node_id }),
|
||||
));
|
||||
|
||||
// Set up event handlers for this socket
|
||||
self.setup_socket_handlers(socket);
|
||||
}
|
||||
|
||||
/// Set up event handlers for a socket
|
||||
fn setup_socket_handlers(&self, socket: SocketRef) {
|
||||
let p2p_manager = self.clone();
|
||||
|
||||
// Handle p2p messages
|
||||
socket.on("p2p_message", move |socket: SocketRef, Data(data): Data<JsonValue>| {
|
||||
let p2p_manager = p2p_manager.clone();
|
||||
async move {
|
||||
info!(?data, "Received p2p message");
|
||||
|
||||
// Parse the message
|
||||
let message: P2PMessage = match serde_json::from_value(data) {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => {
|
||||
info!("Error parsing p2p message: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle the message
|
||||
p2p_manager.handle_message(socket, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Handle a p2p message
|
||||
fn handle_message(&self, socket: SocketRef, message: P2PMessage) {
|
||||
match message.message_type {
|
||||
MessageType::NodeAnnounce => self.handle_node_announce(message),
|
||||
MessageType::NodeListRequest => self.handle_node_list_request(socket, message),
|
||||
MessageType::EntryAnnounce => self.handle_entry_announce(message),
|
||||
MessageType::EntryRequest => self.handle_entry_request(socket, message),
|
||||
MessageType::LedgerSyncRequest => self.handle_ledger_sync_request(socket, message),
|
||||
_ => info!("Unhandled message type: {:?}", message.message_type),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a node announce message
|
||||
fn handle_node_announce(&self, message: P2PMessage) {
|
||||
// Extract the node ID from the message
|
||||
let node_id = match message.payload.get("node_id") {
|
||||
Some(id) => id.as_str().unwrap_or("unknown").to_string(),
|
||||
None => "unknown".to_string(),
|
||||
};
|
||||
|
||||
// Add the node to the known nodes in the ledger
|
||||
self.ledger.add_known_node(node_id);
|
||||
}
|
||||
|
||||
/// Handle a node list request message
|
||||
fn handle_node_list_request(&self, socket: SocketRef, message: P2PMessage) {
|
||||
// Get the list of known nodes
|
||||
let known_nodes = self.ledger.get_known_nodes();
|
||||
|
||||
// Send the response
|
||||
let response = P2PMessage::new(
|
||||
MessageType::NodeListResponse,
|
||||
self.node_id.clone(),
|
||||
message.sender_id,
|
||||
json!({ "nodes": known_nodes }),
|
||||
);
|
||||
|
||||
socket.emit("p2p_message", &serde_json::to_value(response).unwrap()).ok();
|
||||
}
|
||||
|
||||
/// Handle an entry announce message
|
||||
fn handle_entry_announce(&self, message: P2PMessage) {
|
||||
// Extract the entry from the message
|
||||
let entry: LedgerEntry = match serde_json::from_value(message.payload.clone()) {
|
||||
Ok(entry) => entry,
|
||||
Err(e) => {
|
||||
info!("Error parsing entry announce: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Add the entry to the pending entries
|
||||
self.ledger.add_pending_entry(entry);
|
||||
|
||||
// Process pending entries
|
||||
let added_entries = self.ledger.process_pending_entries();
|
||||
|
||||
// Announce any new entries that were added
|
||||
for entry in added_entries {
|
||||
self.broadcast_entry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle an entry request message
|
||||
fn handle_entry_request(&self, socket: SocketRef, message: P2PMessage) {
|
||||
// Extract the entry ID from the message
|
||||
let entry_id = match message.payload.get("entry_id") {
|
||||
Some(id) => id.as_str().unwrap_or("").to_string(),
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
// Find the entry in the ledger
|
||||
let entries = self.ledger.get_entries();
|
||||
let entry = entries.iter().find(|e| e.id == entry_id);
|
||||
|
||||
// Send the response
|
||||
if let Some(entry) = entry {
|
||||
let response = P2PMessage::new(
|
||||
MessageType::EntryResponse,
|
||||
self.node_id.clone(),
|
||||
message.sender_id,
|
||||
serde_json::to_value(entry).unwrap(),
|
||||
);
|
||||
|
||||
socket.emit("p2p_message", &serde_json::to_value(response).unwrap()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a ledger sync request message
|
||||
fn handle_ledger_sync_request(&self, socket: SocketRef, message: P2PMessage) {
|
||||
// Get all entries in the ledger
|
||||
let entries = self.ledger.get_entries();
|
||||
|
||||
// Send the response
|
||||
let response = P2PMessage::new(
|
||||
MessageType::LedgerSyncResponse,
|
||||
self.node_id.clone(),
|
||||
message.sender_id,
|
||||
serde_json::to_value(entries).unwrap(),
|
||||
);
|
||||
|
||||
socket.emit("p2p_message", &serde_json::to_value(response).unwrap()).ok();
|
||||
}
|
||||
|
||||
/// Broadcast a message to all connected nodes
|
||||
pub fn broadcast_message(&self, message: P2PMessage) {
|
||||
let connected_nodes = self.connected_nodes.lock().unwrap();
|
||||
|
||||
for (_, socket) in connected_nodes.iter() {
|
||||
socket.emit("p2p_message", &serde_json::to_value(message.clone()).unwrap()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Broadcast a new ledger entry to all connected nodes
|
||||
pub fn broadcast_entry(&self, entry: LedgerEntry) {
|
||||
let message = P2PMessage::new(
|
||||
MessageType::EntryAnnounce,
|
||||
self.node_id.clone(),
|
||||
"".to_string(),
|
||||
serde_json::to_value(entry).unwrap(),
|
||||
);
|
||||
|
||||
self.broadcast_message(message);
|
||||
}
|
||||
|
||||
/// Send a message to a specific node
|
||||
pub fn send_message(&self, recipient_id: String, message: P2PMessage) -> bool {
|
||||
let connected_nodes = self.connected_nodes.lock().unwrap();
|
||||
|
||||
if let Some(socket) = connected_nodes.get(&recipient_id) {
|
||||
socket.emit("p2p_message", &serde_json::to_value(message).unwrap()).is_ok()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Request the list of known nodes from a specific node
|
||||
pub fn request_node_list(&self, recipient_id: String) -> bool {
|
||||
let message = P2PMessage::new(
|
||||
MessageType::NodeListRequest,
|
||||
self.node_id.clone(),
|
||||
recipient_id.clone(),
|
||||
json!({}),
|
||||
);
|
||||
|
||||
self.send_message(recipient_id, message)
|
||||
}
|
||||
|
||||
/// Request a specific ledger entry from a specific node
|
||||
pub fn request_entry(&self, recipient_id: String, entry_id: String) -> bool {
|
||||
let message = P2PMessage::new(
|
||||
MessageType::EntryRequest,
|
||||
self.node_id.clone(),
|
||||
recipient_id.clone(),
|
||||
json!({ "entry_id": entry_id }),
|
||||
);
|
||||
|
||||
self.send_message(recipient_id, message)
|
||||
}
|
||||
|
||||
/// Request all ledger entries from a specific node
|
||||
pub fn request_ledger_sync(&self, recipient_id: String) -> bool {
|
||||
let message = P2PMessage::new(
|
||||
MessageType::LedgerSyncRequest,
|
||||
self.node_id.clone(),
|
||||
recipient_id.clone(),
|
||||
json!({}),
|
||||
);
|
||||
|
||||
self.send_message(recipient_id, message)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for P2PManager {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
node_id: self.node_id.clone(),
|
||||
ledger: self.ledger.clone(),
|
||||
connected_nodes: self.connected_nodes.clone(),
|
||||
}
|
||||
}
|
||||
}
|
41
crates/gsio-node/tests/basic_test.rs
Normal file
41
crates/gsio-node/tests/basic_test.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use axum::routing::get;
|
||||
use serde_json::json;
|
||||
use socketioxide::{
|
||||
extract::{Data, SocketRef},
|
||||
SocketIo,
|
||||
};
|
||||
|
||||
// Test the Socket.IO server setup
|
||||
#[test]
|
||||
fn test_socketio_setup() {
|
||||
let (_, io) = SocketIo::new_layer();
|
||||
// Set up a namespace to verify it works
|
||||
io.ns("/", |_socket: SocketRef, _data: Data<serde_json::Value>| async move {});
|
||||
// If we got here without errors, the setup is successful
|
||||
assert!(true);
|
||||
}
|
||||
|
||||
// Test the on_connect handler
|
||||
#[tokio::test]
|
||||
async fn test_on_connect_handler() {
|
||||
let (_, io) = SocketIo::new_layer();
|
||||
|
||||
// Define a simple handler for testing
|
||||
io.ns("/", |socket: SocketRef, Data(data): Data<serde_json::Value>| async move {
|
||||
// Echo back the auth data
|
||||
socket.emit("auth", &data).ok();
|
||||
});
|
||||
|
||||
// If we got here without errors, the namespace was set up successfully
|
||||
assert!(true);
|
||||
}
|
||||
|
||||
// Test the Socket.IO layer creation
|
||||
#[test]
|
||||
fn test_socketio_layer() {
|
||||
// Just test that we can create a Socket.IO layer
|
||||
let (layer, _) = SocketIo::new_layer();
|
||||
|
||||
// If we got here without errors, the layer was created successfully
|
||||
assert!(true);
|
||||
}
|
150
crates/gsio-node/tests/ledger_test.rs
Normal file
150
crates/gsio-node/tests/ledger_test.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
use gsio_node::ledger::{LedgerEntry, Ledger, SharedLedger};
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn test_ledger_entry_creation() {
|
||||
// Create a new ledger entry
|
||||
let data = json!({ "message": "Test entry" });
|
||||
let previous_hash = "0".repeat(64);
|
||||
let creator_node_id = "test-node-1".to_string();
|
||||
|
||||
let entry = LedgerEntry::new(data.clone(), previous_hash.clone(), creator_node_id.clone());
|
||||
|
||||
// Verify the entry fields
|
||||
assert_eq!(entry.data, data);
|
||||
assert_eq!(entry.previous_hash, previous_hash);
|
||||
assert_eq!(entry.creator_node_id, creator_node_id);
|
||||
assert!(!entry.hash.is_empty());
|
||||
assert!(entry.signatures.is_empty());
|
||||
|
||||
// Verify the entry is valid
|
||||
assert!(entry.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ledger_add_entry() {
|
||||
// Create a new ledger
|
||||
let node_id = "test-node-1".to_string();
|
||||
let mut ledger = Ledger::new(node_id.clone());
|
||||
|
||||
// Add an entry to the ledger
|
||||
let data = json!({ "message": "Test entry 1" });
|
||||
let result = ledger.add_entry(data.clone());
|
||||
|
||||
// Verify the result
|
||||
assert!(result.is_ok());
|
||||
let entry = result.unwrap();
|
||||
assert_eq!(entry.data, data);
|
||||
assert_eq!(entry.creator_node_id, node_id);
|
||||
|
||||
// Verify the ledger state
|
||||
let entries = ledger.get_entries();
|
||||
assert_eq!(entries.len(), 1);
|
||||
assert_eq!(entries[0].data, data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ledger_chain_integrity() {
|
||||
// Create a new ledger
|
||||
let node_id = "test-node-1".to_string();
|
||||
let mut ledger = Ledger::new(node_id.clone());
|
||||
|
||||
// Add multiple entries to the ledger
|
||||
let data1 = json!({ "message": "Test entry 1" });
|
||||
let data2 = json!({ "message": "Test entry 2" });
|
||||
let data3 = json!({ "message": "Test entry 3" });
|
||||
|
||||
let _entry1 = ledger.add_entry(data1.clone()).unwrap();
|
||||
let _entry2 = ledger.add_entry(data2.clone()).unwrap();
|
||||
let _entry3 = ledger.add_entry(data3.clone()).unwrap();
|
||||
|
||||
// Verify the chain integrity
|
||||
let entries = ledger.get_entries();
|
||||
assert_eq!(entries.len(), 3);
|
||||
|
||||
// First entry should have a previous hash of all zeros
|
||||
assert_eq!(entries[0].previous_hash, "0".repeat(64));
|
||||
|
||||
// Each entry's hash should match the next entry's previous_hash
|
||||
assert_eq!(entries[1].previous_hash, entries[0].hash);
|
||||
assert_eq!(entries[2].previous_hash, entries[1].hash);
|
||||
|
||||
// All entries should be valid
|
||||
for entry in entries {
|
||||
assert!(entry.is_valid());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shared_ledger() {
|
||||
// Create a new shared ledger
|
||||
let node_id = "test-node-1".to_string();
|
||||
let shared_ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
// Add entries to the shared ledger
|
||||
let data1 = json!({ "message": "Test entry 1" });
|
||||
let data2 = json!({ "message": "Test entry 2" });
|
||||
|
||||
let _entry1 = shared_ledger.add_entry(data1.clone()).unwrap();
|
||||
let _entry2 = shared_ledger.add_entry(data2.clone()).unwrap();
|
||||
|
||||
// Verify the entries
|
||||
let entries = shared_ledger.get_entries();
|
||||
assert_eq!(entries.len(), 2);
|
||||
assert_eq!(entries[0].data, data1);
|
||||
assert_eq!(entries[1].data, data2);
|
||||
|
||||
// Verify the last entry
|
||||
let last_entry = shared_ledger.get_last_entry().unwrap();
|
||||
assert_eq!(last_entry.data, data2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pending_entries() {
|
||||
// Create a new shared ledger
|
||||
let node_id = "test-node-1".to_string();
|
||||
let shared_ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
// Add an entry to the ledger
|
||||
let data1 = json!({ "message": "Test entry 1" });
|
||||
let entry1 = shared_ledger.add_entry(data1.clone()).unwrap();
|
||||
|
||||
// Create a pending entry that links to the first entry
|
||||
let data2 = json!({ "message": "Test entry 2" });
|
||||
let entry2 = LedgerEntry::new(data2.clone(), entry1.hash.clone(), "test-node-2".to_string());
|
||||
|
||||
// Add the pending entry
|
||||
shared_ledger.add_pending_entry(entry2.clone());
|
||||
|
||||
// Process pending entries
|
||||
let added_entries = shared_ledger.process_pending_entries();
|
||||
|
||||
// Verify the pending entry was added
|
||||
assert_eq!(added_entries.len(), 1);
|
||||
assert_eq!(added_entries[0].data, data2);
|
||||
|
||||
// Verify the ledger state
|
||||
let entries = shared_ledger.get_entries();
|
||||
assert_eq!(entries.len(), 2);
|
||||
assert_eq!(entries[0].data, data1);
|
||||
assert_eq!(entries[1].data, data2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_known_nodes() {
|
||||
// Create a new shared ledger
|
||||
let node_id = "test-node-1".to_string();
|
||||
let shared_ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
// Add known nodes
|
||||
shared_ledger.add_known_node("test-node-2".to_string());
|
||||
shared_ledger.add_known_node("test-node-3".to_string());
|
||||
shared_ledger.add_known_node("test-node-2".to_string()); // Duplicate should be ignored
|
||||
|
||||
// Verify the known nodes
|
||||
let known_nodes = shared_ledger.get_known_nodes();
|
||||
assert_eq!(known_nodes.len(), 3); // Including the original node
|
||||
assert!(known_nodes.contains(&node_id));
|
||||
assert!(known_nodes.contains(&"test-node-2".to_string()));
|
||||
assert!(known_nodes.contains(&"test-node-3".to_string()));
|
||||
}
|
392
crates/gsio-node/tests/p2p_test.rs
Normal file
392
crates/gsio-node/tests/p2p_test.rs
Normal file
@@ -0,0 +1,392 @@
|
||||
use gsio_node::p2p::{P2PMessage, MessageType};
|
||||
use gsio_node::ledger::{LedgerEntry, SharedLedger};
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
use tracing::info;
|
||||
|
||||
// Mock SocketRef for testing
|
||||
#[derive(Clone)]
|
||||
struct MockSocketRef {
|
||||
id: String,
|
||||
sent_messages: Arc<Mutex<Vec<serde_json::Value>>>,
|
||||
}
|
||||
|
||||
impl MockSocketRef {
|
||||
fn new(id: &str) -> Self {
|
||||
Self {
|
||||
id: id.to_string(),
|
||||
sent_messages: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit<T: serde::Serialize>(&self, _event: &str, data: &T) -> Result<(), String> {
|
||||
let value = serde_json::to_value(data).unwrap();
|
||||
let mut messages = self.sent_messages.lock().unwrap();
|
||||
messages.push(value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_sent_messages(&self) -> Vec<serde_json::Value> {
|
||||
let messages = self.sent_messages.lock().unwrap();
|
||||
messages.clone()
|
||||
}
|
||||
|
||||
fn ns(&self) -> &str {
|
||||
"/"
|
||||
}
|
||||
}
|
||||
|
||||
// Test-specific P2PManager implementation
|
||||
struct P2PManager {
|
||||
/// The ID of this node
|
||||
node_id: String,
|
||||
/// The shared ledger
|
||||
pub ledger: SharedLedger,
|
||||
/// Connected sockets by node ID
|
||||
connected_nodes: Arc<Mutex<HashMap<String, MockSocketRef>>>,
|
||||
}
|
||||
|
||||
impl P2PManager {
|
||||
/// Create a new p2p manager
|
||||
pub fn new(node_id: String, ledger: SharedLedger) -> Self {
|
||||
Self {
|
||||
node_id,
|
||||
ledger,
|
||||
connected_nodes: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the node ID
|
||||
pub fn node_id(&self) -> &str {
|
||||
&self.node_id
|
||||
}
|
||||
|
||||
/// Get a clone of the connected nodes Arc
|
||||
pub fn clone_connected_nodes(&self) -> Arc<Mutex<HashMap<String, MockSocketRef>>> {
|
||||
self.connected_nodes.clone()
|
||||
}
|
||||
|
||||
/// Handle a new connection from another node
|
||||
pub fn handle_connection(&self, socket: MockSocketRef, data: JsonValue) {
|
||||
info!(ns = socket.ns(), ?socket.id, "P2P node connected");
|
||||
|
||||
// Extract the node ID from the connection data
|
||||
let node_id = match data.get("node_id") {
|
||||
Some(id) => id.as_str().unwrap_or("unknown").to_string(),
|
||||
None => "unknown".to_string(),
|
||||
};
|
||||
|
||||
// Add the node to the connected nodes
|
||||
{
|
||||
let mut connected_nodes = self.connected_nodes.lock().unwrap();
|
||||
connected_nodes.insert(node_id.clone(), socket.clone());
|
||||
}
|
||||
|
||||
// Add the node to the known nodes in the ledger
|
||||
self.ledger.add_known_node(node_id.clone());
|
||||
|
||||
// In the real implementation, we would set up event handlers for this socket
|
||||
// but for testing purposes, we don't need to do that
|
||||
}
|
||||
|
||||
/// Broadcast a message to all connected nodes
|
||||
pub fn broadcast_message(&self, message: P2PMessage) {
|
||||
let connected_nodes = self.connected_nodes.lock().unwrap();
|
||||
|
||||
for (_, socket) in connected_nodes.iter() {
|
||||
socket.emit("p2p_message", &serde_json::to_value(message.clone()).unwrap()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Broadcast a new ledger entry to all connected nodes
|
||||
pub fn broadcast_entry(&self, entry: LedgerEntry) {
|
||||
let message = P2PMessage::new(
|
||||
MessageType::EntryAnnounce,
|
||||
self.node_id.clone(),
|
||||
"".to_string(),
|
||||
serde_json::to_value(entry).unwrap(),
|
||||
);
|
||||
|
||||
self.broadcast_message(message);
|
||||
}
|
||||
|
||||
/// Send a message to a specific node
|
||||
pub fn send_message(&self, recipient_id: String, message: P2PMessage) -> bool {
|
||||
let connected_nodes = self.connected_nodes.lock().unwrap();
|
||||
|
||||
if let Some(socket) = connected_nodes.get(&recipient_id) {
|
||||
socket.emit("p2p_message", &serde_json::to_value(message).unwrap()).is_ok()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Request the list of known nodes from a specific node
|
||||
pub fn request_node_list(&self, recipient_id: String) -> bool {
|
||||
let message = P2PMessage::new(
|
||||
MessageType::NodeListRequest,
|
||||
self.node_id.clone(),
|
||||
recipient_id.clone(),
|
||||
json!({}),
|
||||
);
|
||||
|
||||
self.send_message(recipient_id, message)
|
||||
}
|
||||
|
||||
/// Request a specific ledger entry from a specific node
|
||||
pub fn request_entry(&self, recipient_id: String, entry_id: String) -> bool {
|
||||
let message = P2PMessage::new(
|
||||
MessageType::EntryRequest,
|
||||
self.node_id.clone(),
|
||||
recipient_id.clone(),
|
||||
json!({ "entry_id": entry_id }),
|
||||
);
|
||||
|
||||
self.send_message(recipient_id, message)
|
||||
}
|
||||
|
||||
/// Request all ledger entries from a specific node
|
||||
pub fn request_ledger_sync(&self, recipient_id: String) -> bool {
|
||||
let message = P2PMessage::new(
|
||||
MessageType::LedgerSyncRequest,
|
||||
self.node_id.clone(),
|
||||
recipient_id.clone(),
|
||||
json!({}),
|
||||
);
|
||||
|
||||
self.send_message(recipient_id, message)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_p2p_message_creation() {
|
||||
let message_type = MessageType::NodeAnnounce;
|
||||
let sender_id = "test-node-1".to_string();
|
||||
let recipient_id = "test-node-2".to_string();
|
||||
let payload = json!({ "node_id": "test-node-1" });
|
||||
|
||||
let message = P2PMessage::new(
|
||||
message_type,
|
||||
sender_id.clone(),
|
||||
recipient_id.clone(),
|
||||
payload.clone(),
|
||||
);
|
||||
|
||||
// Verify the message fields
|
||||
assert!(matches!(message.message_type, MessageType::NodeAnnounce));
|
||||
assert_eq!(message.sender_id, sender_id);
|
||||
assert_eq!(message.recipient_id, recipient_id);
|
||||
assert_eq!(message.payload, payload);
|
||||
assert!(!message.message_id.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_p2p_manager_creation() {
|
||||
let node_id = "test-node-1".to_string();
|
||||
let ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
let p2p_manager = P2PManager::new(node_id.clone(), ledger);
|
||||
|
||||
// Verify the manager fields
|
||||
assert_eq!(p2p_manager.node_id(), &node_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_connection() {
|
||||
let node_id = "test-node-1".to_string();
|
||||
let ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
let p2p_manager = P2PManager::new(node_id.clone(), ledger);
|
||||
|
||||
// Create a mock socket
|
||||
let socket = MockSocketRef::new("socket-1");
|
||||
|
||||
// Handle a connection
|
||||
let connection_data = json!({
|
||||
"node_id": "test-node-2"
|
||||
});
|
||||
|
||||
p2p_manager.handle_connection(socket.clone(), connection_data);
|
||||
|
||||
// Verify the node was added to known nodes
|
||||
let known_nodes = p2p_manager.ledger.get_known_nodes();
|
||||
assert!(known_nodes.contains(&"test-node-2".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_broadcast_message() {
|
||||
let node_id = "test-node-1".to_string();
|
||||
let ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
let p2p_manager = P2PManager::new(node_id.clone(), ledger);
|
||||
|
||||
// Add some connected nodes
|
||||
let socket1 = MockSocketRef::new("socket-1");
|
||||
let socket2 = MockSocketRef::new("socket-2");
|
||||
|
||||
{
|
||||
let connected_nodes_arc = p2p_manager.clone_connected_nodes();
|
||||
let mut connected_nodes = connected_nodes_arc.lock().unwrap();
|
||||
connected_nodes.insert("test-node-2".to_string(), socket1.clone());
|
||||
connected_nodes.insert("test-node-3".to_string(), socket2.clone());
|
||||
}
|
||||
|
||||
// Create a message to broadcast
|
||||
let message = P2PMessage::new(
|
||||
MessageType::NodeAnnounce,
|
||||
node_id.clone(),
|
||||
"".to_string(),
|
||||
json!({ "node_id": node_id }),
|
||||
);
|
||||
|
||||
// Broadcast the message
|
||||
p2p_manager.broadcast_message(message);
|
||||
|
||||
// Verify the message was sent to all connected nodes
|
||||
let messages1 = socket1.get_sent_messages();
|
||||
let messages2 = socket2.get_sent_messages();
|
||||
|
||||
assert_eq!(messages1.len(), 1);
|
||||
assert_eq!(messages2.len(), 1);
|
||||
|
||||
let sent_message1 = &messages1[0];
|
||||
let sent_message2 = &messages2[0];
|
||||
|
||||
assert_eq!(sent_message1["sender_id"], node_id);
|
||||
assert_eq!(sent_message2["sender_id"], node_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_send_message() {
|
||||
let node_id = "test-node-1".to_string();
|
||||
let ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
let p2p_manager = P2PManager::new(node_id.clone(), ledger);
|
||||
|
||||
// Add a connected node
|
||||
let socket = MockSocketRef::new("socket-1");
|
||||
|
||||
{
|
||||
let connected_nodes_arc = p2p_manager.clone_connected_nodes();
|
||||
let mut connected_nodes = connected_nodes_arc.lock().unwrap();
|
||||
connected_nodes.insert("test-node-2".to_string(), socket.clone());
|
||||
}
|
||||
|
||||
// Create a message to send
|
||||
let message = P2PMessage::new(
|
||||
MessageType::NodeListRequest,
|
||||
node_id.clone(),
|
||||
"test-node-2".to_string(),
|
||||
json!({}),
|
||||
);
|
||||
|
||||
// Send the message
|
||||
let result = p2p_manager.send_message("test-node-2".to_string(), message);
|
||||
|
||||
// Verify the message was sent
|
||||
assert!(result);
|
||||
|
||||
let messages = socket.get_sent_messages();
|
||||
assert_eq!(messages.len(), 1);
|
||||
|
||||
let sent_message = &messages[0];
|
||||
assert_eq!(sent_message["sender_id"], node_id);
|
||||
assert_eq!(sent_message["recipient_id"], "test-node-2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_node_list() {
|
||||
let node_id = "test-node-1".to_string();
|
||||
let ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
let p2p_manager = P2PManager::new(node_id.clone(), ledger);
|
||||
|
||||
// Add a connected node
|
||||
let socket = MockSocketRef::new("socket-1");
|
||||
|
||||
{
|
||||
let connected_nodes_arc = p2p_manager.clone_connected_nodes();
|
||||
let mut connected_nodes = connected_nodes_arc.lock().unwrap();
|
||||
connected_nodes.insert("test-node-2".to_string(), socket.clone());
|
||||
}
|
||||
|
||||
// Request the node list
|
||||
let result = p2p_manager.request_node_list("test-node-2".to_string());
|
||||
|
||||
// Verify the request was sent
|
||||
assert!(result);
|
||||
|
||||
let messages = socket.get_sent_messages();
|
||||
assert_eq!(messages.len(), 1);
|
||||
|
||||
let sent_message = &messages[0];
|
||||
assert_eq!(sent_message["sender_id"], node_id);
|
||||
assert_eq!(sent_message["recipient_id"], "test-node-2");
|
||||
assert_eq!(sent_message["message_type"], "NodeListRequest");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_entry() {
|
||||
let node_id = "test-node-1".to_string();
|
||||
let ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
let p2p_manager = P2PManager::new(node_id.clone(), ledger);
|
||||
|
||||
// Add a connected node
|
||||
let socket = MockSocketRef::new("socket-1");
|
||||
|
||||
{
|
||||
let connected_nodes_arc = p2p_manager.clone_connected_nodes();
|
||||
let mut connected_nodes = connected_nodes_arc.lock().unwrap();
|
||||
connected_nodes.insert("test-node-2".to_string(), socket.clone());
|
||||
}
|
||||
|
||||
// Request an entry
|
||||
let entry_id = "test-entry-1".to_string();
|
||||
let result = p2p_manager.request_entry("test-node-2".to_string(), entry_id.clone());
|
||||
|
||||
// Verify the request was sent
|
||||
assert!(result);
|
||||
|
||||
let messages = socket.get_sent_messages();
|
||||
assert_eq!(messages.len(), 1);
|
||||
|
||||
let sent_message = &messages[0];
|
||||
assert_eq!(sent_message["sender_id"], node_id);
|
||||
assert_eq!(sent_message["recipient_id"], "test-node-2");
|
||||
assert_eq!(sent_message["message_type"], "EntryRequest");
|
||||
assert_eq!(sent_message["payload"]["entry_id"], entry_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_ledger_sync() {
|
||||
let node_id = "test-node-1".to_string();
|
||||
let ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
let p2p_manager = P2PManager::new(node_id.clone(), ledger);
|
||||
|
||||
// Add a connected node
|
||||
let socket = MockSocketRef::new("socket-1");
|
||||
|
||||
{
|
||||
let connected_nodes_arc = p2p_manager.clone_connected_nodes();
|
||||
let mut connected_nodes = connected_nodes_arc.lock().unwrap();
|
||||
connected_nodes.insert("test-node-2".to_string(), socket.clone());
|
||||
}
|
||||
|
||||
// Request ledger sync
|
||||
let result = p2p_manager.request_ledger_sync("test-node-2".to_string());
|
||||
|
||||
// Verify the request was sent
|
||||
assert!(result);
|
||||
|
||||
let messages = socket.get_sent_messages();
|
||||
assert_eq!(messages.len(), 1);
|
||||
|
||||
let sent_message = &messages[0];
|
||||
assert_eq!(sent_message["sender_id"], node_id);
|
||||
assert_eq!(sent_message["recipient_id"], "test-node-2");
|
||||
assert_eq!(sent_message["message_type"], "LedgerSyncRequest");
|
||||
}
|
999
crates/gsio-relay/Cargo.lock
generated
Normal file
999
crates/gsio-relay/Cargo.lock
generated
Normal file
@@ -0,0 +1,999 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console_error_panic_hook"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"potential_utf",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_core"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_collections",
|
||||
"icu_normalizer_data",
|
||||
"icu_properties",
|
||||
"icu_provider",
|
||||
"smallvec",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer_data"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_collections",
|
||||
"icu_locale_core",
|
||||
"icu_properties_data",
|
||||
"icu_provider",
|
||||
"potential_utf",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locale_core",
|
||||
"stable_deref_trait",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
|
||||
dependencies = [
|
||||
"idna_adapter",
|
||||
"smallvec",
|
||||
"utf8_iter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna_adapter"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
|
||||
dependencies = [
|
||||
"icu_normalizer",
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "realscheme"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"futures",
|
||||
"http",
|
||||
"worker",
|
||||
"worker-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-wasm-bindgen"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-wasm-bindgen"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.45.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-streams"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "worker"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "727789ca7eff9733efbea9d0e97779edc1cf1926e98aee7d7d8afe32805458aa"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"js-sys",
|
||||
"matchit",
|
||||
"pin-project",
|
||||
"serde",
|
||||
"serde-wasm-bindgen 0.6.5",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"worker-kv",
|
||||
"worker-macros",
|
||||
"worker-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "worker-kv"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f06d4d1416a9f8346ee9123b0d9a11b3cfa38e6cfb5a139698017d1597c4d41"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"serde-wasm-bindgen 0.5.0",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "worker-macros"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d625c24570ba9207a2617476013335f28a95cbe513e59bb814ffba092a18058"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-bindgen-macro-support",
|
||||
"worker-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "worker-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34563340d41016b4381257c5a16b0d2bc590dbe00500ecfbebcaa16f5f85ce90"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
25
crates/gsio-relay/Cargo.toml
Normal file
25
crates/gsio-relay/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "gsio-relay"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
authors = [ "geoffsee <>" ]
|
||||
|
||||
[package.metadata.release]
|
||||
release = false
|
||||
|
||||
# https://github.com/rustwasm/wasm-pack/issues/1247
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = false
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
worker = { version = "0.5.0", features = ['http'] }
|
||||
worker-macros = { version = "0.5.0", features = ['http'] }
|
||||
console_error_panic_hook = { version = "0.1.1" }
|
||||
http = "1.1"
|
||||
futures = "0.3.31"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.37"
|
134
crates/gsio-relay/README.md
Normal file
134
crates/gsio-relay/README.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# gsio-relay
|
||||
|
||||
A Rust-based WebSocket server implemented as a Cloudflare Worker that serves as a relay in the GSIO-Net distributed ledger system.
|
||||
|
||||
## Overview
|
||||
|
||||
The gsio-relay component is a WebSocket server that:
|
||||
- Accepts WebSocket connections from gsio-node instances
|
||||
- Relays messages between nodes to enable peer-to-peer communication
|
||||
- Runs as a Cloudflare Worker for global distribution and reliability
|
||||
- Facilitates the synchronization of distributed ledgers across nodes
|
||||
|
||||
## Features
|
||||
|
||||
- **WebSocket Server**: Provides real-time bidirectional communication
|
||||
- **Cloudflare Worker**: Runs on Cloudflare's edge network for low latency
|
||||
- **Message Relay**: Forwards messages between nodes in the network
|
||||
- **Lightweight**: Minimal implementation focused on efficient message passing
|
||||
- **Scalable**: Can handle many concurrent connections
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Rust (latest stable version)
|
||||
- Wrangler CLI (for Cloudflare Workers development)
|
||||
- Node.js (for running Wrangler)
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
# Clone the repository (if you haven't already)
|
||||
git clone <repository-url>
|
||||
cd gsio-net
|
||||
|
||||
# Install Wrangler CLI if you haven't already
|
||||
npm install -g wrangler
|
||||
|
||||
# Build the gsio-relay component
|
||||
cd crates/gsio-relay
|
||||
cargo install -q worker-build && worker-build --release
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Running Locally
|
||||
|
||||
```bash
|
||||
# Run the worker locally
|
||||
wrangler dev
|
||||
```
|
||||
|
||||
The WebSocket server will start on port 8787 by default.
|
||||
|
||||
### Deploying to Cloudflare
|
||||
|
||||
```bash
|
||||
# Deploy to Cloudflare
|
||||
wrangler publish
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
The worker can be configured using the `wrangler.toml` file:
|
||||
|
||||
```toml
|
||||
name = "gsio-relay"
|
||||
type = "javascript"
|
||||
account_id = "<your-account-id>"
|
||||
workers_dev = true
|
||||
compatibility_date = "2023-01-01"
|
||||
|
||||
[build]
|
||||
command = "cargo install -q worker-build && worker-build --release"
|
||||
|
||||
[build.upload]
|
||||
format = "modules"
|
||||
main = "./worker/worker.mjs"
|
||||
```
|
||||
|
||||
## WebSocket Protocol
|
||||
|
||||
The gsio-relay server accepts WebSocket connections and relays messages between connected clients. The protocol is simple:
|
||||
|
||||
1. Connect to the WebSocket server
|
||||
2. Send messages as text
|
||||
3. Receive echoed messages from the server
|
||||
|
||||
In the GSIO-Net system, nodes use this relay to exchange P2P messages for ledger synchronization and node discovery.
|
||||
|
||||
## Examples
|
||||
|
||||
### Connecting to the Relay
|
||||
|
||||
```javascript
|
||||
// Using browser WebSocket API
|
||||
const websocket = new WebSocket("wss://gsio-relay.your-worker.workers.dev");
|
||||
|
||||
websocket.addEventListener("open", () => {
|
||||
console.log("Connected to relay server");
|
||||
|
||||
// Send a message
|
||||
websocket.send(JSON.stringify({
|
||||
message_type: "NodeAnnounce",
|
||||
sender_id: "node-id",
|
||||
recipient_id: "",
|
||||
payload: { node_id: "node-id" }
|
||||
}));
|
||||
});
|
||||
|
||||
websocket.addEventListener("message", (event) => {
|
||||
console.log("Message received:", event.data);
|
||||
});
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
The gsio-relay component is a simple WebSocket server that:
|
||||
|
||||
1. Accepts incoming WebSocket connections
|
||||
2. Receives messages from connected clients
|
||||
3. Echoes messages back to the sender (in the current implementation)
|
||||
4. In a more advanced implementation, it would relay messages to the appropriate recipients
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Run tests
|
||||
wrangler dev --test
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[Add license information here]
|
222
crates/gsio-relay/bun.lock
Normal file
222
crates/gsio-relay/bun.lock
Normal file
@@ -0,0 +1,222 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "realscheme",
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"wrangler": "latest",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="],
|
||||
|
||||
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.3.2", "", { "peerDependencies": { "unenv": "2.0.0-rc.17", "workerd": "^1.20250508.0" }, "optionalPeers": ["workerd"] }, "sha512-MtUgNl+QkQyhQvv5bbWP+BpBC1N0me4CHHuP2H4ktmOMKdB/6kkz/lo+zqiA4mEazb4y+1cwyNjVrQ2DWeE4mg=="],
|
||||
|
||||
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250525.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-L5l+7sSJJT2+riR5rS3Q3PKNNySPjWfRIeaNGMVRi1dPO6QPi4lwuxfRUFNoeUdilZJUVPfSZvTtj9RedsKznQ=="],
|
||||
|
||||
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250525.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y3IbIdrF/vJWh/WBvshwcSyUh175VAiLRW7963S1dXChrZ1N5wuKGQm9xY69cIGVtitpMJWWW3jLq7J/Xxwm0Q=="],
|
||||
|
||||
"@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250525.0", "", { "os": "linux", "cpu": "x64" }, "sha512-KSyQPAby+c6cpENoO0ayCQlY6QIh28l/+QID7VC1SLXfiNHy+hPNsH1vVBTST6CilHVAQSsy9tCZ9O9XECB8yg=="],
|
||||
|
||||
"@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250525.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nt0FUxS2kQhJUea4hMCNPaetkrAFDhPnNX/ntwcqVlGgnGt75iaAhupWJbU0GB+gIWlKeuClUUnDZqKbicoKyg=="],
|
||||
|
||||
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250525.0", "", { "os": "win32", "cpu": "x64" }, "sha512-mwTj+9f3uIa4NEXR1cOa82PjLa6dbrb3J+KCVJFYIaq7e63VxEzOchCXS4tublT2pmOhmFqkgBMXrxozxNkR2Q=="],
|
||||
|
||||
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="],
|
||||
|
||||
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
|
||||
|
||||
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
|
||||
|
||||
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="],
|
||||
|
||||
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="],
|
||||
|
||||
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="],
|
||||
|
||||
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="],
|
||||
|
||||
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="],
|
||||
|
||||
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="],
|
||||
|
||||
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="],
|
||||
|
||||
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="],
|
||||
|
||||
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="],
|
||||
|
||||
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="],
|
||||
|
||||
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="],
|
||||
|
||||
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.4" }, "os": "linux", "cpu": "s390x" }, "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q=="],
|
||||
|
||||
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="],
|
||||
|
||||
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="],
|
||||
|
||||
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="],
|
||||
|
||||
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.33.5", "", { "dependencies": { "@emnapi/runtime": "^1.2.0" }, "cpu": "none" }, "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg=="],
|
||||
|
||||
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.33.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="],
|
||||
|
||||
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.2.15", "", { "dependencies": { "bun-types": "1.2.15" } }, "sha512-U1ljPdBEphF0nw1MIk0hI7kPg7dFdPyM7EenHsp6W5loNHl7zqy6JQf/RKCgnUn2KDzUpkBwHPnEJEjII594bA=="],
|
||||
|
||||
"@types/node": ["@types/node@24.0.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg=="],
|
||||
|
||||
"acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
|
||||
|
||||
"acorn-walk": ["acorn-walk@8.3.2", "", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="],
|
||||
|
||||
"as-table": ["as-table@1.0.55", "", { "dependencies": { "printable-characters": "^1.0.42" } }, "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ=="],
|
||||
|
||||
"blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.15", "", { "dependencies": { "@types/node": "*" } }, "sha512-NarRIaS+iOaQU1JPfyKhZm4AsUOrwUOqRNHY0XxI8GI8jYxiLXLcdjYMG9UKS+fwWasc1uw1htV9AX24dD+p4w=="],
|
||||
|
||||
"color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
|
||||
|
||||
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"data-uri-to-buffer": ["data-uri-to-buffer@2.0.2", "", {}, "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA=="],
|
||||
|
||||
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
|
||||
|
||||
"esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="],
|
||||
|
||||
"exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="],
|
||||
|
||||
"exsolve": ["exsolve@1.0.5", "", {}, "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"get-source": ["get-source@2.0.12", "", { "dependencies": { "data-uri-to-buffer": "^2.0.0", "source-map": "^0.6.1" } }, "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w=="],
|
||||
|
||||
"glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="],
|
||||
|
||||
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
|
||||
|
||||
"mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
|
||||
|
||||
"miniflare": ["miniflare@4.20250525.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250525.0", "ws": "8.18.0", "youch": "3.3.4", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-4PJlT5WA+hfclFU5Q7xnpG1G1VGYTXaf/3iu6iKQ8IsbSi9QvPTA2bSZ5goCFxmJXDjV4cxttVxB0Wl1CLuQ0w=="],
|
||||
|
||||
"mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="],
|
||||
|
||||
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
|
||||
|
||||
"path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"printable-characters": ["printable-characters@1.0.42", "", {}, "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ=="],
|
||||
|
||||
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||
|
||||
"sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="],
|
||||
|
||||
"simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
|
||||
|
||||
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"stacktracey": ["stacktracey@2.1.8", "", { "dependencies": { "as-table": "^1.0.36", "get-source": "^2.0.12" } }, "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw=="],
|
||||
|
||||
"stoppable": ["stoppable@1.1.0", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||
|
||||
"ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
|
||||
|
||||
"undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
|
||||
|
||||
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||
|
||||
"unenv": ["unenv@2.0.0-rc.17", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.4", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1" } }, "sha512-B06u0wXkEd+o5gOCMl/ZHl5cfpYbDZKAT+HWTL+Hws6jWu7dCiqBBXXXzMFcFVJb8D4ytAnYmxJA83uwOQRSsg=="],
|
||||
|
||||
"workerd": ["workerd@1.20250525.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250525.0", "@cloudflare/workerd-darwin-arm64": "1.20250525.0", "@cloudflare/workerd-linux-64": "1.20250525.0", "@cloudflare/workerd-linux-arm64": "1.20250525.0", "@cloudflare/workerd-windows-64": "1.20250525.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-SXJgLREy/Aqw2J71Oah0Pbu+SShbqbTExjVQyRBTM1r7MG7fS5NUlknhnt6sikjA/t4cO09Bi8OJqHdTkrcnYQ=="],
|
||||
|
||||
"wrangler": ["wrangler@4.19.1", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.3.2", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20250525.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.17", "workerd": "1.20250525.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250525.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-b+ed2SJKauHgndl4Im1wHE+FeSSlrdlEZNuvpc8q/94k4EmRxRkXnwBAsVWuicBxG3HStFLQPGGlvL8wGKTtHw=="],
|
||||
|
||||
"ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
|
||||
|
||||
"youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="],
|
||||
|
||||
"zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="],
|
||||
}
|
||||
}
|
19
crates/gsio-relay/package.json
Normal file
19
crates/gsio-relay/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "realscheme",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "wrangler dev",
|
||||
"ws:client": "bun ./ws_client.ts",
|
||||
"wscat": "wscat -c ws:///localhost:3004/ws",
|
||||
"wscat:ping": "echo 'ping' | wscat -c ws:///localhost:3004/ws"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"wrangler": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
29
crates/gsio-relay/src/lib.rs
Normal file
29
crates/gsio-relay/src/lib.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use futures::StreamExt;
|
||||
use worker::*;
|
||||
|
||||
#[event(fetch)]
|
||||
async fn fetch(req: HttpRequest, _env: Env, _ctx: Context) -> Result<worker::Response> {
|
||||
let upgrade_header = match req.headers().get("Upgrade") {
|
||||
Some(h) => h.to_str().unwrap(),
|
||||
None => "",
|
||||
};
|
||||
if upgrade_header != "websocket" {
|
||||
return worker::Response::error("Expected Upgrade: websocket", 426);
|
||||
}
|
||||
|
||||
let ws = WebSocketPair::new()?;
|
||||
let client = ws.client;
|
||||
let server = ws.server;
|
||||
server.accept()?;
|
||||
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let mut event_stream = server.events().expect("could not open stream");
|
||||
while let Some(event) = event_stream.next().await {
|
||||
match event.expect("received error in websocket") {
|
||||
WebsocketEvent::Message(msg) => server.send(&format!("echo: {:?}", &msg.text().unwrap())).unwrap(),
|
||||
WebsocketEvent::Close(event) => console_log!("{:?}", event),
|
||||
}
|
||||
}
|
||||
});
|
||||
worker::Response::from_websocket(client)
|
||||
}
|
78
crates/gsio-relay/tests/websocket_test.rs
Normal file
78
crates/gsio-relay/tests/websocket_test.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
// Integration testing must be written in tandem with Miniflare
|
||||
// See https://github.com/cloudflare/workers-rs?tab=readme-ov-file#testing-with-miniflare
|
||||
|
||||
// use worker::*;
|
||||
// use wasm_bindgen_test::*;
|
||||
//
|
||||
// wasm_bindgen_test_configure!(run_in_browser);
|
||||
//
|
||||
// // Test the WebSocket upgrade check
|
||||
// #[wasm_bindgen_test]
|
||||
// async fn test_websocket_upgrade_check() {
|
||||
// // Create a mock request without the Upgrade header
|
||||
// let mut headers = Headers::new();
|
||||
// headers.set("Content-Type", "text/plain").unwrap();
|
||||
//
|
||||
// let req = Request::new_with_init(
|
||||
// "https://example.com/",
|
||||
// RequestInit::new()
|
||||
// .with_method(Method::Get)
|
||||
// .with_headers(headers),
|
||||
// ).unwrap();
|
||||
//
|
||||
// // Create a mock environment and context
|
||||
// let env = worker::Env::new();
|
||||
// let ctx = worker::Context::new();
|
||||
//
|
||||
// // Call the fetch function
|
||||
// let result = fetch(req, env, ctx).await;
|
||||
//
|
||||
// // Verify that the response is an error with status 426
|
||||
// assert!(result.is_ok());
|
||||
// let response = result.unwrap();
|
||||
// assert_eq!(response.status_code(), 426);
|
||||
// }
|
||||
//
|
||||
// // Test the WebSocket pair creation
|
||||
// #[wasm_bindgen_test]
|
||||
// async fn test_websocket_pair_creation() {
|
||||
// // Create a WebSocketPair
|
||||
// let ws_pair = WebSocketPair::new();
|
||||
//
|
||||
// // Verify that the pair was created successfully
|
||||
// assert!(ws_pair.is_ok());
|
||||
//
|
||||
// let pair = ws_pair.unwrap();
|
||||
// assert!(Some(pair.client).is_some());
|
||||
// assert!(Some(pair.server).is_some());
|
||||
// }
|
||||
//
|
||||
// // Test the WebSocket server accept
|
||||
// #[wasm_bindgen_test]
|
||||
// async fn test_websocket_server_accept() {
|
||||
// // Create a WebSocketPair
|
||||
// let ws_pair = WebSocketPair::new().unwrap();
|
||||
// let server = ws_pair.server;
|
||||
//
|
||||
// // Accept the connection
|
||||
// let result = server.accept();
|
||||
//
|
||||
// // Verify that the connection was accepted successfully
|
||||
// assert!(result.is_ok());
|
||||
// }
|
||||
//
|
||||
// // Test the WebSocket response creation
|
||||
// #[wasm_bindgen_test]
|
||||
// async fn test_websocket_response_creation() {
|
||||
// // Create a WebSocketPair
|
||||
// let ws_pair = WebSocketPair::new().unwrap();
|
||||
// let client = ws_pair.client;
|
||||
//
|
||||
// // Create a response from the WebSocket
|
||||
// let response = Response::from_websocket(client);
|
||||
//
|
||||
// // Verify that the response was created successfully
|
||||
// assert!(response.is_ok());
|
||||
// let ws_response = response.unwrap();
|
||||
// assert_eq!(ws_response.status_code(), 101); // Switching Protocols
|
||||
// }
|
28
crates/gsio-relay/tsconfig.json
Normal file
28
crates/gsio-relay/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Environment setup & latest features
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
}
|
7
crates/gsio-relay/wrangler.toml
Normal file
7
crates/gsio-relay/wrangler.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
name = "gsio-relay"
|
||||
main = "build/worker/shim.mjs"
|
||||
compatibility_date = "2025-06-10"
|
||||
dev.port = 3001
|
||||
|
||||
[build]
|
||||
command = "cargo install -q worker-build && worker-build --release"
|
Reference in New Issue
Block a user