Skip to content
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

feat: Add ability to create guest links with password (SQSERVICES-1975) #15014

Merged
merged 65 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
2753f4d
feat: Add ability to create guest links with password (SQSERVICES-1975)
thisisamir98 Apr 14, 2023
07e394c
feat: Add GuestLinkPasswordModal
thisisamir98 Apr 19, 2023
3479d34
join with login
thisisamir98 Apr 21, 2023
3aa2a77
sso password
thisisamir98 Apr 21, 2023
768b9d1
joing with deeplink
thisisamir98 Apr 24, 2023
02138ec
clean up copies
thisisamir98 Apr 24, 2023
8314b82
Merge branch 'dev' of github.com:wireapp/wire-webapp into feat/SQSERV…
thisisamir98 Apr 24, 2023
d16bc8f
fix error
thisisamir98 Apr 24, 2023
3a63325
Merge branch 'dev' of github.com:wireapp/wire-webapp into feat/SQSERV…
thisisamir98 Apr 24, 2023
73c5ec0
Update src/script/auth/component/GuestLinkPasswordModal.tsx
thisisamir98 Apr 25, 2023
c1a393a
backward compatibility
thisisamir98 Apr 25, 2023
8019074
Merge branch 'feat/SQSERVICES-1975' of github.com:wireapp/wire-webapp…
thisisamir98 Apr 25, 2023
fc0df1a
Update src/script/auth/component/GuestLinkPasswordModal.tsx
thisisamir98 Apr 26, 2023
4cab38d
use on if
thisisamir98 Apr 26, 2023
498955a
Merge branch 'feat/SQSERVICES-1975' of github.com:wireapp/wire-webapp…
thisisamir98 Apr 26, 2023
9f0be47
fix backenderror
thisisamir98 Apr 26, 2023
a28c71c
fix type error
thisisamir98 Apr 26, 2023
d865548
Merge branch 'dev' of github.com:wireapp/wire-webapp into feat/SQSERV…
thisisamir98 Apr 27, 2023
d61bd21
BackendErrorlabel
thisisamir98 Apr 27, 2023
e6999db
fix type error
thisisamir98 Apr 27, 2023
eee35b8
fix state bug
thisisamir98 Apr 28, 2023
911156b
add loading state
thisisamir98 May 2, 2023
6fdc56b
bump core
thisisamir98 May 3, 2023
1f33a06
use api-client supportsGuestLinksWithPassword
thisisamir98 May 3, 2023
92112cb
feat: conversation name
thisisamir98 May 8, 2023
96b2824
Merge branch 'dev' of github.com:wireapp/wire-webapp into feat/SQSERV…
thisisamir98 May 8, 2023
656afbc
add URL_LEARN_MORE_ABOUT_GUEST_LINKS
thisisamir98 May 8, 2023
ef65588
throw error
thisisamir98 May 8, 2023
155c129
Merge branch 'dev' of github.com:wireapp/wire-webapp into feat/SQSERV…
thisisamir98 Jun 19, 2023
5ec9259
runfix: password not secure option
thisisamir98 Jun 19, 2023
d0ab5ea
copy your password reminder message
thisisamir98 Jun 21, 2023
2625b8f
The text inputs wrong labels
thisisamir98 Jun 21, 2023
0927ef5
disabled state
thisisamir98 Jun 21, 2023
80adff0
design review for joining on desktop
thisisamir98 Jun 23, 2023
cabae78
remove log
thisisamir98 Jun 27, 2023
ac71cc1
Merge branch 'dev' of github.com:wireapp/wire-webapp into feat/SQSERV…
thisisamir98 Jun 27, 2023
d2f4adc
disabled copy to clipboard
thisisamir98 Jun 27, 2023
2d7f09f
border color on focus
thisisamir98 Jun 27, 2023
55a9aed
rename
thisisamir98 Jun 27, 2023
c399830
fix redirect bug
thisisamir98 Jun 27, 2023
7646ede
invalid password error handling
thisisamir98 Jun 30, 2023
a8c6d0d
login and single sign on password error handling
thisisamir98 Jun 30, 2023
1e853c9
fix broken tests
thisisamir98 Jun 30, 2023
39475fb
fix ts error
thisisamir98 Jun 30, 2023
f3b2127
add password conditions check
thisisamir98 Jul 24, 2023
ac430c1
add modal close button
thisisamir98 Jul 24, 2023
351d3ec
add close button to login
thisisamir98 Jul 24, 2023
f25adf8
close modal on sso with link pass
thisisamir98 Jul 24, 2023
a2835f5
fix primary button disabled
thisisamir98 Jul 25, 2023
2f6c9d7
password copy
thisisamir98 Jul 25, 2023
d500b7f
chore: merge with dev
tlebon Oct 23, 2023
224a75b
feat: update guest link passwords for new convo join page
tlebon Oct 24, 2023
e8967d0
fix: issue with some edge cases not working
tlebon Oct 26, 2023
c02ea4b
chore: merge dev
tlebon Nov 9, 2023
0c84609
chore: remove error throw
tlebon Nov 9, 2023
5e70571
fix: remove form param submission bug
tlebon Nov 9, 2023
72a0561
chore: remove password from error on L132
tlebon Nov 13, 2023
c643e5f
chore: missing error catch
tlebon Nov 13, 2023
ccfc429
feat: possible fix for open issues
tlebon Nov 13, 2023
c94981f
fix: password verification
tlebon Nov 14, 2023
45c45db
chore: condense password regex
tlebon Nov 20, 2023
772e337
feat: update primary modal
tlebon Nov 20, 2023
6f16f41
chore: disable guest link passwords feature
tlebon Nov 27, 2023
6164354
chore: merge dev
tlebon Dec 19, 2023
59d4ab0
chore: merge dev
tlebon Dec 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions server/config/client.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@
SHOW_LOADING_INFORMATION: env.FEATURE_SHOW_LOADING_INFORMATION == 'true',
USE_CORE_CRYPTO: env.FEATURE_USE_CORE_CRYPTO == 'true',
MAX_USERS_TO_PING_WITHOUT_ALERT:
(env.FEATURE_MAX_USERS_TO_PING_WITHOUT_ALERT && Number(env.FEATURE_MAX_USERS_TO_PING_WITHOUT_ALERT)) || 3,

Check warning on line 74 in server/config/client.config.ts

View workflow job for this annotation

GitHub Actions / lint

No magic number: 3
},
MAX_GROUP_PARTICIPANTS: (env.MAX_GROUP_PARTICIPANTS && Number(env.MAX_GROUP_PARTICIPANTS)) || 500,

Check warning on line 76 in server/config/client.config.ts

View workflow job for this annotation

GitHub Actions / lint

No magic number: 500
MAX_VIDEO_PARTICIPANTS: (env.MAX_VIDEO_PARTICIPANTS && Number(env.MAX_VIDEO_PARTICIPANTS)) || 4,

Check warning on line 77 in server/config/client.config.ts

View workflow job for this annotation

GitHub Actions / lint

No magic number: 4
NEW_PASSWORD_MINIMUM_LENGTH: (env.NEW_PASSWORD_MINIMUM_LENGTH && Number(env.NEW_PASSWORD_MINIMUM_LENGTH)) || 8,

Check warning on line 78 in server/config/client.config.ts

View workflow job for this annotation

GitHub Actions / lint

No magic number: 8
URL: {
ACCOUNT_BASE: env.URL_ACCOUNT_BASE,
MOBILE_BASE: env.URL_MOBILE_BASE,
Expand All @@ -95,6 +95,7 @@
MICROPHONE_ACCESS_DENIED: env.URL_SUPPORT_MICROPHONE_ACCESS_DENIED,
PRIVACY_VERIFY_FINGERPRINT: env.URL_SUPPORT_PRIVACY_VERIFY_FINGERPRINT,
SCREEN_ACCESS_DENIED: env.URL_SUPPORT_SCREEN_ACCESS_DENIED,
LEARN_MORE_ABOUT_GUEST_LINKS: env.URL_LEARN_MORE_ABOUT_GUEST_LINKS,
NON_FEDERATING_INFO: env.URL_SUPPORT_NON_FEDERATING_INFO,
OAUTH_LEARN_MORE: env.URL_SUPPORT_OAUTH_LEARN_MORE,
OFFLINE_BACKEND: env.URL_SUPPORT_OFFLINE_BACKEND,
Expand Down
2 changes: 2 additions & 0 deletions server/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@ export type Env = {

URL_SUPPORT_SCREEN_ACCESS_DENIED: string;

URL_LEARN_MORE_ABOUT_GUEST_LINKS: string;

URL_SUPPORT_NON_FEDERATING_INFO: string;

URL_SUPPORT_OAUTH_LEARN_MORE: string;
Expand Down
32 changes: 32 additions & 0 deletions src/i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"BackendError.LABEL.CONVERSATION_CODE_NOT_FOUND": "This link is no longer valid. Ask the person who invited you how to join.",
"BackendError.LABEL.CONVERSATION_NOT_FOUND": "CONVERSATION_NOT_FOUND",
"BackendError.LABEL.CONVERSATION_TOO_MANY_MEMBERS": "This conversation has reached the limit of participants",
"BackendErrorLabel.INVALID_CONVERSATION_PASSWORD": "The password is incorrect, please try again.",
"BackendError.LABEL.EMAIL_EXISTS": "This email address is already in use. {supportEmailExistsLink}",
"BackendError.LABEL.EMAIL_REQUIRED": "You can’t use your username as two-factor authentication is activated. Please log in with your email instead.",
"BackendError.LABEL.HANDLE_EXISTS": "This username is already taken",
Expand Down Expand Up @@ -634,8 +635,25 @@
"guestOptionsCreateLink": "Create link",
"guestOptionsInfoHeader": "Invite others with a link",
"guestOptionsInfoText": "Invite others with a link to this conversation. Anyone with the link can join the conversation, even if they don’t have {{brandName}}.",
"guestOptionsInfoPasswordSecured": "Link is password secured",
"guestOptionsInfoTextWithPassword": "Users are asked to enter the password before they can join the conversation with a guest link.",
"guestOptionsInfoTextForgetPassword": "Forgot password? Revoke the link and create a new one.",
"guestOptionsInfoModalTitle": "Create password secured link",
"guestOptionsInfoModalTitleSubTitle": "People who want to join the conversation via the guest link need to enter this password first.",
"guestOptionsInfoModalCancel": "Cancel",
"guestOptionsInfoModalFormLabel": "Guest link password",
"guestOptionsInfoModalAction": "Create Link",
"guestOptionsInfoModalTitleBoldSubTitle": "You can't change the password later. Make sure to copy and store it.",
"guestOptionsInfoTextSecureWithPassword": "You can also secure the link with a password.",
"guestOptionsPasswordRadioLabel": "Guest link password",
"guestOptionsPasswordRadioOptionSecured": "Password secured",
"guestOptionsPasswordRadioOptionNotSecured": "Not password secured",
"guestOptionsPasswordCopyToClipboard": "Copy Password",
"guestOptionsPasswordCopyToClipboardSuccess": "Password Copied!",
"guestOptionsPasswordForceToCopy": "You need to copy the password so that you can store and share it with people you want to invite.",
"guestOptionsRevokeLink": "Revoke link",
"guestOptionsTitle": "Guests",
"generatePassword": "Generate password",
"guestRoomConversationBadge": "[bold]Guests[/bold] are present",
"guestRoomConversationBadgeExternal": "[bold]Externals[/bold] are present",
"guestRoomConversationBadgeExternalAndGuest": "[bold]Externals[/bold] and [bold]guests[/bold] are present",
Expand All @@ -658,6 +676,15 @@
"guestRoomToggleInfoDisabled": "You can't disable the guest option in this conversation, as it has been created by someone from another team.",
"guestRoomToggleInfoExtended": "Open this conversation to people outside your team. You can always change it later.",
"guestRoomToggleInfoHead": "Guest Links",
"guestLinkPasswordModal.headline": "{conversationName} \n Enter password",
"guestLinkPasswordModal.headlineDefault": "Group Conversation \n Enter password",
"guestLinkPasswordModal.description": "Please enter the password you have received with the access link for this conversation.",
"guestLinkPasswordModal.conversationPasswordProtected": "This conversation is password protected.",
"guestLinkPasswordModal.passwordInputLabel": "Conversation password",
"guestLinkPasswordModal.passwordInputPlaceholder": "Enter Conversation password",
"guestLinkPasswordModal.learnMoreLink": "Learn more about guest links",
"guestLinkPasswordModal.joinConversation": "Join Conversation",
"guestLinkPasswordModal.passwordIncorrect": "Password is incorrect, please try again.",
"guestRoomToggleName": "Allow Guests",
"historyInfo.learnMore": "Learn more",
"historyInfo.noHistoryHeadline": "It’s the first time you’re using {brandName} on this device.",
Expand Down Expand Up @@ -876,6 +903,11 @@
"modalConversationGuestOptionsGetCodeMessage": "Could not get access link.",
"modalConversationGuestOptionsRequestCodeMessage": "Could not request access link. Please try again.",
"modalConversationGuestOptionsRevokeCodeMessage": "Could not revoke access link. Please try again.",
"modalGuestLinkJoinPlaceholder": "Enter password",
"modalGuestLinkJoinConfirmPlaceholder": "Confirm your password",
"modalGuestLinkJoinLabel": "Set password",
"modalGuestLinkJoinConfirmLabel": "Confirm password",
"modalGuestLinkJoinHelperText": "Use at least {{minPasswordLength}} characters, with one lowercase letter, one capital letter, a number, and a special character.",
"modalConversationJoinConfirm": "Join",
"modalConversationJoinFullHeadline": "You could not join the conversation",
"modalConversationJoinFullMessage": "The conversation is full.",
Expand Down
68 changes: 68 additions & 0 deletions src/script/auth/component/JoinGuestLinkPasswordModal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Wire
* Copyright (C) 2023 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

import {fireEvent, render} from '@testing-library/react';

import {JoinGuestLinkPasswordModal, JoinGuestLinkPasswordModalProps} from './JoinGuestLinkPasswordModal';

import {withIntl, withTheme} from '../util/test/TestUtil';

describe('JoinGuestLinkPasswordModal', () => {
const onSubmitPasswordMock = jest.fn();
const props: JoinGuestLinkPasswordModalProps = {
onSubmitPassword: onSubmitPasswordMock,
onClose: jest.fn(),
conversationName: 'test group',
error: null,
};

beforeEach(() => {
onSubmitPasswordMock.mockClear();
});

it('should call onSubmitPassword with the password value when the form is submitted', () => {
const {getByTestId} = render(withTheme(withIntl(<JoinGuestLinkPasswordModal {...props} />)));
const input = getByTestId('guest-link-join-password-input') as HTMLInputElement;
const joinConversationButton = getByTestId('guest-link-join-submit-button') as HTMLButtonElement;
fireEvent.change(input, {target: {value: 'password'}});
joinConversationButton.click();
expect(onSubmitPasswordMock).toHaveBeenCalledWith('password');
});

it('should disable the join conversation button when the password input is empty', () => {
const {getByTestId} = render(withTheme(withIntl(<JoinGuestLinkPasswordModal {...props} />)));
const joinConversationButton = getByTestId('guest-link-join-submit-button') as HTMLButtonElement;
expect(joinConversationButton.disabled).toBe(true);
});

it('should enable the join conversation button when the password input is not empty', () => {
const {getByTestId} = render(withTheme(withIntl(<JoinGuestLinkPasswordModal {...props} />)));
const input = getByTestId('guest-link-join-password-input');
const joinConversationButton = getByTestId('guest-link-join-submit-button') as HTMLButtonElement;
fireEvent.change(input, {target: {value: 'password'}});
expect(joinConversationButton.disabled).toBe(false);
});

it('should not call onSubmitPassword with an empty string when the form is submitted with an empty password input', () => {
const {getByTestId} = render(withTheme(withIntl(<JoinGuestLinkPasswordModal {...props} />)));
const joinConversationButton = getByTestId('guest-link-join-submit-button') as HTMLButtonElement;
joinConversationButton.click();
expect(onSubmitPasswordMock).toHaveBeenCalledTimes(0);
});
});
112 changes: 112 additions & 0 deletions src/script/auth/component/JoinGuestLinkPasswordModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Wire
* Copyright (C) 2023 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

import React, {useState} from 'react';

import {StatusCodes as HTTP_STATUS} from 'http-status-codes';
import {useIntl} from 'react-intl';

import {Button, COLOR, Container, ErrorMessage, Form, H2, Input, Link, Modal, Text} from '@wireapp/react-ui-kit';

import {Config} from '../../Config';
import {joinGuestLinkPasswordModalStrings} from '../../strings';

export interface JoinGuestLinkPasswordModalProps {
onSubmitPassword: (password: string) => void;
isLoading?: boolean;
conversationName?: string;
error: (Error & {label?: string; code?: number; message?: string}) | null;
onClose: () => void;
}

const JoinGuestLinkPasswordModal: React.FC<JoinGuestLinkPasswordModalProps> = ({
error,
onClose,
isLoading,
conversationName,
onSubmitPassword,
}) => {
const {formatMessage: _} = useIntl();
const [passwordValue, setPasswordValue] = useState<string>('');

const onSubmit = (event: React.FormEvent<HTMLFormElement | HTMLButtonElement>) => {
event.preventDefault();
onSubmitPassword(passwordValue);
};

const Error = () => {
if (error?.code === HTTP_STATUS.FORBIDDEN || error?.code === HTTP_STATUS.BAD_REQUEST) {
return <ErrorMessage>{_(joinGuestLinkPasswordModalStrings.passwordIncorrect)}</ErrorMessage>;
}
return null;
};

return (
<Modal onClose={onClose}>
<Container style={{maxWidth: '400px'}}>
<H2 style={{whiteSpace: 'break-spaces', fontWeight: 500, marginTop: '10px', textAlign: 'center'}}>
{conversationName
? _(joinGuestLinkPasswordModalStrings.headline, {conversationName})
: _(joinGuestLinkPasswordModalStrings.headlineDefault)}
</H2>
<Text block fontSize="var(--font-size-base)" style={{marginBottom: 24}}>
{_(joinGuestLinkPasswordModalStrings.description)}
</Text>
<Form
name="guest-password-join-form"
data-uie-name="guest-password-join-form"
onSubmit={(event: React.FormEvent<HTMLFormElement>) => onSubmit(event)}
autoComplete="off"
>
<Input
error={<Error />}
data-uie-name="guest-link-join-password-input"
name="guest-join-password"
required
placeholder={_(joinGuestLinkPasswordModalStrings.passwordInputLabel)}
label={_(joinGuestLinkPasswordModalStrings.passwordInputLabel)}
id="guest_link_join_password"
className="modal__input"
type="password"
autoComplete="off"
value={passwordValue}
onChange={event => setPasswordValue(event.currentTarget.value)}
/>
</Form>
<Link href={Config.getConfig().URL.SUPPORT.LEARN_MORE_ABOUT_GUEST_LINKS} target="_blank">
<Text block color={COLOR.BLUE} style={{textDecoration: 'underline', marginBottom: 24}}>
{_(joinGuestLinkPasswordModalStrings.learnMoreLink)}
</Text>
</Link>
<Button
showLoading={isLoading}
block
type="button"
disabled={!passwordValue}
onClick={(event: React.FormEvent<HTMLButtonElement>) => onSubmit(event)}
data-uie-name="guest-link-join-submit-button"
>
{_(joinGuestLinkPasswordModalStrings.joinConversation)}
</Button>
</Container>
</Modal>
);
};

export {JoinGuestLinkPasswordModal};
3 changes: 2 additions & 1 deletion src/script/auth/module/action/AuthAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export class AuthAction {
code: string,
uri?: string,
getEntropy?: () => Promise<Uint8Array>,
password?: string,
): ThunkAction => {
const onBeforeLogin: LoginLifecycleFunction = async (dispatch, getState, {actions: {authAction}}) =>
dispatch(authAction.doSilentLogout());
Expand All @@ -74,7 +75,7 @@ export class AuthAction {
getState,
{actions: {localStorageAction, conversationAction}},
) => {
const conversation = await dispatch(conversationAction.doJoinConversationByCode(key, code, uri));
const conversation = await dispatch(conversationAction.doJoinConversationByCode(key, code, uri, password));
const domain = conversation?.qualified_conversation?.domain;
return (
conversation &&
Expand Down
44 changes: 42 additions & 2 deletions src/script/auth/module/action/ConversationAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
*
*/

import type {ConversationJoinData} from '@wireapp/api-client/lib/conversation/data/ConversationJoinData';
import type {ConversationEvent} from '@wireapp/api-client/lib/event/';
import {StatusCodes as HTTP_STATUS} from 'http-status-codes';

import {isBackendError} from 'Util/TypePredicateUtil';

import {ConversationActionCreator} from './creator/';

Expand All @@ -37,18 +41,54 @@ export class ConversationAction {
};
};

doJoinConversationByCode = (key: string, code: string, uri?: string): ThunkAction<Promise<ConversationEvent>> => {
doJoinConversationByCode = (
key: string,
code: string,
uri?: string,
password?: string,
): ThunkAction<Promise<ConversationEvent>> => {
return async (dispatch, getState, {apiClient}) => {
dispatch(ConversationActionCreator.startJoinConversationByCode());
try {
const conversationEvent = await apiClient.api.conversation.postJoinByCode({code, key, uri});
const conversationEvent = await apiClient.api.conversation.postJoinByCode({code, key, uri, password});
dispatch(ConversationActionCreator.successfulJoinConversationByCode(conversationEvent));
return conversationEvent;
} catch (error) {
/*
Backend does return a password-invalid error even though we have not submitted any password
expected: passsword-required.
received: password-invalid.
we have to dispatch conversation info with has_password field in order to handle error message in JoinGuestLinkPasswordModal properly.
*/
if (isBackendError(error) && !password && error.code === HTTP_STATUS.FORBIDDEN) {
dispatch(
ConversationActionCreator.successfulConversationCodeGetInfo({
has_password: true,
} as ConversationJoinData),
);
throw error;
}
dispatch(ConversationActionCreator.failedJoinConversationByCode(error));
throw error;
}
};
};

doGetConversationInfoByCode = (key: string, code: string): ThunkAction<Promise<ConversationJoinData | undefined>> => {
return async (dispatch, getState, {apiClient}) => {
dispatch(ConversationActionCreator.startConversationCodeGetInfo());
try {
const conversationInfo = await apiClient.api.conversation.getJoinByCode({code, key});
dispatch(ConversationActionCreator.successfulConversationCodeGetInfo(conversationInfo));
return conversationInfo;
} catch (error) {
if (isBackendError(error)) {
dispatch(ConversationActionCreator.failedConversationCodeGetInfo(error));
return undefined;
}
throw error;
}
};
};
}
export const conversationAction = new ConversationAction();
Loading
Loading