{"id":37342427,"url":"https://github.com/sockudo/sockudo","last_synced_at":"2026-04-01T22:22:25.867Z","repository":{"id":292414415,"uuid":"885984101","full_name":"sockudo/sockudo","owner":"sockudo","description":"Blazingly fast pusher drop-in replacement written in rust","archived":false,"fork":false,"pushed_at":"2026-03-29T19:27:55.000Z","size":5994,"stargazers_count":526,"open_issues_count":3,"forks_count":35,"subscribers_count":7,"default_branch":"master","last_synced_at":"2026-03-29T21:24:25.181Z","etag":null,"topics":["abbly","broadcasting","pusher","realtime","rust","rust-lang","websocket","websockets","ws"],"latest_commit_sha":null,"homepage":"https://sockudo.io/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sockudo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"countradooku","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":"radooku","thanks_dev":null,"custom":null}},"created_at":"2024-11-09T22:01:43.000Z","updated_at":"2026-03-29T13:21:28.000Z","dependencies_parsed_at":"2026-03-02T04:21:07.323Z","dependency_job_id":null,"html_url":"https://github.com/sockudo/sockudo","commit_stats":null,"previous_names":["rustnsparks/sockudo"],"tags_count":78,"template":false,"template_full_name":null,"purl":"pkg:github/sockudo/sockudo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sockudo%2Fsockudo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sockudo%2Fsockudo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sockudo%2Fsockudo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sockudo%2Fsockudo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sockudo","download_url":"https://codeload.github.com/sockudo/sockudo/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sockudo%2Fsockudo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31292639,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T21:15:39.731Z","status":"ssl_error","status_checked_at":"2026-04-01T21:15:34.046Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["abbly","broadcasting","pusher","realtime","rust","rust-lang","websocket","websockets","ws"],"created_at":"2026-01-16T03:52:20.408Z","updated_at":"2026-04-01T22:22:25.859Z","avatar_url":"https://github.com/sockudo.png","language":"Rust","funding_links":["https://github.com/sponsors/countradooku","https://buymeacoffee.com/radooku"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\r\n  \u003cimg src=\"images/logo.svg\" alt=\"Sockudo Logo\" width=\"200\"\u003e\r\n  \u003ch1 align=\"center\"\u003eSockudo\u003c/h1\u003e\r\n  \u003cp align=\"center\"\u003eA high-performance, scalable WebSocket server for real-time applications, built in Rust.\u003c/p\u003e\r\n  \u003cp align=\"center\"\u003e\r\n    \u003ca href=\"https://github.com/Sockudo/sockudo\"\u003e\u003cimg src=\"https://img.shields.io/github/stars/sockudo/sockudo?style=social\" alt=\"Stars\"\u003e\u003c/a\u003e\r\n    \u003ca href=\"https://github.com/sockudo/sockudo/actions\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/sockudo/sockudo/ci.yml?branch=main\" alt=\"Build Status\"\u003e\u003c/a\u003e\r\n    \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/github/license/sockudo/sockudo\" alt=\"License\"\u003e\u003c/a\u003e\r\n  \u003c/p\u003e\r\n\u003c/p\u003e\r\n\r\n## Star History\r\n\r\n[![Star History Chart](https://api.star-history.com/svg?repos=sockudo/sockudo\u0026type=Date)](https://star-history.com/#Sockudo/sockudo\u0026Date)\r\n\r\n## Features\r\n\r\n- **🚀 High Performance** - Handle 100K+ concurrent connections\r\n- **🔄 Dual Protocol** - V1 for Pusher compatibility, V2 for Sockudo-native with serial numbers, message IDs, and connection recovery\r\n- **🔗 Connection Recovery** - Ably-style serial-based message replay for exactly-once delivery (V2)\r\n- **🆔 Message Idempotency** - Automatic `message_id` on every broadcast, HTTP-level `idempotency_key` deduplication\r\n- **🏗️ Scalable Architecture** - Redis, Redis Cluster, NATS adapters\r\n- **🛡️ Production Ready** - Rate limiting, SSL/TLS, metrics\r\n- **⚡ Async Cleanup** - Non-blocking disconnect handling\r\n- **📊 Real-time Metrics** - Prometheus integration\r\n- **🧠 Delta Compression + Conflation** - Fossil and Xdelta3 (VCDIFF) with per-channel controls\r\n- **🏷️ Tag Filtering** - High-performance server-side filtering with optional tag emission controls\r\n- **🌐 Native WebSocket Engine** - `sockudo_ws` with advanced runtime tuning\r\n- **📦 Official SDKs** - Client and server SDKs for all major platforms\r\n\r\n## What's New in v4\r\n\r\nSockudo v4 introduces **dual protocol versioning**, making Sockudo its own platform while retaining full Pusher compatibility:\r\n\r\n| | Protocol V1 (default) | Protocol V2 |\r\n|---|---|---|\r\n| Event prefix | `pusher:` / `pusher_internal:` | `sockudo:` / `sockudo_internal:` |\r\n| `serial` | Never sent | Always on every message |\r\n| `message_id` | Never sent | Always on every broadcast |\r\n| Connection recovery | Not available | Always available |\r\n| Delta compression | Not available | Native |\r\n| Tag filtering | Not available | Native |\r\n| Compatible SDKs | Official Pusher SDKs | Sockudo client SDKs |\r\n\r\nAdditional v4 changes:\r\n- **TOML configuration** (preferred over JSON) - `config/config.toml`\r\n- **Rebranded SDKs** - All server and client SDKs renamed from `pusher-*` to `sockudo-*`\r\n- **HTTP idempotency** - Atomic `idempotency_key` deduplication via `SET NX` (no race conditions)\r\n- **Replay buffer** - Per-channel message buffer with configurable TTL and max size\r\n\r\n## Official Client SDKs\r\n\r\n| Client | Package | Default Protocol |\r\n|---|---|---|\r\n| JavaScript / TypeScript | `@sockudo/client` | V2 |\r\n| Swift | `SockudoSwift` (SPM) | V2 |\r\n| Kotlin | `io.sockudo:sockudo-kotlin` | V2 |\r\n| Flutter / Dart | `sockudo_flutter` | V2 |\r\n\r\nAll Sockudo client SDKs default to protocol V2 (`sockudo:` prefix, serial tracking, connection recovery). Set `protocolVersion: 1` to use Pusher-compatible mode. For V1 you can also use the official Pusher SDKs directly.\r\n\r\n## Official Server SDKs\r\n\r\n| Language | Package |\r\n|---|---|\r\n| Node.js | `sockudo` |\r\n| PHP | `sockudo/sockudo-php-server` |\r\n| Ruby | `sockudo` gem |\r\n| Go | `github.com/sockudo/sockudo-http-go` |\r\n| Rust | `sockudo-http` |\r\n| Java | `io.sockudo:sockudo-http-java` |\r\n| .NET | `SockudoServer` |\r\n| Swift | `Sockudo` (SPM) |\r\n\r\nAll server SDKs support `idempotency_key` for safe publish retries.\r\n\r\n## Quick Start\r\n\r\n### Docker (Recommended)\r\n\r\n```bash\r\ngit clone https://github.com/sockudo/sockudo.git\r\ncd sockudo\r\nmake up\r\n\r\n# Server runs on http://localhost:6001\r\n# Metrics on http://localhost:9601/metrics\r\n```\r\n\r\n### Kubernetes (Helm)\r\n\r\n```bash\r\nhelm install sockudo ./charts/sockudo\r\n\r\n# Production with Redis adapter and autoscaling\r\nhelm install sockudo ./charts/sockudo \\\n  --set config.adapterDriver=redis \\\n  --set redis.host=redis-master \\\n  --set redis.existingSecret=my-redis-secret \\\n  --set defaultApp.existingSecret=my-default-app-secret \\\n  --set autoscaling.enabled=true \\\n  --set pdb.enabled=true \\\n  --set ingress.enabled=true \\\n  --set serviceMonitor.enabled=true\n```\n\nWhen `defaultApp.existingSecret` is set, the Secret must contain `default-app-secret` and can optionally include `default-app-id` and `default-app-key`.\n\nSee [`charts/sockudo/values.yaml`](charts/sockudo/values.yaml) for all configurable options.\n\r\n### From Source\r\n\r\n```bash\r\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\r\n\r\ngit clone https://github.com/sockudo/sockudo.git\r\ncd sockudo\r\n\r\n# Fast local development build (default - no external dependencies)\r\ncargo run --release\r\n\r\n# Production build with all features\r\ncargo run --release --features full\r\n```\r\n\r\n### Feature Flags\r\n\r\n```bash\r\n# Local development (fastest - default)\r\ncargo build                                    # ~30-50% faster compile\r\n\r\n# With specific backends\r\ncargo build --features \"redis,postgres\"        # Redis + PostgreSQL\r\ncargo build --features \"redis-cluster,mysql\"   # Redis Cluster + MySQL\r\ncargo build --features \"redis,surrealdb\"       # Redis + SurrealDB 3\r\ncargo build --features \"rabbitmq,postgres\"     # RabbitMQ + PostgreSQL\r\ncargo build --features \"google-pubsub,mysql\"   # Google Pub/Sub + MySQL\r\ncargo build --features \"kafka,postgres\"        # Kafka + PostgreSQL\r\n\r\n# Full production build\r\ncargo build --release --features full          # All backends\r\n```\r\n\r\n**Available Features:**\r\n- `local` (default) - In-memory implementations only\r\n- `v2` (default) - All Sockudo V2 features (delta, tag-filtering, recovery)\r\n- `delta` - Delta compression (V2)\r\n- `tag-filtering` - Server-side tag filtering (V2)\r\n- `recovery` - Connection recovery with serial numbers and message IDs (V2)\r\n- `redis` - Redis adapter, cache, queue, rate limiter\r\n- `redis-cluster` - Redis Cluster support\r\n- `nats` - NATS adapter\r\n- `rabbitmq` - RabbitMQ adapter\r\n- `google-pubsub` - Google Cloud Pub/Sub adapter\r\n- `kafka` - Kafka adapter\r\n- `mysql` / `postgres` / `dynamodb` / `surrealdb` / `scylladb` - App manager backends\r\n- `sqs` / `lambda` - AWS integrations\r\n- `full` - All features enabled\r\n\r\n```bash\r\n# Pure Pusher-only server (no V2 features, smallest binary)\r\ncargo build --no-default-features\r\n\r\n# V2 with only delta compression\r\ncargo build --no-default-features --features delta\r\n\r\n# V2 with recovery only (serial + message_id + replay buffer)\r\ncargo build --no-default-features --features recovery\r\n```\r\n\r\n## Basic Usage\r\n\r\n### Protocol V2 (Sockudo-native, default for Sockudo SDKs)\r\n\r\n```javascript\r\nimport { SockudoClient } from '@sockudo/client';\r\n\r\nconst client = new SockudoClient('app-key', {\r\n    wsHost: 'localhost',\r\n    wsPort: 6001,\r\n    forceTLS: false,\r\n    // protocolVersion: 2 is the default\r\n});\r\n\r\nconst channel = client.subscribe('my-channel');\r\nchannel.bind('my-event', (data) =\u003e {\r\n    console.log('Received:', data);\r\n});\r\n```\r\n\r\n```swift\r\nimport SockudoSwift\r\n\r\nlet client = try SockudoClient(\r\n    \"app-key\",\r\n    options: .init(\r\n        cluster: \"local\",\r\n        forceTLS: false,\r\n        enabledTransports: [.ws],\r\n        wsHost: \"127.0.0.1\",\r\n        wsPort: 6001,\r\n        wssPort: 6001\r\n    )\r\n)\r\n\r\nlet channel = client.subscribe(\"my-channel\")\r\nchannel.bind(\"my-event\") { data, _ in\r\n    print(data ?? \"\")\r\n}\r\nclient.connect()\r\n```\r\n\r\n### Protocol V1 (Pusher-compatible)\r\n\r\n```javascript\r\nimport Pusher from 'pusher-js';\r\n\r\nconst pusher = new Pusher('app-key', {\r\n    wsHost: 'localhost',\r\n    wsPort: 6001,\r\n    cluster: '',\r\n    forceTLS: false\r\n});\r\n\r\nconst channel = pusher.subscribe('my-channel');\r\nchannel.bind('my-event', (data) =\u003e {\r\n    console.log('Received:', data);\r\n});\r\n```\r\n\r\n## Configuration\r\n\r\nSockudo uses TOML configuration (preferred) with JSON as a fallback. The server tries `config/config.toml` first, then `config/config.json`.\r\n\r\n### Core Configuration (`config/config.toml`)\r\n\r\n```toml\r\nport = 6001\r\nhost = \"0.0.0.0\"\r\ndebug = false\r\n\r\n[app_manager]\r\ndriver = \"memory\"\r\n\r\n[app_manager.array]\r\n[[app_manager.array.apps]]\r\nid = \"app-id\"\r\nkey = \"app-key\"\r\nsecret = \"app-secret\"\r\nenabled = true\r\n[app_manager.array.apps.policy.limits]\r\nmax_connections = 100000\r\nmax_client_events_per_second = 1000\r\n\r\n[app_manager.array.apps.policy.features]\r\nenable_client_messages = true\r\n\r\n[app_manager.array.apps.policy.channels]\r\nallowed_origins = [\"*\"]\r\n\r\n[[app_manager.array.apps.policy.channels.channel_namespaces]]\r\nname = \"ticker\"\r\nchannel_name_pattern = \"^ticker:[A-Za-z0-9._-]+$\"\r\nmax_channel_name_length = 200\r\n\r\n[[app_manager.array.apps.policy.channels.channel_namespaces]]\r\nname = \"chat\"\r\nchannel_name_pattern = \"^chat:[A-Za-z0-9._-]+$\"\r\nmax_channel_name_length = 200\r\nallow_user_limited_channels = true\r\n\r\n[adapter]\r\ndriver = \"local\"\r\n\r\n[cache]\r\ndriver = \"memory\"\r\n\r\n[queue]\r\ndriver = \"memory\"\r\n```\r\n\r\nPer-app app-manager entries are where V2 channel semantics live, now grouped under `app.policy`: `policy.channels.allowed_origins`, `policy.channels.channel_namespaces`, app-level `policy.idempotency`, `policy.connection_recovery`, and `policy.channels.channel_delta_compression`.\r\n\r\nFor existing MySQL/PostgreSQL app tables, use the explicit migration scripts under [`migrations/mysql`](/Users/radudiaconu/Desktop/Code/Rust/sockudo/migrations/mysql) and [`migrations/postgresql`](/Users/radudiaconu/Desktop/Code/Rust/sockudo/migrations/postgresql) to backfill `policy` and then drop legacy columns during a maintenance window.\r\n\r\n### Environment Variables\r\n\r\n```bash\r\n# Basic settings\r\nPORT=6001\r\nHOST=0.0.0.0\r\nDEBUG=false\r\n\r\n# Default app credentials\r\nSOCKUDO_DEFAULT_APP_ID=app-id\r\nSOCKUDO_DEFAULT_APP_KEY=app-key\r\nSOCKUDO_DEFAULT_APP_SECRET=app-secret\r\n\r\n# Scaling drivers\r\nADAPTER_DRIVER=redis          # local, redis, redis-cluster, nats, rabbitmq, google-pubsub, kafka\r\nCACHE_DRIVER=redis           # memory, redis, redis-cluster, none\r\nQUEUE_DRIVER=redis           # memory, redis, redis-cluster, sqs, none\r\nAPP_MANAGER_DRIVER=surrealdb # memory, mysql, postgres, dynamodb, surrealdb, scylladb\r\n\r\nRABBITMQ_URL=amqp://guest:guest@127.0.0.1:5672/%2f\r\nGOOGLE_PUBSUB_PROJECT_ID=my-gcp-project\r\nKAFKA_BROKERS=127.0.0.1:9092\r\n\r\nDATABASE_SURREALDB_URL=ws://127.0.0.1:8000\r\nDATABASE_SURREALDB_NAMESPACE=sockudo\r\nDATABASE_SURREALDB_DATABASE=sockudo\r\nDATABASE_SURREALDB_USERNAME=root\r\nDATABASE_SURREALDB_PASSWORD=root\r\nDATABASE_SURREALDB_TABLE_NAME=applications\r\n\r\n# V2 feature gates\r\nEPHEMERAL_ENABLED=true\r\nECHO_CONTROL_ENABLED=true\r\nECHO_CONTROL_DEFAULT_ECHO_MESSAGES=true\r\nEVENT_NAME_FILTERING_ENABLED=true\r\n```\r\n\r\n### Delta Compression (`config/config.toml`)\r\n\r\n```toml\r\n[delta_compression]\r\nenabled = true\r\nalgorithm = \"Fossil\"\r\nfull_message_interval = 10\r\nmin_message_size = 100\r\nmax_state_age_secs = 300\r\nmax_channel_states_per_socket = 100\r\nmax_conflation_states_per_channel = 100\r\ncluster_coordination = true\r\nomit_delta_algorithm = true\r\n```\r\n\r\n### Tag Filtering (`config/config.toml`)\r\n\r\n```toml\r\n[tag_filtering]\r\nenabled = true\r\nenable_tags = false\r\n```\r\n\r\n### V2 Message Features (`config/config.toml`)\r\n\r\n```toml\r\n[ephemeral]\r\nenabled = true\r\n\r\n[echo_control]\r\nenabled = true\r\ndefault_echo_messages = true\r\n\r\n[event_name_filtering]\r\nenabled = true\r\nmax_events_per_filter = 50\r\nmax_event_name_length = 200\r\n```\r\n\r\n### WebSocket Runtime (`config/config.toml`)\r\n\r\n```toml\r\n[websocket]\r\nmax_messages = 1000\r\nmax_bytes = 1048576\r\ndisconnect_on_buffer_full = false\r\nmax_message_size = 67108864\r\nmax_frame_size = 16777216\r\nwrite_buffer_size = 16384\r\nmax_backpressure = 1048576\r\nauto_ping = true\r\nping_interval = 30\r\nidle_timeout = 120\r\ncompression = \"disabled\"\r\n```\r\n\r\n### Performance Tuning\r\n\r\n```bash\r\nSOCKUDO_DEFAULT_APP_MAX_CONNECTIONS=100000\r\nSOCKUDO_DEFAULT_APP_MAX_CLIENT_EVENTS_PER_SECOND=10000\r\n\r\nCLEANUP_QUEUE_BUFFER_SIZE=50000\r\nCLEANUP_BATCH_SIZE=25\r\nCLEANUP_WORKER_THREADS=auto\r\n\r\nADAPTER_BUFFER_MULTIPLIER_PER_CPU=128\r\n```\r\n\r\n### Database Pooling\r\n\r\n```toml\r\n[database_pooling]\r\nenabled = true\r\nmin = 2\r\nmax = 10\r\n\r\n[database.mysql]\r\npool_min = 4\r\npool_max = 32\r\n\r\n[database.postgres]\r\npool_min = 2\r\npool_max = 16\r\n```\r\n\r\n## Deployment Scenarios\r\n\r\n| Scenario | CPU/RAM | Adapter | Cache | Queue | Max Connections |\r\n|----------|---------|---------|-------|-------|-----------------|\r\n| **Development** | 1vCPU/1GB | local | memory | memory | 1K |\r\n| **Small Production** | 2vCPU/2GB | redis | redis | redis | 10K |\r\n| **High Traffic** | 4vCPU/4GB+ | redis | redis | redis | 50K+ |\r\n| **Multi-Region** | 8vCPU/8GB+ | redis-cluster | redis-cluster | redis-cluster | 100K+ |\r\n\r\nAll scenarios can be deployed via Docker Compose or the included [Helm chart](charts/sockudo/) on Kubernetes with built-in HPA autoscaling.\r\n\r\n## Architecture\r\n\r\n```\r\n┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐\r\n│   Load Balancer │────│   Sockudo Node  │────│   Redis Cluster │\r\n│    (Nginx)      │    │    (Rust/Tokio) │    │  (State Store)  │\r\n└─────────────────┘    └─────────────────┘    └─────────────────┘\r\n         │                       │                       │\r\n         │              ┌─────────────────┐             │\r\n         └──────────────│   Sockudo Node  │─────────────┘\r\n                        │    (Rust/Tokio) │\r\n                        └─────────────────┘\r\n```\r\n\r\n## Documentation\r\n\r\n- **[Full Documentation](docs/)** - Complete setup and configuration guide\r\n- **[Client Overview](docs/content/3.client/1.overview.md)** - Official SDKs and runtime targets\r\n- **[Docker Deployment](docker-compose.yml)** - Production-ready containers\r\n- **[Helm Charts](charts/sockudo/)** - Kubernetes deployment with HPA, PDB, ServiceMonitor\r\n\r\n## Testing\r\n\r\n```bash\r\n# Run all server tests\r\nmake test\r\n\r\n# Interactive WebSocket testing\r\ncd test/interactive \u0026\u0026 npm install \u0026\u0026 npm start\r\n# Open http://localhost:3000\r\n\r\n# Load testing\r\nmake benchmark\r\n```\r\n\r\nClient and server SDK tests are run from their respective repositories. See each SDK's README for instructions.\r\n\r\n## Contributing\r\n\r\n1. Fork the repository\r\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\r\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\r\n4. Push to the branch (`git push origin feature/amazing-feature`)\r\n5. Open a Pull Request\r\n\r\n## License\r\n\r\nLicensed under the [MIT License](LICENSE).\r\n\r\n## Support\r\n\r\n- **Issues**: [GitHub Issues](https://github.com/sockudo/sockudo/issues)\r\n- **Discussions**: [GitHub Discussions](https://github.com/sockudo/sockudo/discussions)\r\n- **Documentation**: [sockudo.io](https://sockudo.io)\r\n- **Discord**: [Join our Discord](https://discord.gg/ySfNxfh2gZ)\r\n- **X**: [@sockudorealtime](https://x.com/sockudorealtime)\r\n- **Email**: [sockudorealtime](mailto:office@sockudo.io)\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsockudo%2Fsockudo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsockudo%2Fsockudo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsockudo%2Fsockudo/lists"}