feat(core): avoid popup window being blocked (#6451)

This commit is contained in:
liuyi 2024-04-03 16:50:09 +08:00 committed by GitHub
parent 3e9e2ce93b
commit 6fa4b7da54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 78 additions and 28 deletions

View File

@ -4,6 +4,7 @@ import { registerAffineCommand } from '@toeverything/infra';
import type { createStore } from 'jotai';
import { openSettingModalAtom } from '../atoms';
import { popupWindow } from '../utils';
export function registerAffineHelpCommands({
t,
@ -20,7 +21,7 @@ export function registerAffineHelpCommands({
icon: <NewIcon />,
label: t['com.affine.cmdk.affine.whats-new'](),
run() {
window.open(runtimeConfig.changelogUrl, '_blank');
popupWindow(runtimeConfig.changelogUrl);
},
})
);

View File

@ -4,6 +4,7 @@ import { Loading } from '@affine/component/ui/loading';
import { AffineShapeIcon } from '@affine/core/components/page-list';
import { useCredentialsRequirement } from '@affine/core/hooks/affine/use-server-config';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { popupWindow } from '@affine/core/utils';
import { SubscriptionPlan, type SubscriptionRecurring } from '@affine/graphql';
import {
changePasswordMutation,
@ -48,7 +49,7 @@ const usePaymentRedirect = () => {
successCallbackLink: null,
},
});
window.open(checkoutUrl, '_self', 'norefferer');
popupWindow(checkoutUrl);
}, [recurring, plan, coupon, idempotencyKey, checkoutSubscription]);
};

View File

@ -11,7 +11,7 @@ import { useCallback } from 'react';
import { useAppSettingHelper } from '../../../../../hooks/affine/use-app-setting-helper';
import { appIconMap, appNames } from '../../../../../pages/open-app';
import { mixpanel } from '../../../../../utils';
import { mixpanel, popupWindow } from '../../../../../utils';
import { relatedLinks } from './config';
import * as styles from './style.css';
import { UpdateCheckSection } from './update-check-section';
@ -99,7 +99,7 @@ export const AboutAffine = () => {
desc={t['com.affine.aboutAFFiNE.changelog.description']()}
style={{ cursor: 'pointer' }}
onClick={() => {
window.open(runtimeConfig.changelogUrl, '_blank');
popupWindow(runtimeConfig.changelogUrl);
}}
>
<ArrowRightSmallIcon />
@ -143,7 +143,7 @@ export const AboutAffine = () => {
<div
className={styles.communityItem}
onClick={() => {
window.open(link, '_blank');
popupWindow(link);
}}
key={title}
>

View File

@ -31,7 +31,7 @@ import { useMutation } from '../../../../../hooks/use-mutation';
import { useQuery } from '../../../../../hooks/use-query';
import type { SubscriptionMutator } from '../../../../../hooks/use-subscription';
import { useUserSubscription } from '../../../../../hooks/use-subscription';
import { mixpanel } from '../../../../../utils';
import { mixpanel, popupWindow } from '../../../../../utils';
import { SWRErrorBoundary } from '../../../../pure/swr-error-bundary';
import { CancelAction, ResumeAction } from '../plans/actions';
import * as styles from './style.css';
@ -262,7 +262,7 @@ const PaymentMethodUpdater = () => {
const update = useAsyncCallback(async () => {
await trigger(null, {
onSuccess: data => {
window.open(data.createCustomerPortal, '_blank', 'noopener noreferrer');
popupWindow(data.createCustomerPortal);
},
});
}, [trigger]);
@ -361,7 +361,7 @@ const InvoiceLine = ({
const open = useCallback(() => {
if (invoice.link) {
window.open(invoice.link, '_blank', 'noopener noreferrer');
popupWindow(invoice.link);
}
}, [invoice.link]);

View File

@ -1,6 +1,7 @@
import { Button } from '@affine/component';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useMutation } from '@affine/core/hooks/use-mutation';
import { popupWindow } from '@affine/core/utils';
import { createCheckoutSessionMutation } from '@affine/graphql';
import { nanoid } from 'nanoid';
import { useCallback, useEffect, useMemo, useRef } from 'react';
@ -51,11 +52,7 @@ export const AISubscribe = ({
},
{
onSuccess: data => {
const newTab = window.open(
data.createCheckoutSession,
'_blank',
'noopener noreferrer'
);
const newTab = popupWindow(data.createCheckoutSession);
if (newTab) {
newTabRef.current = newTab;
newTab.addEventListener('close', onClose);

View File

@ -5,6 +5,7 @@ import type {
Subscription,
SubscriptionMutator,
} from '@affine/core/hooks/use-subscription';
import { popupWindow } from '@affine/core/utils';
import type { SubscriptionRecurring } from '@affine/graphql';
import {
createCheckoutSessionMutation,
@ -309,13 +310,7 @@ const Upgrade = ({
},
{
onSuccess: data => {
// FIXME: safari prevents from opening new tab by window api
// TODO(@xp): what if electron?
const newTab = window.open(
data.createCheckoutSession,
'_blank',
'noopener noreferrer'
);
const newTab = popupWindow(data.createCheckoutSession);
if (newTab) {
newTabRef.current = newTab;

View File

@ -1,4 +1,5 @@
import { Tooltip } from '@affine/component';
import { popupWindow } from '@affine/core/utils';
import { Unreachable } from '@affine/env/constant';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon, NewIcon, ResetIcon } from '@blocksuite/icons';
@ -181,13 +182,12 @@ export function AppUpdaterButton({
onDownloadUpdate();
}
} else {
window.open(
`https://github.com/toeverything/AFFiNE/releases/tag/v${updateAvailable.version}`,
'_blank'
popupWindow(
`https://github.com/toeverything/AFFiNE/releases/tag/v${updateAvailable.version}`
);
}
} else if (changelogUnread) {
window.open(runtimeConfig.changelogUrl, '_blank');
popupWindow(runtimeConfig.changelogUrl);
onOpenChangelog();
} else {
throw new Unreachable();

View File

@ -1,4 +1,5 @@
import { Tooltip } from '@affine/component/ui/tooltip';
import { popupWindow } from '@affine/core/utils';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon, NewIcon } from '@blocksuite/icons';
import { Doc, useLiveData, useServiceOptional } from '@toeverything/infra';
@ -70,7 +71,7 @@ export const HelpIsland = () => {
<StyledIconWrapper
data-testid="right-bottom-change-log-icon"
onClick={() => {
window.open(runtimeConfig.changelogUrl, '_blank');
popupWindow(runtimeConfig.changelogUrl);
}}
>
<NewIcon />

View File

@ -7,7 +7,7 @@ import { atomWithObservable, atomWithStorage } from 'jotai/utils';
import { useCallback, useState } from 'react';
import { Observable } from 'rxjs';
import { mixpanel } from '../utils';
import { mixpanel, popupWindow } from '../utils';
import { useAsyncCallback } from './affine-async-hooks';
function rpcToObservable<
@ -191,7 +191,7 @@ export const useAppUpdater = () => {
mixpanel.track('Button', {
resolve: 'OpenChangelog',
});
window.open(runtimeConfig.changelogUrl, '_blank');
popupWindow(runtimeConfig.changelogUrl);
await setChangelogUnread(true);
}, [setChangelogUnread]);

View File

@ -1,4 +1,5 @@
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
import { popupWindow } from '@affine/core/utils';
import { useLiveData, useService } from '@toeverything/infra';
import type { To } from 'history';
import { useCallback } from 'react';
@ -32,7 +33,7 @@ export const WorkbenchLink = ({
typeof to === 'string'
? to
: `${to.pathname}${to.search}${to.hash}`;
window.open(basename + href, '_blank');
popupWindow(basename + href);
}
} else {
workbench.open(to);

View File

@ -0,0 +1,43 @@
import { type LoaderFunction, Navigate, useLoaderData } from 'react-router-dom';
const trustedDomain = [
'stripe.com',
'github.com',
'twitter.com',
'discord.gg',
'youtube.com',
't.me',
'reddit.com',
];
export const loader: LoaderFunction = async ({ request }) => {
const url = new URL(request.url);
const searchParams = url.searchParams;
const redirectUri = searchParams.get('redirect_uri');
if (!redirectUri) {
return { allow: false };
}
const target = new URL(redirectUri);
if (
trustedDomain.some(domain =>
new RegExp(`.?${domain}$`).test(target.hostname)
)
) {
location.href = redirectUri;
}
return { allow: true };
};
export const Component = () => {
const { allow } = useLoaderData() as { allow: boolean };
if (allow) {
return null;
}
return <Navigate to="/404" />;
};

View File

@ -75,6 +75,10 @@ export const topLevelRoutes = [
path: '/onboarding',
lazy: () => import('./pages/onboarding'),
},
{
path: '/redirect-proxy',
lazy: () => import('./pages/redirect'),
},
{
path: '*',
lazy: () => import('./pages/404'),

View File

@ -2,5 +2,6 @@ export * from './create-emotion-cache';
export * from './fractional-indexing';
export * from './intl-formatter';
export * from './mixpanel';
export * from './popup';
export * from './string2color';
export * from './toast';

View File

@ -0,0 +1,6 @@
export function popupWindow(target: string) {
const url = new URL(runtimeConfig.serverUrlPrefix + '/redirect-proxy');
url.searchParams.set('redirect_uri', target);
return window.open(url, '_blank', `noreferrer noopener`);
}