0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

refactor(ui): add ui social connector filters (#822)

* refactor(ui): add ui social connector filters

add ui social connector filters

* refactor(ui): add some comments

add some comments
This commit is contained in:
simeng-li 2022-05-13 19:04:32 +08:00 committed by GitHub
parent ab6c124620
commit 907a63b52a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 186 additions and 65 deletions

View file

@ -6,7 +6,7 @@ import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
import { socialConnectors, mockSignInExperienceSettings } from '@/__mocks__/logto';
import * as socialSignInApi from '@/apis/social';
import { generateState, storeState } from '@/hooks/use-social';
import { generateState, storeState } from '@/hooks/utils';
import SecondarySocialSignIn, { defaultSize } from './SecondarySocialSignIn';

View file

@ -3,67 +3,20 @@ import { useTranslation } from 'react-i18next';
import { useParams, useNavigate } from 'react-router-dom';
import { invokeSocialSignIn, signInWithSocial } from '@/apis/social';
import { generateRandomString, parseQueryParameters } from '@/utils';
import { parseQueryParameters } from '@/utils';
import useApi, { ErrorHandlers } from './use-api';
import { PageContext } from './use-page-context';
import useTerms from './use-terms';
/**
* Social Connector State Utility Methods
* @param state
* @param state.uuid - unique id
* @param state.platform - platform
* @param state.callbackLink - callback uri scheme
*/
type State = {
uuid: string;
platform: 'web' | 'ios' | 'android';
callbackLink?: string;
};
const storageKeyPrefix = 'social_auth_state';
const getLogtoNativeSdk = () => {
if (typeof logtoNativeSdk !== 'undefined') {
return logtoNativeSdk;
}
};
export const generateState = () => {
const uuid = generateRandomString();
const platform = getLogtoNativeSdk()?.platform ?? 'web';
const callbackLink = getLogtoNativeSdk()?.callbackLink;
const state: State = { uuid, platform, callbackLink };
return btoa(JSON.stringify(state));
};
export const decodeState = (state: string) => {
try {
return JSON.parse(atob(state)) as State;
} catch {}
};
export const stateValidation = (state: string, connectorId: string) => {
const stateStorage = sessionStorage.getItem(`${storageKeyPrefix}:${connectorId}`);
return stateStorage === state;
};
export const storeState = (state: string, connectorId: string) => {
sessionStorage.setItem(`${storageKeyPrefix}:${connectorId}`, state);
};
/* ============================================================================ */
const isNativeWebview = () => {
const platform = getLogtoNativeSdk()?.platform ?? '';
return ['ios', 'android'].includes(platform);
};
import {
getLogtoNativeSdk,
isNativeWebview,
generateState,
decodeState,
stateValidation,
storeState,
filterSocialConnectors,
} from './utils';
const useSocial = () => {
const { setToast, experienceSettings } = useContext(PageContext);
@ -87,13 +40,9 @@ const useSocial = () => {
[navigate, parameters.connector]
);
// Filter native supported social connectors
const socialConnectors = useMemo(
() =>
(experienceSettings?.socialConnectors ?? []).filter(({ id }) => {
return !isNativeWebview() || getLogtoNativeSdk()?.supportedSocialConnectorIds.includes(id);
}),
[experienceSettings?.socialConnectors]
() => filterSocialConnectors(experienceSettings?.socialConnectors),
[experienceSettings]
);
const { run: asyncInvokeSocialSignIn } = useApi(invokeSocialSignIn);

View file

@ -0,0 +1,45 @@
import { ConnectorData } from '@/types';
import { filterSocialConnectors } from './utils';
const mockConnectors = [
{ platform: 'Web', target: 'facebook' },
{ platform: 'Web', target: 'google' },
{ platform: 'Universal', target: 'facebook' },
{ platform: 'Universal', target: 'WeChat' },
{ platform: 'Native', target: 'WeChat' },
{ platform: 'Native', target: 'Alipay' },
] as ConnectorData[];
describe('filterSocialConnectors', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('undefined input should return empty list', () => {
expect(filterSocialConnectors()).toEqual([]);
});
it('filter web connectors', () => {
expect(filterSocialConnectors(mockConnectors)).toEqual([
{ platform: 'Web', target: 'facebook' },
{ platform: 'Web', target: 'google' },
{ platform: 'Universal', target: 'WeChat' },
]);
});
it('filter Native connectors', () => {
/* eslint-disable @silverhand/fp/no-mutation */
// @ts-expect-error mock global object
globalThis.logtoNativeSdk = {
platform: 'ios',
supportedSocialConnectorIds: ['Web', 'WeChat'],
};
/* eslint-enable @silverhand/fp/no-mutation */
expect(filterSocialConnectors(mockConnectors)).toEqual([
{ platform: 'Universal', target: 'facebook' },
{ platform: 'Native', target: 'WeChat' },
]);
});
});

View file

@ -0,0 +1,120 @@
import { ConnectorData } from '@/types';
import { generateRandomString } from '@/utils';
/**
* Native SDK Utility Methods
*/
export const getLogtoNativeSdk = () => {
if (typeof logtoNativeSdk !== 'undefined') {
return logtoNativeSdk;
}
};
export const isNativeWebview = () => {
const platform = getLogtoNativeSdk()?.platform ?? '';
return ['ios', 'android'].includes(platform);
};
/**
* Social Connector State Utility Methods
* @param state
* @param state.uuid - unique id
* @param state.platform - platform
* @param state.callbackLink - callback uri scheme
*/
type State = {
uuid: string;
platform: 'web' | 'ios' | 'android';
callbackLink?: string;
};
const storageKeyPrefix = 'social_auth_state';
export const generateState = () => {
const uuid = generateRandomString();
const platform = getLogtoNativeSdk()?.platform ?? 'web';
const callbackLink = getLogtoNativeSdk()?.callbackLink;
const state: State = { uuid, platform, callbackLink };
return btoa(JSON.stringify(state));
};
export const decodeState = (state: string) => {
try {
return JSON.parse(atob(state)) as State;
} catch {}
};
export const stateValidation = (state: string, connectorId: string) => {
const stateStorage = sessionStorage.getItem(`${storageKeyPrefix}:${connectorId}`);
return stateStorage === state;
};
export const storeState = (state: string, connectorId: string) => {
sessionStorage.setItem(`${storageKeyPrefix}:${connectorId}`, state);
};
/**
* Social Connectors Filter Utility Methods
*/
export const filterSocialConnectors = (socialConnectors?: ConnectorData[]) => {
if (!socialConnectors) {
return [];
}
const connectorMap = new Map<string, ConnectorData>();
if (!isNativeWebview()) {
for (const connector of socialConnectors) {
const { platform, target } = connector;
if (platform === 'Native') {
continue;
}
/**
* Accepts both web and universal platform connectors.
* Insert universal connectors only if there is no web platform connector provided with the same target.
* Web platform has higher priority.
**/
if (platform === 'Web' || !connectorMap.get(target)) {
connectorMap.set(target, connector);
continue;
}
}
return Array.from(connectorMap.values());
}
for (const connector of socialConnectors) {
const { platform, target } = connector;
if (platform === 'Web') {
continue;
}
/**
* Accepts both Native and universal platform connectors.
* Insert universal connectors only if there is no Native platform connector provided with the same target.
* Native platform has higher priority.
**/
if (
platform === 'Native' &&
getLogtoNativeSdk()?.supportedSocialConnectorIds.includes(target)
) {
connectorMap.set(target, connector);
continue;
}
if (platform === 'Universal' && !connectorMap.get(target)) {
connectorMap.set(target, connector);
continue;
}
}
return Array.from(connectorMap.values());
};

View file

@ -1,4 +1,10 @@
import { Branding, LanguageInfo, TermsOfUse, ConnectorMetadata } from '@logto/schemas';
import {
Branding,
LanguageInfo,
TermsOfUse,
ConnectorMetadata,
ConnectorPlatform,
} from '@logto/schemas';
export type UserFlow = 'sign-in' | 'register';
export type SignInMethod = 'username' | 'email' | 'sms' | 'social';
@ -10,6 +16,7 @@ export enum SearchParameters {
export interface ConnectorData extends ConnectorMetadata {
id: string;
platform: ConnectorPlatform;
}
export type SignInExperienceSettings = {