Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into refa…
Browse files Browse the repository at this point in the history
…ctor/client-todos
  • Loading branch information
tassoevan committed Jan 22, 2024
2 parents 22549a3 + 6587e0d commit e0b77d5
Show file tree
Hide file tree
Showing 14 changed files with 185 additions and 165 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import type { LoginService } from '@rocket.chat/ui-contexts';
import { AuthenticationContext, useSetting } from '@rocket.chat/ui-contexts';
import { Meteor } from 'meteor/meteor';
import type { ContextType, ReactElement, ReactNode } from 'react';
import React, { useMemo } from 'react';

import { createReactiveSubscriptionFactory } from '../../lib/createReactiveSubscriptionFactory';
import { useLDAPAndCrowdCollisionWarning } from './hooks/useLDAPAndCrowdCollisionWarning';

const capitalize = (str: string): string => str.charAt(0).toUpperCase() + str.slice(1);

const config: Record<string, Partial<LoginService>> = {
'apple': { title: 'Apple', icon: 'apple' },
'facebook': { title: 'Facebook', icon: 'facebook' },
'twitter': { title: 'Twitter', icon: 'twitter' },
'google': { title: 'Google', icon: 'google' },
'github': { title: 'Github', icon: 'github' },
'github_enterprise': { title: 'Github Enterprise', icon: 'github' },
'gitlab': { title: 'Gitlab', icon: 'gitlab' },
'dolphin': { title: 'Dolphin', icon: 'dophin' },
'drupal': { title: 'Drupal', icon: 'drupal' },
'nextcloud': { title: 'Nextcloud', icon: 'nextcloud' },
'tokenpass': { title: 'Tokenpass', icon: 'tokenpass' },
'meteor-developer': { title: 'Meteor', icon: 'meteor' },
'wordpress': { title: 'WordPress', icon: 'wordpress' },
'linkedin': { title: 'Linkedin', icon: 'linkedin' },
};

export type LoginMethods = keyof typeof Meteor extends infer T ? (T extends `loginWith${string}` ? T : never) : never;

type AuthenticationProviderProps = {
children: ReactNode;
};

const AuthenticationProvider = ({ children }: AuthenticationProviderProps): ReactElement => {
const isLdapEnabled = useSetting<boolean>('LDAP_Enable');
const isCrowdEnabled = useSetting<boolean>('CROWD_Enable');

const loginMethod: LoginMethods = (isLdapEnabled && 'loginWithLDAP') || (isCrowdEnabled && 'loginWithCrowd') || 'loginWithPassword';

useLDAPAndCrowdCollisionWarning();

const contextValue = useMemo(
(): ContextType<typeof AuthenticationContext> => ({
loginWithToken: (token: string): Promise<void> =>
new Promise((resolve, reject) =>
Meteor.loginWithToken(token, (err) => {
if (err) {
return reject(err);
}
resolve(undefined);
}),
),
loginWithPassword: (user: string | { username: string } | { email: string } | { id: string }, password: string): Promise<void> =>
new Promise((resolve, reject) => {
Meteor[loginMethod](user, password, (error) => {
if (error) {
reject(error);
return;
}

resolve();
});
}),
loginWithService: <T extends LoginService>({ service, clientConfig = {} }: T): (() => Promise<true>) => {
const loginMethods = {
'meteor-developer': 'MeteorDeveloperAccount',
} as const;

const loginWithService = `loginWith${loginMethods[service] || capitalize(String(service || ''))}`;

const method: (config: unknown, cb: (error: any) => void) => Promise<true> = (Meteor as any)[loginWithService] as any;

if (!method) {
return () => Promise.reject(new Error('Login method not found'));
}

return () =>
new Promise((resolve, reject) => {
method(clientConfig, (error: any): void => {
if (!error) {
resolve(true);
return;
}
reject(error);
});
});
},
queryAllServices: createReactiveSubscriptionFactory(() =>
ServiceConfiguration.configurations
.find(
{
showButton: { $ne: false },
},
{
sort: {
service: 1,
},
},
)
.fetch()
.map(
({ appId: _, ...service }) =>
({
title: capitalize(String((service as any).service || '')),
...service,
...(config[(service as any).service] ?? {}),
} as any),
),
),
}),
[loginMethod],
);

return <AuthenticationContext.Provider children={children} value={contextValue} />;
};

export default AuthenticationProvider;
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useSetting } from '@rocket.chat/ui-contexts';
import { Meteor } from 'meteor/meteor';
import { useEffect } from 'react';

import type { LoginMethods } from '../UserProvider';
import type { LoginMethods } from '../AuthenticationProvider';

export function useLDAPAndCrowdCollisionWarning() {
const isLdapEnabled = useSetting<boolean>('LDAP_Enable');
Expand Down
45 changes: 24 additions & 21 deletions apps/meteor/client/providers/MeteorProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';

import { OmnichannelRoomIconProvider } from '../components/RoomIcon/OmnichannelRoomIcon/provider/OmnichannelRoomIconProvider';
import ActionManagerProvider from './ActionManagerProvider';
import AuthenticationProvider from './AuthenticationProvider/AuthenticationProvider';
import AuthorizationProvider from './AuthorizationProvider';
import AvatarUrlProvider from './AvatarUrlProvider';
import { CallProvider } from './CallProvider';
Expand Down Expand Up @@ -36,27 +37,29 @@ const MeteorProvider: FC = ({ children }) => (
<LayoutProvider>
<AvatarUrlProvider>
<UserProvider>
<CustomSoundProvider>
<DeviceProvider>
<ModalProvider>
<AuthorizationProvider>
<EmojiPickerProvider>
<OmnichannelRoomIconProvider>
<UserPresenceProvider>
<ActionManagerProvider>
<VideoConfProvider>
<CallProvider>
<OmnichannelProvider>{children}</OmnichannelProvider>
</CallProvider>
</VideoConfProvider>
</ActionManagerProvider>
</UserPresenceProvider>
</OmnichannelRoomIconProvider>
</EmojiPickerProvider>
</AuthorizationProvider>
</ModalProvider>
</DeviceProvider>
</CustomSoundProvider>
<AuthenticationProvider>
<CustomSoundProvider>
<DeviceProvider>
<ModalProvider>
<AuthorizationProvider>
<EmojiPickerProvider>
<OmnichannelRoomIconProvider>
<UserPresenceProvider>
<ActionManagerProvider>
<VideoConfProvider>
<CallProvider>
<OmnichannelProvider>{children}</OmnichannelProvider>
</CallProvider>
</VideoConfProvider>
</ActionManagerProvider>
</UserPresenceProvider>
</OmnichannelRoomIconProvider>
</EmojiPickerProvider>
</AuthorizationProvider>
</ModalProvider>
</DeviceProvider>
</CustomSoundProvider>
</AuthenticationProvider>
</UserProvider>
</AvatarUrlProvider>
</LayoutProvider>
Expand Down
100 changes: 3 additions & 97 deletions apps/meteor/client/providers/UserProvider/UserProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { IRoom, ISubscription, IUser } from '@rocket.chat/core-typings';
import { useLocalStorage } from '@rocket.chat/fuselage-hooks';
import type { LoginService, SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
import { UserContext, useEndpoint, useSetting } from '@rocket.chat/ui-contexts';
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
import { UserContext, useEndpoint } from '@rocket.chat/ui-contexts';
import { Meteor } from 'meteor/meteor';
import type { ContextType, ReactElement, ReactNode } from 'react';
import React, { useEffect, useMemo } from 'react';
Expand All @@ -15,33 +15,13 @@ import { createReactiveSubscriptionFactory } from '../../lib/createReactiveSubsc
import { useCreateFontStyleElement } from '../../views/account/accessibility/hooks/useCreateFontStyleElement';
import { useDeleteUser } from './hooks/useDeleteUser';
import { useEmailVerificationWarning } from './hooks/useEmailVerificationWarning';
import { useLDAPAndCrowdCollisionWarning } from './hooks/useLDAPAndCrowdCollisionWarning';
import { useUpdateAvatar } from './hooks/useUpdateAvatar';
import { useUpdateCustomUserStatus } from './hooks/useUpdateCustomUserStatus';

const getUserId = (): string | null => Meteor.userId();

const getUser = (): IUser | null => Meteor.user() as IUser | null;

const capitalize = (str: string): string => str.charAt(0).toUpperCase() + str.slice(1);

const config: Record<string, Partial<LoginService>> = {
'apple': { title: 'Apple', icon: 'apple' },
'facebook': { title: 'Facebook', icon: 'facebook' },
'twitter': { title: 'Twitter', icon: 'twitter' },
'google': { title: 'Google', icon: 'google' },
'github': { title: 'Github', icon: 'github' },
'github_enterprise': { title: 'Github Enterprise', icon: 'github' },
'gitlab': { title: 'Gitlab', icon: 'gitlab' },
'dolphin': { title: 'Dolphin', icon: 'dophin' },
'drupal': { title: 'Drupal', icon: 'drupal' },
'nextcloud': { title: 'Nextcloud', icon: 'nextcloud' },
'tokenpass': { title: 'Tokenpass', icon: 'tokenpass' },
'meteor-developer': { title: 'Meteor', icon: 'meteor' },
'wordpress': { title: 'WordPress', icon: 'wordpress' },
'linkedin': { title: 'Linkedin', icon: 'linkedin' },
};

const logout = (): Promise<void> =>
new Promise((resolve, reject) => {
const user = getUser();
Expand All @@ -56,16 +36,11 @@ const logout = (): Promise<void> =>
});
});

export type LoginMethods = keyof typeof Meteor extends infer T ? (T extends `loginWith${string}` ? T : never) : never;

type UserProviderProps = {
children: ReactNode;
};

const UserProvider = ({ children }: UserProviderProps): ReactElement => {
const isLdapEnabled = useSetting<boolean>('LDAP_Enable');
const isCrowdEnabled = useSetting<boolean>('CROWD_Enable');

const userId = useReactiveValue(getUserId);
const user = useReactiveValue(getUser);
const [userLanguage, setUserLanguage] = useLocalStorage('userLanguage', '');
Expand All @@ -76,9 +51,6 @@ const UserProvider = ({ children }: UserProviderProps): ReactElement => {
const createFontStyleElement = useCreateFontStyleElement();
createFontStyleElement(user?.settings?.preferences?.fontSize);

const loginMethod: LoginMethods = (isLdapEnabled && 'loginWithLDAP') || (isCrowdEnabled && 'loginWithCrowd') || 'loginWithPassword';

useLDAPAndCrowdCollisionWarning();
useEmailVerificationWarning(user ?? undefined);

useUpdateCustomUserStatus();
Expand All @@ -103,75 +75,9 @@ const UserProvider = ({ children }: UserProviderProps): ReactElement => {

return ChatRoom.find(query, options).fetch();
}),
loginWithToken: (token: string): Promise<void> =>
new Promise((resolve, reject) =>
Meteor.loginWithToken(token, (err) => {
if (err) {
return reject(err);
}
resolve(undefined);
}),
),
loginWithPassword: (user: string | { username: string } | { email: string } | { id: string }, password: string): Promise<void> =>
new Promise((resolve, reject) => {
Meteor[loginMethod](user, password, (error) => {
if (error) {
reject(error);
return;
}

resolve();
});
}),
logout,
loginWithService: <T extends LoginService>({ service, clientConfig = {} }: T): (() => Promise<true>) => {
const loginMethods = {
'meteor-developer': 'MeteorDeveloperAccount',
} as const;

const loginWithService = `loginWith${loginMethods[service] || capitalize(String(service || ''))}`;

const method: (config: unknown, cb: (error: any) => void) => Promise<true> = (Meteor as any)[loginWithService] as any;

if (!method) {
return () => Promise.reject(new Error('Login method not found'));
}

return () =>
new Promise((resolve, reject) => {
method(clientConfig, (error: any): void => {
if (!error) {
resolve(true);
return;
}
reject(error);
});
});
},
queryAllServices: createReactiveSubscriptionFactory(() =>
ServiceConfiguration.configurations
.find(
{
showButton: { $ne: false },
},
{
sort: {
service: 1,
},
},
)
.fetch()
.map(
({ appId: _, ...service }) =>
({
title: capitalize(String((service as any).service || '')),
...service,
...(config[(service as any).service] ?? {}),
} as any),
),
),
}),
[userId, user, loginMethod],
[userId, user],
);

useEffect(() => {
Expand Down
6 changes: 1 addition & 5 deletions apps/meteor/client/sidebar/Sidebar.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ISetting } from '@rocket.chat/core-typings';
import type { LoginService, SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
import { UserContext, SettingsContext } from '@rocket.chat/ui-contexts';
import type { Meta, Story } from '@storybook/react';
import type { ObjectId } from 'mongodb';
Expand Down Expand Up @@ -98,10 +98,6 @@ const userContextValue: ContextType<typeof UserContext> = {
querySubscription: () => [() => () => undefined, () => undefined],
queryRoom: () => [() => () => undefined, () => undefined],

queryAllServices: () => [() => (): void => undefined, (): LoginService[] => []],
loginWithService: () => () => Promise.reject('loginWithService not implemented'),
loginWithPassword: async () => Promise.reject('loginWithPassword not implemented'),
loginWithToken: async () => Promise.reject('loginWithToken not implemented'),
logout: () => Promise.resolve(),
};

Expand Down
4 changes: 0 additions & 4 deletions packages/mock-providers/src/MockedAppRootBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,7 @@ export class MockedAppRootBuilder {
};

private user: ContextType<typeof UserContext> = {
loginWithPassword: () => Promise.reject(new Error('not implemented')),
logout: () => Promise.reject(new Error('not implemented')),
loginWithService: () => () => Promise.reject(new Error('not implemented')),
loginWithToken: () => Promise.reject(new Error('not implemented')),
queryAllServices: () => [() => () => undefined, () => []],
queryPreference: () => [() => () => undefined, () => undefined],
queryRoom: () => [() => () => undefined, () => undefined],
querySubscription: () => [() => () => undefined, () => undefined],
Expand Down
5 changes: 0 additions & 5 deletions packages/mock-providers/src/MockedUserContext.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { LoginService } from '@rocket.chat/ui-contexts';
import { UserContext } from '@rocket.chat/ui-contexts';
import React from 'react';
import type { ContextType } from 'react';
Expand All @@ -23,10 +22,6 @@ const userContextValue: ContextType<typeof UserContext> = {
querySubscription: () => [() => () => undefined, () => undefined],
queryRoom: () => [() => () => undefined, () => undefined],

queryAllServices: () => [() => (): void => undefined, (): LoginService[] => []],
loginWithService: () => () => Promise.reject('loginWithService not implemented'),
loginWithPassword: async () => Promise.reject('loginWithPassword not implemented'),
loginWithToken: async () => Promise.reject('loginWithToken not implemented'),
logout: () => Promise.resolve(),
};

Expand Down
Loading

0 comments on commit e0b77d5

Please sign in to comment.