Skip to content

Repository files navigation

Crystal

warning: this app won't update mastery points in real time if you compile it yourself since there is a compile-time secret for function authentication that are not included in this repo, to stop malicious actors from modifying the database. this will be removed later once user's are required to log in via RSO (almost no effort to the user)

desktop client to help you keep track of your league challenges! screenshots incoming

uses tauri with rust and react, typescript, and shadcn/ui with tailwind

special thanks to sylv for help with irelia

Crystal isn't endorsed by Riot Games and doesn't reflect the views or opinions of Riot Games or anyone officially involved in producing or managing Riot Games properties. Riot Games, and all associated properties are trademarks or registered trademarks of Riot Games, Inc.

---
title: System Architecture
---
flowchart TD
    subgraph External["External Services"]
        LCU["League Client (LCU API)"]
        RIOT["Riot API\n(challenges, mastery)"]
        CDRAGON["CommunityDragon CDN\n(champions, skins, statstones)"]
    end

    subgraph Supabase["Supabase"]
        EDGE["Edge Function\n(get-user)"]
        PG["PostgreSQL\n(users table)"]
        EDGE <--> PG
        EDGE <--> RIOT
    end

    subgraph Tauri["Tauri Desktop App"]
        subgraph Rust["Rust Backend"]
            LCU_REST["LCU REST Client\n(irelia)"]
            LCU_WS["LCU WebSocket\n(irelia)"]
            CONN_LOOP["Connection Loop\n(5s poll + health check)"]
            CMDS["Tauri Commands\n(lcu_request, http_request, get_connected)"]
            TRAY["System Tray\n(show/hide, quit)"]
        end

        subgraph Frontend["React Frontend"]
            MAIN["main.tsx\n(providers)"]
            APP["App.tsx\n(data fetching & events)"]
            CTX_STATIC["StaticDataContext\n(challenges, mastery, champions, skins, eternals, loot)"]
            CTX_SESSION["SessionDataContext\n(champ select, gameflow)"]
            LAYOUT["Layout\n(sidebar, titlebar)"]
            PAGES["Pages\n(Home, Mastery, Lobby, Profile, Skins, Eternals, Team Builder, Settings, Debug, User)"]
        end
    end

    LCU <-->|REST via irelia| LCU_REST
    LCU -->|WebSocket subscriptions| LCU_WS
    CONN_LOOP -->|manages| LCU_REST
    CONN_LOOP -->|manages| LCU_WS
    CONN_LOOP -->|emits connection/lcu-refresh| APP

    LCU_WS -->|"emits champ-select,\ngameflow events"| APP
    CMDS <-->|"Tauri IPC\n(invoke)"| APP

    APP -->|"invoke http_request"| CDRAGON
    APP -->|"supabase-js\n(functions.invoke)"| EDGE
    APP -->|updates| CTX_STATIC
    APP -->|updates| CTX_SESSION
    CTX_STATIC --> PAGES
    CTX_SESSION --> PAGES
    MAIN --> LAYOUT --> APP
Loading
---
title: Data Refresh Pipeline (refresh_data)
---
flowchart TD
    START(["refresh_data() triggered"]) --> CHECK{Connected?}
    CHECK -->|No| SKIP["Skip refresh\nsetLoading(false)"]
    CHECK -->|Yes| PARALLEL

    subgraph PARALLEL["Phase 1 — Parallel Requests"]
        direction LR
        P1["LCU: /lol-challenges/v1/\nchallenges/local-player → lcu_data"]
        P2["LCU: /lol-summoner/v1/\ncurrent-summoner → summoner info"]
        P3["LCU: /lol-loot/v2/\nplayer-loot-map → loot_data"]
    end

    P2 --> REGION["LCU: /riotclient/region-locale"]
    REGION --> SUPA_AND_SKINS

    subgraph SUPA_AND_SKINS["Concurrent Sub-Requests"]
        direction LR
        SUPA["Supabase get-user\n(riot_id + region)\n→ riot_data, mastery_data"]
        SKINS["LCU: /lol-champions/v1/\ninventories/.../skins-minimal\n→ minimal_skins"]
    end

    SUPA --> MASTERY_CHECK{"Supabase returned\nmastery data?"}
    MASTERY_CHECK -->|Yes| USE_DB["Use database\nmastery_data"]
    MASTERY_CHECK -->|No| LCU_MASTERY["Fallback: LCU\n/lol-champion-mastery/v1/\nlocal-player/champion-mastery"]

    PARALLEL --> PHASE2

    subgraph PHASE2["Phase 2 — Sequential After Phase 1"]
        ETERNALS["Per-champion eternals\n/lol-statstones/v2/\nplayer-statstones-self/{id}\n(one request per champion)"]
    end

    PHASE2 --> DONE["setLoading(false, 100)"]
Loading
---
title: Supabase Edge Function (get-user)
---
flowchart TD
    REQ["Client Request (riot_id, region)"] --> AUTH{"x-secret header valid?"}
    AUTH -->|"No (logged, not blocked)"| CONTINUE
    AUTH -->|Yes| CONTINUE

    CONTINUE --> RIOT_ACCOUNT["Riot API: GET /riot/account/v1/accounts/by-riot-id/{name}/{tag} → puuid"]

    RIOT_ACCOUNT --> DB_CHECK{"User exists in PostgreSQL?"}

    DB_CHECK -->|No| FETCH_FRESH["Riot API: GET challenges + mastery for puuid"]
    FETCH_FRESH --> INSERT["INSERT into users table"]
    INSERT --> RETURN_FRESH["Return fresh riot_data + mastery_data"]

    DB_CHECK -->|Yes| STALE_CHECK{"Last update > 10 min ago?"}
    STALE_CHECK -->|Yes| FETCH_UPDATE["Riot API: GET challenges + mastery"]
    FETCH_UPDATE --> UPDATE["UPDATE users table"]
    UPDATE --> RETURN_UPDATED["Return updated riot_data + mastery_data + cached lcu_data"]

    STALE_CHECK -->|No| RETURN_CACHED["Return cached riot_data + mastery_data + lcu_data"]
Loading

alt text alt text alt text alt text alt text

About

an app that tracks league of legends challenge progress

Topics

Resources

Stars

8 stars

Watchers

2 watching

Forks

Releases

Used by

Contributors

Languages