{"id":47617214,"url":"https://github.com/maraf/sharedspaces","last_synced_at":"2026-05-09T07:06:50.056Z","repository":{"id":346024628,"uuid":"1180796293","full_name":"maraf/SharedSpaces","owner":"maraf","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-08T21:04:37.000Z","size":31487,"stargazers_count":0,"open_issues_count":13,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-08T22:22:40.110Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maraf.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"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}},"created_at":"2026-03-13T12:23:44.000Z","updated_at":"2026-05-08T20:46:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/maraf/SharedSpaces","commit_stats":null,"previous_names":["maraf/sharedspaces"],"tags_count":28,"template":false,"template_full_name":null,"purl":"pkg:github/maraf/SharedSpaces","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maraf%2FSharedSpaces","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maraf%2FSharedSpaces/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maraf%2FSharedSpaces/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maraf%2FSharedSpaces/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maraf","download_url":"https://codeload.github.com/maraf/SharedSpaces/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maraf%2FSharedSpaces/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32810391,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T08:22:46.396Z","status":"online","status_checked_at":"2026-05-09T02:00:06.633Z","response_time":123,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2026-04-01T21:36:59.374Z","updated_at":"2026-05-09T07:06:50.050Z","avatar_url":"https://github.com/maraf.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SharedSpaces\n\n\u003e A self-hostable web platform for real-time file and text sharing via QR code and PIN.\n\n## About\n\n**SharedSpaces** is a lightweight, anonymous collaboration tool that lets users join shared spaces with a simple QR code or PIN — no accounts, no tracking, no friction. Share files, text, and links in real-time with anyone in the space, all under a display name you choose.\n\nThe platform is built for **self-hosting** with minimal dependencies (SQLite, local filesystem) and clean architecture. The server is a pure API with no rendered UI. The client is a standalone web app that can connect to any server — including multiple servers simultaneously. Perfect for quick file drops, event collaboration, classroom sharing, or temporary project spaces.\n\nAnonymous by design: users pick a display name when joining a space. No email, no registration, no persistent identity across spaces. When you're done, leave. When the space is deleted, everything is gone.\n\n## Key Features\n\n- **QR/PIN join** — Scan a code or enter a short PIN to join a space instantly\n- **Real-time sync** — See files and messages appear live via WebSocket (SignalR)\n- **Anonymous identity** — Display name only, no accounts or persistent profiles\n- **Multi-server support** — Connect to multiple servers simultaneously from one client\n- **Self-hostable** — SQLite database, local file storage, zero cloud dependencies\n- **JWT-based access** — Secure, token-based authentication with no expiration\n- **File + text sharing** — Upload files or post text snippets; quota enforced per space\n- **Admin controls** — Create spaces, generate invitations, manage content\n- **CLI tool** — Join spaces, upload files, and sync folders from the terminal\n\n## Screenshots\n\n**Home view** — Your spaces across multiple servers:\n\n![SharedSpaces home view](docs/screenshots/home--desktop.png)\n\n**Inside a space** — Real-time shared content:\n\n![SharedSpaces space view with content](docs/screenshots/space--desktop.png)\n\n## Tech Stack\n\n- **Server:** .NET 10 (ASP.NET Core Web API)\n- **Client:** Lit HTML + Web Components, TypeScript, Vite, Tailwind CSS v4\n- **CLI:** .NET global tool (`System.CommandLine`) with SignalR real-time sync\n- **Real-time:** SignalR (WebSocket)\n- **Database:** SQLite with EF Core (swappable to PostgreSQL)\n- **Auth:** JWT (no expiration; validity = member existence)\n- **Storage:** Local filesystem (abstracted for future cloud providers)\n\n## Getting Started\n\n### Prerequisites\n\n- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)\n- [Node.js 20+](https://nodejs.org/) (for client build)\n\n### Development\n\n1. **Clone the repository:**\n   ```bash\n   git clone https://github.com/maraf/SharedSpaces.git\n   cd SharedSpaces\n   ```\n\n2. **Run the server:**\n   ```bash\n   cd src/SharedSpaces.Server\n   dotnet restore\n   dotnet run\n   ```\n   The API will be available at `https://localhost:7218` (or `http://localhost:5165`). Check the terminal output for the exact URL.\n\n3. **Run the client (in a separate terminal):**\n   ```bash\n   cd src/SharedSpaces.Client\n   npm install\n   npm run dev\n   ```\n   The client will be available at `http://localhost:5173`.\n\n4. **Connect the client to your server:**\n   - Open the client in your browser at `http://localhost:5173`.\n   - Use the client UI to add/connect to your server by entering its URL (e.g., `https://localhost:7218`).\n   - No environment variables are required for API routing; the server URL is chosen at runtime in the client.\n\n## Command-Line Interface (CLI)\n\nThe **SharedSpaces CLI** lets you join spaces, upload files, list spaces, and sync files — all from the terminal, no browser needed.\n\n### Install\n\n```bash\ndotnet tool install --global SharedSpaces.Cli\n```\n\n### Quick Start\n\n**Join a space** with an invitation link or PIN:\n```bash\nsharedspaces join \"https://server.example.com|550e8400-e29b-41d4-a716-446655440000|123456\"\n```\n\n**List your spaces:**\n```bash\nsharedspaces spaces\nsharedspaces spaces --json   # machine-readable output\n```\n\n**Upload a file:**\n```bash\nsharedspaces upload myfile.txt --space-id 550e8400-e29b-41d4-a716-446655440000\n```\n\n**Sync a folder in real-time** (two-way: downloads from the server and uploads local changes):\n```bash\nsharedspaces sync --space-id 550e8400-e29b-41d4-a716-446655440000 --folder ~/shared\n```\n\n### Commands\n\n| Command | Description |\n|---------|-------------|\n| `join \u003curl\u003e` | Exchange an invitation PIN for an access token and store it locally |\n| `spaces` | List all joined spaces (supports `--json` for machine-readable output) |\n| `upload \u003cfile\u003e` | Upload a file to a space (`--space-id` required) |\n| `sync` | Two-way file sync between a space and a local folder (`--space-id`, `--folder` required; `--passive` for periodic polling instead of real-time SignalR) |\n\n### How sync works\n\nThe `sync` command keeps a local folder in sync with a space:\n\n- **Initial download** — Fetches all existing files from the space\n- **Real-time updates** — Receives new files and deletions via SignalR (WebSocket)\n- **Local file watching** — Automatically uploads new files added to the folder\n- **Fallback polling** — Falls back to HTTP polling if the WebSocket connection drops\n- **Passive mode** — Pass `--passive` (with optional `--interval \u003cseconds\u003e`, default `300`) to skip SignalR and pull the journal on a fixed interval instead. Local uploads still happen immediately.\n- **Resilience** — Automatic reconnection with exponential backoff, atomic file writes, deduplication to prevent upload loops\n\nPress `Ctrl+C` to stop syncing gracefully.\n\n### Configuration\n\nTokens are stored in `~/.sharedspaces/config.json`. Each entry contains only the JWT — all metadata (space ID, server URL, display name, space name) is extracted from claims at runtime.\n\nFor full CLI documentation, see [`src/SharedSpaces.Cli/README.md`](src/SharedSpaces.Cli/README.md).\n\n### Building for production\n\n**Server:**\n```bash\ncd src/SharedSpaces.Server\ndotnet publish -c Release -o ./publish\n```\n\n**Client:**\n```bash\ncd src/SharedSpaces.Client\nnpm run build\n```\n\nThe built client will be in `dist/` and can be served by any static file host.\n\n## Project Structure\n\n```\nSharedSpaces/\n├── src/\n│   ├── SharedSpaces.Server/          # ASP.NET Core Web API + SignalR\n│   │   ├── Domain/                   # Entities, value objects\n│   │   ├── Features/                 # Vertical slice: Spaces, Invitations, Tokens, Items, Admin\n│   │   ├── Infrastructure/           # EF Core, file storage, SignalR hub\n│   │   └── Program.cs\n│   ├── SharedSpaces.Client/          # Lit HTML + Web Components SPA\n│   │   ├── e2e/                      # Playwright tests\n│   │   └── src/\n│   │       ├── features/             # join, space-view, admin\n│   │       ├── components/           # Reusable UI components\n│   │       ├── lib/                  # SignalR client, API client, utilities\n│   │       └── index.ts\n│   ├── SharedSpaces.Cli/             # .NET global tool (CLI entry point)\n│   │   └── Commands/                 # join, spaces, upload, sync\n│   └── SharedSpaces.Cli.Core/        # Shared CLI library\n│       ├── Services/                 # API client, config, sync engine\n│       └── Models/                   # Config model, JWT-backed space entry\n├── tests/\n│   ├── SharedSpaces.Server.Tests/\n│   └── SharedSpaces.Cli.Core.Tests/\n├── docs/\n│   └── screenshots/                  # UI screenshots\n└── SharedSpaces.sln\n```\n\n## Architecture\n\nThe server and client are **fully decoupled**. The server is a pure API with no UI. The client is a standalone SPA that connects to any server via its base URL. A single client instance can connect to multiple servers simultaneously, each with its own JWT token for authentication.\n\n**Key design decisions:**\n- **JWT tokens have no expiration** — validity is determined by the existence of a `SpaceMember` record and its `IsRevoked` flag\n- **Invitation PINs are one-time use** — deleted immediately after a token is issued\n- **Space item IDs are client-generated** — the client creates GUIDs and uses PUT/upsert semantics\n- **Multi-server by default** — JWT claims include `server_url` so the client knows where to send requests\n\nFor detailed architecture, domain model, and API behavior, explore the codebase — the code is the spec, and inline comments call out key design decisions.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaraf%2Fsharedspaces","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaraf%2Fsharedspaces","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaraf%2Fsharedspaces/lists"}