feat: Share game link to a friend#2149
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a “share game” flow that generates a hydralauncher://game?... deep link for a specific repack/source, copies it to clipboard, and supports opening the repacks modal + guiding the recipient to add a missing download source.
Changes:
- Add Share action in the game details hero panel that copies a deep link to the clipboard.
- Extend deep link handling in the main process to route
hydralauncher://gameURIs to the game details page (including repack/source query params). - Update repacks modal to (a) auto-highlight a shared repack and (b) show a “missing source” warning with a CTA to add the shared source; add i18n strings across locales.
Reviewed changes
Copilot reviewed 40 out of 40 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/renderer/src/pages/game-details/modals/repacks-modal.tsx | Accepts shared repack/source params, shows missing-source warning, and adds “shared by friend” badge. |
| src/renderer/src/pages/game-details/modals/repacks-modal.scss | Styles for the missing-source warning section in the repacks modal. |
| src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx | Adds Share button and deep-link generation/copy logic. |
| src/renderer/src/pages/game-details/game-details.tsx | Passes deep link query params down to the repacks modal. |
| src/renderer/src/pages/game-details/game-details-content.tsx | Auto-opens repacks modal when repackId is present in the URL. |
| src/renderer/src/context/game-details/game-details.context.types.ts | Adds getDownloadSourceById to the game details context interface. |
| src/renderer/src/context/game-details/game-details.context.tsx | Loads download sources into context state and exposes getDownloadSourceById. |
| src/main/index.ts | Adds main-process deep link parsing/redirect for hydralauncher://game. |
| src/locales/ar/translation.json | Adds new i18n strings for share flow. |
| src/locales/be/translation.json | Adds new i18n strings for share flow. |
| src/locales/bg/translation.json | Adds new i18n strings for share flow. |
| src/locales/ca/translation.json | Adds new i18n strings for share flow. |
| src/locales/cs/translation.json | Adds new i18n strings for share flow. |
| src/locales/da/translation.json | Adds new i18n strings for share flow. |
| src/locales/de/translation.json | Adds new i18n strings for share flow. |
| src/locales/en/translation.json | Adds new i18n strings for share flow (source-missing warning, shared badge, clipboard toast). |
| src/locales/es/translation.json | Adds new i18n strings for share flow. |
| src/locales/et/translation.json | Adds new i18n strings for share flow. |
| src/locales/fa/translation.json | Adds new i18n strings for share flow. |
| src/locales/fi/translation.json | Adds new i18n strings for share flow. |
| src/locales/fr/translation.json | Adds new i18n strings for share flow. |
| src/locales/hu/translation.json | Adds new i18n strings for share flow. |
| src/locales/id/translation.json | Adds new i18n strings for share flow. |
| src/locales/it/translation.json | Adds new i18n strings for share flow. |
| src/locales/kk/translation.json | Adds new i18n strings for share flow. |
| src/locales/ko/translation.json | Adds new i18n strings for share flow. |
| src/locales/lv/translation.json | Adds new i18n strings for share flow. |
| src/locales/nb/translation.json | Adds new i18n strings for share flow. |
| src/locales/nl/translation.json | Adds new i18n strings for share flow. |
| src/locales/pl/translation.json | Adds new i18n strings for share flow. |
| src/locales/pt-BR/translation.json | Adds new i18n strings for share flow. |
| src/locales/pt-PT/translation.json | Adds new i18n strings for share flow. |
| src/locales/ro/translation.json | Adds new i18n strings for share flow. |
| src/locales/ru/translation.json | Adds new i18n strings for share flow. |
| src/locales/sl/translation.json | Adds new i18n strings for share flow. |
| src/locales/sv/translation.json | Adds new i18n strings for share flow. |
| src/locales/tr/translation.json | Adds new i18n strings for share flow. |
| src/locales/uk/translation.json | Adds new i18n strings for share flow. |
| src/locales/uz/translation.json | Adds new i18n strings for share flow. |
| src/locales/zh/translation.json | Adds new i18n strings for share flow. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| &__shared-source-warning { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
| gap: calc(globals.$spacing-unit * 2); | ||
| padding: calc(globals.$spacing-unit * 1.5) calc(globals.$spacing-unit * 2); | ||
| margin-bottom: calc(globals.$spacing-unit * 2); | ||
| border-radius: 8px; | ||
| background: var(--color-background-light); | ||
| border: 1px solid rgba(globals.$warning-color, 0.35); | ||
| } | ||
|
|
||
| &__shared-source-warning-text { | ||
| margin: 0; | ||
| color: globals.$body-color; | ||
| font-size: globals.$small-font-size; | ||
| } |
There was a problem hiding this comment.
In this SCSS block, the indentation/nesting formatting is inconsistent with the rest of the .repacks-modal BEM selectors in this file (properties and the &__shared-source-warning-text selector aren’t aligned like the surrounding sections). This is likely to be reformatted by Prettier/stylelint and can cause noisy diffs—please re-indent to match the existing pattern used above (e.g., &__repacks, &__filter-top).
| const downloadedRepack = repacks.find((repack) => | ||
| repack.uris.includes(downloadUri) | ||
| ); |
There was a problem hiding this comment.
The repack lookup for the currently-downloaded URI uses an exact match (repack.uris.includes(downloadUri)), but elsewhere (e.g., the “last downloaded option” badge logic) the code uses a substring match. If game.download.uri is only a substring of the repack URI, this will fail to find the repack and sharing will silently do nothing. Consider aligning this matching logic with checkIfLastDownloadedOption (use a .some(uri => uri.includes(downloadUri))-style check) so share works reliably.
| const toggleShareGame = async () => { | ||
| if (!game?.download?.uri) return; | ||
|
|
||
| const downloadUri = game.download.uri; | ||
| setToggleLibraryGameDisabled(true); | ||
|
|
||
| try { | ||
| const downloadedRepack = repacks.find((repack) => | ||
| repack.uris.includes(downloadUri) | ||
| ); | ||
|
|
||
| if (!downloadedRepack) return; | ||
|
|
||
| const source = getDownloadSourceById(downloadedRepack.downloadSourceId); | ||
| const sourceUrl = source?.url; | ||
|
|
||
| if (!sourceUrl) return; | ||
|
|
There was a problem hiding this comment.
toggleShareGame can early-return in several cases (no matching repack, download sources not loaded yet, missing source URL), but the UI provides no feedback to the user. Since this is a user-initiated action, consider showing an error toast (or disabling/hiding the share action until the required data is available) so the user understands why nothing was copied.
| {game.download && ( | ||
| <Button | ||
| onClick={toggleShareGame} | ||
| theme="outline" | ||
| disabled={deleting} | ||
| className="hero-panel-actions__action" | ||
| > | ||
| <ShareIcon /> | ||
| </Button> | ||
| )} |
There was a problem hiding this comment.
The share button sets toggleLibraryGameDisabled while generating/copying the link, but the button itself is only disabled by deleting. This means users can repeatedly click Share while the async operation is in flight. Consider including toggleLibraryGameDisabled in the disabled condition (or use a dedicated isSharing state) to prevent duplicate clicks and make the disabled state consistent with the handler.
| const { | ||
| objectId, | ||
| shopDetails, | ||
| game, | ||
| hasNSFWContentBlocked, | ||
| shop, | ||
| setShowGameOptionsModal, | ||
| setGameOptionsInitialCategory, | ||
| setShowRepacksModal | ||
| } = useContext(gameDetailsContext); |
There was a problem hiding this comment.
There are a couple of formatting issues here that don’t match the surrounding code style: missing trailing comma in the context destructuring and missing semicolon/newline consistency. This is likely to be flagged by formatting/lint tooling—please run the formatter or align with the existing style in this file (commas/semicolons).
| const repackId = searchParams.get("repackId"); | ||
| if (repackId) { | ||
| setShowRepacksModal(true) | ||
| } |
There was a problem hiding this comment.
Missing semicolon after setShowRepacksModal(true) (and the effect body’s formatting) is inconsistent with the rest of the file’s semicolon usage. This can cause lint/format CI failures; please format this block to match the surrounding style.
|



When submitting this pull request, I confirm the following (please check the boxes):
Fill in the PR content:
This PR solves #1994 by adding a way to share a game with another Hydra user through Hydra’s existing deep link support. The goal is to make sharing a game faster and more direct, so instead of asking someone to manually search for a title, the sender can copy a link and send them straight to the same game page in Hydra.
The generated link uses the hydralauncher://game format and includes the information needed to identify the shared repack, such as the game provider, the game ID, the repack ID, and the source URL. For example:
hydralauncher://game?shop=steam&objectId=3590&repackId=n00mN8Jn&sourceUrl=https%3A%2F%2Fhydralinks.cloud%2Fsources%2Ffitgirl.json
With this, Hydra can open the correct game details page and try to point the recipient to the same release that was originally shared.
On the sender side, the share action is available from the game details hero actions. Hydra generates the link using the current download data by matching the installed download with the corresponding repack and resolving the source URL from that repack. The deep link is then copied to the clipboard so it can be sent to a friend.
On the recipient side, opening the link launches or focuses Hydra, parses the deep link, and navigates to the existing game details route. If a repackId is present, the repacks modal opens automatically. When the repack exists locally, it is highlighted with a Shared by friend badge so the user can easily identify the intended release. If the repack is not available because the required source is missing, the game page still opens normally, but the modal shows a warning and provides an action to add the shared source through Hydra’s existing add-source flow.