diff --git a/README.md b/README.md index 3e2a827e..d3fdd070 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,21 @@ ## Remnawave Subscription Page +### INCY encrypted subscription links + +This fork adds support for INCY encrypted deep links (`incy://crypt1/...`), similar to +the existing `HAPP_CRYPT4_LINK` placeholder for Happ. + +**Setup:** +1. Install the dependency in `frontend`: `npm install @densds/link-encoder` +2. In your app-config JSON (Subscription Page settings), use the placeholder + `{{INCY_CRYPT1_LINK}}` as the `link` value for an INCY button. + +Encryption happens entirely client-side, the same way `HAPP_CRYPT3_LINK` / +`HAPP_CRYPT4_LINK` already work: the subscription URL is built from the already-loaded, +already-validated subscription info (`constructSubscriptionUrl`), never from +unvalidated user input, so there's no backend endpoint involved and nothing for it to +expose. + Learn more about Remnawave [here](https://docs.rw/). # Contributors diff --git a/backend/package.json b/backend/package.json index 4b070d18..e027e25b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -81,4 +81,4 @@ "typescript": "~5.9.3", "webpack-node-externals": "^3.0.0" } -} +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index c1bb6707..10699c35 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,17 @@ services: build: context: . dockerfile: Dockerfile + container_name: remnawave-subscription-page + hostname: remnawave-subscription-page + restart: always env_file: - .env ports: - '127.0.0.1:3010:3010' + networks: + - remnawave-network + +networks: + remnawave-network: + driver: bridge + external: true \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index df05f926..23df2aa1 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,6 +2,27 @@
+ + + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8f52b077..986e9293 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,6 +9,7 @@ "version": "8.0.0", "license": "AGPL-3.0-only", "dependencies": { + "@densds/link-encoder": "^2.0.0", "@gfazioli/mantine-spinner": "4.1.8", "@kastov/cryptohapp": "^1.1.2", "@mantine/core": "9.4.1", @@ -587,6 +588,15 @@ "node": ">=18" } }, + "node_modules/@densds/link-encoder": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@densds/link-encoder/-/link-encoder-2.0.1.tgz", + "integrity": "sha512-lvLW2CBXmdEK+jlX1KNzKHenN4OLHH0BUsvQme9ODZWHJ5FBKLZHg5Mn7dqDgp82uqa684QmYtfBSJWkXrMVyA==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/@epic-web/invariant": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 34360ccd..ae1796f0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,6 +28,7 @@ }, "dependencies": { "@gfazioli/mantine-spinner": "4.1.8", + "@densds/link-encoder": "^2.0.0", "@kastov/cryptohapp": "^1.1.2", "@mantine/core": "9.4.1", "@mantine/hooks": "9.4.1", diff --git a/frontend/src/widgets/main/installation-guide/installation-guide.connector.tsx b/frontend/src/widgets/main/installation-guide/installation-guide.connector.tsx index 6bcf5359..69c38d8b 100644 --- a/frontend/src/widgets/main/installation-guide/installation-guide.connector.tsx +++ b/frontend/src/widgets/main/installation-guide/installation-guide.connector.tsx @@ -15,8 +15,9 @@ import { UnstyledButton } from '@mantine/core' import { notifications } from '@mantine/notifications' +import { encryptLink } from '@densds/link-encoder' import { useClipboard } from '@mantine/hooks' -import { useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import clsx from 'clsx' import { constructSubscriptionUrl } from '@shared/utils/construct-subscription-url' @@ -32,6 +33,16 @@ import classes from './installation-guide.module.css' export type TBlockVariant = 'accordion' | 'cards' | 'minimal' | 'timeline' +// window.open (not location.href) is what actually hands off custom schemes +// (happ://, incy://, ...) to the OS from inside Telegram's Mini App WebView, same +// as the validated fix in the sibling shopbot project — as long as it's called +// synchronously, with no async gap burning through the click's user-activation +// window. It also keeps regular http(s) subscription links in a new tab instead +// of navigating the page itself away. +const navigateToUrl = (url: string) => { + window.open(url, '_blank') +} + interface IProps { BlockRenderer: React.ComponentType