-
Notifications
You must be signed in to change notification settings - Fork 135
Adding encrypted INCY links #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
5fefecd
c1aad90
5a2334e
efc5114
c2cdca5
351ab0a
3f7b8ad
b624e8b
e0ab594
9139943
b8f9abc
2a125d1
b43ea6c
5036029
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -81,4 +81,4 @@ | |
| "typescript": "~5.9.3", | ||
| "webpack-node-externals": "^3.0.0" | ||
| } | ||
| } | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<IBlockRendererProps> | ||
| hasPlatformApps: Record<TSubscriptionPagePlatformKey, boolean> | ||
|
|
@@ -81,10 +92,61 @@ export const InstallationGuideConnector = (props: IProps) => { | |
| subscription.user.shortUuid | ||
| ) | ||
|
|
||
| const hasIncyButton = useMemo( | ||
| () => | ||
| Object.values(platforms).some((platformConfig) => | ||
| platformConfig?.apps.some((app) => | ||
| app.blocks.some((block) => | ||
| block.buttons.some((button) => button.link === '{{INCY_CRYPT1_LINK}}') | ||
| ) | ||
| ) | ||
| ), | ||
| [platforms] | ||
| ) | ||
|
|
||
| const [incyCryptLink, setIncyCryptLink] = useState<string | undefined>(undefined) | ||
| const [incyCryptLoading, setIncyCryptLoading] = useState(() => hasIncyButton) | ||
|
|
||
| useEffect(() => { | ||
| if (!hasIncyButton) return | ||
|
|
||
| let cancelled = false | ||
|
|
||
| // name is capped at 128 chars per @densds/link-encoder's encryptLink contract | ||
| const name = subscription.user.username.slice(0, 128) | ||
|
|
||
| encryptLink(subscriptionUrl, { name }) | ||
| .then((link) => { | ||
| if (!cancelled) setIncyCryptLink(link) | ||
| }) | ||
| .catch(() => { | ||
| // incyCryptLink stays undefined; handleButtonClick surfaces | ||
| // the "not ready" notification when the user clicks the button | ||
| }) | ||
| .finally(() => { | ||
| if (!cancelled) setIncyCryptLoading(false) | ||
| }) | ||
|
|
||
| return () => { | ||
| cancelled = true | ||
| } | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [hasIncyButton, subscriptionUrl, subscription.user.username]) | ||
|
|
||
| const handleButtonClick = (button: TSubscriptionPageButtonConfig) => { | ||
| let formattedUrl: string | undefined | ||
|
|
||
| if (button.type === 'subscriptionLink' || button.type === 'copyButton') { | ||
| if (button.link === '{{INCY_CRYPT1_LINK}}') { | ||
| if (!incyCryptLink) { | ||
| notifications.show({ | ||
| title: 'Error', | ||
| message: 'INCY link is not ready yet, please try again in a moment', | ||
| color: 'red' | ||
| }) | ||
| return | ||
| } | ||
| formattedUrl = incyCryptLink | ||
| } else if (button.type === 'subscriptionLink' || button.type === 'copyButton') { | ||
| formattedUrl = TemplateEngine.formatWithMetaInfo(button.link, { | ||
| username: subscription.user.username, | ||
| subscriptionUrl | ||
|
|
@@ -104,13 +166,17 @@ export const InstallationGuideConnector = (props: IProps) => { | |
| break | ||
| } | ||
| case 'external': { | ||
| window.open(button.link, '_blank') | ||
| if (formattedUrl) { | ||
| navigateToUrl(formattedUrl) | ||
| } else { | ||
| window.open(button.link, '_blank') | ||
| } | ||
| break | ||
| } | ||
| case 'subscriptionLink': { | ||
| if (!formattedUrl) return | ||
|
|
||
| window.open(formattedUrl, '_blank') | ||
| navigateToUrl(formattedUrl) | ||
| break | ||
|
Comment on lines
176
to
180
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The change from |
||
| } | ||
| default: | ||
|
|
@@ -129,6 +195,7 @@ export const InstallationGuideConnector = (props: IProps) => { | |
| {buttons.map((button, index) => ( | ||
| <Button | ||
| key={index} | ||
| disabled={button.link === '{{INCY_CRYPT1_LINK}}' && incyCryptLoading} | ||
| leftSection={ | ||
| <span | ||
| dangerouslySetInnerHTML={{ | ||
|
|
@@ -137,6 +204,7 @@ export const InstallationGuideConnector = (props: IProps) => { | |
| style={{ display: 'flex', alignItems: 'center' }} | ||
| /> | ||
| } | ||
| loading={button.link === '{{INCY_CRYPT1_LINK}}' && incyCryptLoading} | ||
| onClick={() => handleButtonClick(button)} | ||
| radius="md" | ||
| variant={variant} | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@densds/link-encoderfrom PR author@densds/link-encoderis published under the same namespace as this PR's author (densds) and returns no results in standard npm-registry searches. Unlike the existing@kastov/cryptohappdependency (which is independently verifiable), this package has no visible GitHub repository, changelog, or community adoption. Since it handles subscription URL encryption on the client side — processing real user subscription URLs — it sits in a sensitive position. Before merging, the project maintainers should verify the package's source code and confirm its npm publish provenance matches the author's identity.