From 8eac4726b8995229a96d97f8e5648bf8f5608ead Mon Sep 17 00:00:00 2001 From: Embbnux Ji Date: Tue, 2 Mar 2021 16:17:19 +0800 Subject: [PATCH] sync features and bugfixs from b33ddb14 (#1660) --- .sync | 2 +- packages/babel-settings/babel.config.js | 1 + packages/babel-settings/package.json | 1 + packages/core/README.md | 19 +- packages/core/lib/RcModule/RcModule.ts | 15 +- packages/core/lib/usm-redux/core/module.ts | 25 +- packages/core/lib/usm/core/module.ts | 5 - .../assets/icons/icon-pvc-connecting.svg | 2 +- .../assets/icons/icon-pvc-disabled.svg | 2 +- .../assets/icons/icon-pvc-disconnecting.svg | 2 +- .../assets/icons/icon-pvc-off.svg | 12 +- .../assets/icons/icon-pvc-on.svg | 2 +- .../ActivityCallLogPanel.ut.tsx | 9 +- .../ActivityCallLogPanel/IvrInfo/IvrInfo.tsx | 2 +- .../ActivityCallLogPanel/i18n/en-US.ts | 2 + .../utils/getButtonText.tsx | 4 + .../EvIntegratedSoftphoneAlert.tsx | 1 + .../EvIntegratedSoftphoneAlert/i18n/en-US.ts | 2 + .../BasicSessionPanel.spec.tsx | 9 +- .../BasicSessionPanel/BasicSessionPanel.tsx | 3 + .../components/BasicSessionPanel/styles.scss | 4 + .../CallHistoryCallLogPanel.tsx | 123 + .../CallHistoryCallLogPanel/index.ts | 1 + .../ChooseAccountPanel/ChooseAccountPanel.tsx | 46 +- .../components/ChooseAccountPanel/styles.scss | 19 +- .../components/DialerPanel/DialerPanel.tsx | 10 +- .../WorkingStateButton/WorkingStateButton.tsx | 1 - .../WorkingStateButton/styles.scss | 8 - .../components/MainViewPanel/styles.scss | 2 + .../ManualDialSettingsPanel.tsx | 1 + .../components/PickList/PickList.tsx | 9 +- .../RequeueCallGroupDetailPanel.spec.tsx | 6 +- .../RequeueCallGroupPanel.spec.tsx | 25 +- .../SessionConfigPanel/SessionConfigPanel.tsx | 46 +- .../components/SessionConfigPanel/styles.scss | 6 +- .../TransferCallPanel/TransferCallPanel.tsx | 1 + .../InternalPanel/InternalPanel.ut.tsx | 52 +- .../PhoneBookPanel/PhoneBookPanel.ut.tsx | 25 +- .../CallHistoryCallLogPage.tsx | 13 + .../CallHistoryCallLogPage/index.ts | 1 + packages/engage-voice-widget/enums/index.ts | 2 +- .../enums/{authStatus.ts => loginStatus.ts} | 4 +- .../enums/tabManagerEvents.ts | 3 +- .../interfaces/EvActivityCallUI.interface.ts | 12 +- .../interfaces/EvData.interface.ts | 5 +- .../lib/EvClient/EvClient.ts | 4 + .../lib/EvClient/__SDK__/agentLibrary.js | 2306 ++++++++++------- .../interfaces/EvSdkResponse.interface.ts | 1 + .../lib/contactMatchIdentify.ts | 2 +- .../EvActivityCallUI.interface.ts | 4 +- .../EvActivityCallUI/EvActivityCallUI.ts | 34 +- .../EvAgentSession.interface.ts | 4 +- .../modules/EvAgentSession/EvAgentSession.ts | 52 +- .../EvAgentSessionUI.interface.ts | 4 +- .../EvAgentSessionUI/EvAgentSessionUI.ts | 5 +- .../modules/EvAuth/EvAuth.interface.ts | 10 +- .../modules/EvAuth/EvAuth.ts | 178 +- .../modules/EvCall/EvCall.interface.ts | 6 +- .../modules/EvCall/EvCall.ts | 21 +- .../EvCallDataSource.interface.ts | 4 +- .../EvCallHistory/EvCallHistory.interface.ts | 2 + .../modules/EvCallHistory/EvCallHistory.ts | 27 +- .../modules/EvCallMonitor/EvCallMonitor.ts | 2 + .../EvChooseAccountUI/EvChooseAccountUI.ts | 2 +- .../EvDialerUI/EvDialerUI.interface.ts | 5 +- .../EvIntegratedSoftphone.interface.ts | 4 +- .../EvIntegratedSoftphone.ts | 54 +- .../EvIntegratedSoftphone/i18n/en-US.ts | 1 + .../EvPresence/EvPresence.interface.ts | 4 +- .../EvRequeueCall/EvRequeueCall.interface.ts | 4 +- .../EvSettings/EvSettings.interface.ts | 5 +- .../modules/EvStorage/EvStorage.interface.ts | 12 + .../modules/EvStorage/EvStorage.ts | 116 + .../modules/EvStorage/index.ts | 2 + .../EvTransferCall.interface.ts | 4 +- .../EvWorkingState.interface.ts | 6 +- packages/engage-voice-widget/package.json | 4 +- packages/i18n/index.js | 58 +- .../enums/callLoggerTriggerTypes.ts | 2 + .../{constants => enums}/usageTypes.ts | 0 .../helpers/meetingHelper.ts | 14 +- .../interfaces/CallLog.interface.ts | 26 + .../ClientResponse.ts} | 9 - .../interfaces/MessageStore.model.ts | 33 + .../interfaces/Rcv.model.ts | 4 +- .../interfaces/Webphone.interface.ts | 2 + .../lib/Analytics/index.js | 1 + .../lib/Analytics/pendo.js | 25 + .../lib/LoggerBaseV2/LoggerBase.interface.ts | 10 + .../lib/LoggerBaseV2/LoggerBase.ts | 108 + .../lib/LoggerBaseV2/index.ts | 3 + .../lib/LoggerBaseV2/loggerBaseHelper.ts | 17 + .../lib/contactHelper.ts | 8 +- .../lib/dataTransport.ts | 45 + .../getIntlDateTimeFormatter.ts | 23 +- .../messageHelper/messageHelper.interface.ts | 12 - .../lib/messageHelper/messageHelper.ts | 46 +- .../lib/proxy/proxify.ts | 4 +- .../lib/proxy/proxyState.ts | 23 - .../modules/AccountInfoV2/AccountInfo.ts | 11 - .../ActiveCallControlV2/ActiveCallControl.ts | 24 +- .../modules/ActiveCallsV2/ActiveCalls.ts | 6 +- .../AddressBookV2/AddressBook.interface.ts | 7 + .../modules/AddressBookV2/AddressBook.ts | 59 +- .../modules/AddressBookV2/helpers.ts | 4 +- .../modules/Analytics/Analytics.ts | 124 +- .../modules/Analytics/trackEvents.ts | 2 + .../AnalyticsV2/Analytics.interface.ts | 72 + .../modules/AnalyticsV2/Analytics.ts | 224 ++ .../modules/AnalyticsV2/analyticsRouters.ts | 64 + .../modules/AnalyticsV2/index.ts | 4 + .../modules/AnalyticsV2/trackEvents.ts | 83 + .../modules/AudioSettingsV2/AudioSettings.ts | 8 +- .../modules/Auth/index.ts | 67 +- .../modules/AuthV2/Auth.ts | 26 +- .../modules/AvailabilityMonitor/index.d.ts | 2 +- .../modules/BlockedNumberV2/BlockedNumber.ts | 6 +- .../modules/CallHistory/index.js | 4 + .../modules/CallHistoryV2/CallHistory.ts | 1 + .../CallLoggerV2/CallLogger.interface.ts | 69 + .../modules/CallLoggerV2/CallLogger.ts | 389 +++ .../modules/CallLoggerV2/callLoggerHelper.ts | 6 + .../modules/CallLoggerV2/index.ts | 3 + .../modules/CallV2/Call.ts | 9 + .../ConferenceCall.interfaces.ts | 205 ++ .../ConferenceCallV2/ConferenceCall.ts | 1053 ++++++++ .../ConferenceCallV2/conferenceCallErrors.js | 20 + .../modules/ConferenceCallV2/index.ts | 2 + .../modules/ConferenceCallV2/lib/constants.ts | 25 + .../modules/ConferenceCallV2/lib/helpers.ts | 29 + .../modules/ConferenceCallV2/lib/index.ts | 2 + .../modules/ConversationLogger/index.js | 6 +- .../ConversationLogger.interface.ts | 59 + .../ConversationLogger.ts | 479 ++++ .../conversationLoggerHelper.ts | 17 + .../modules/ConversationLoggerV2/index.ts | 3 + .../Conversations.interface.ts | 10 +- .../modules/ConversationsV2/Conversations.ts | 58 +- .../DateTimeFormat.interface.ts | 2 +- .../DateTimeFormatV2/DateTimeFormat.ts | 2 +- .../modules/ErrorLogger/ErrorLogger.ts | 2 +- .../ExtensionDeviceV2/ExtensionDevice.ts | 6 +- .../ExtensionPhoneNumber.ts | 2 +- .../ForwardingNumberV2/ForwardingNumber.ts | 25 +- .../GenericMeetingV2/GenericMeeting.ts | 4 + .../modules/GlipCompanyV2/GlipCompany.ts | 5 +- .../modules/LocaleV2/Locale.ts | 34 +- .../modules/MeetingV2/Meeting.ts | 29 +- .../modules/MeetingV2/i18n/de-DE.js | 5 + .../modules/MeetingV2/i18n/en-GB.js | 5 + .../modules/MeetingV2/i18n/es-419.js | 5 + .../modules/MeetingV2/i18n/es-ES.js | 5 + .../modules/MeetingV2/i18n/fr-CA.js | 5 + .../modules/MeetingV2/i18n/fr-FR.js | 5 + .../modules/MeetingV2/i18n/it-IT.js | 5 + .../modules/MeetingV2/i18n/ja-JP.js | 5 + .../modules/MeetingV2/i18n/ko-KR.js | 5 + .../modules/MeetingV2/i18n/nl-NL.js | 5 + .../modules/MeetingV2/i18n/pt-BR.js | 5 + .../modules/MeetingV2/i18n/pt-PT.js | 5 + .../modules/MeetingV2/i18n/zh-CN.js | 5 + .../modules/MeetingV2/i18n/zh-TW.js | 5 + .../MessageStoreV2/MessageStore.interface.ts | 66 + .../modules/MessageStoreV2/MessageStore.ts | 911 +++++++ .../modules/MessageStoreV2/index.ts | 3 + .../MessageStoreV2/messageStoreErrors.ts | 6 + .../MessageStoreV2/messageStoreHelper.ts | 44 + .../modules/RcVideoV2/RcVideo.ts | 47 +- .../modules/RcVideoV2/i18n/de-DE.js | 7 + .../modules/RcVideoV2/i18n/en-GB.js | 7 + .../modules/RcVideoV2/i18n/es-419.js | 7 + .../modules/RcVideoV2/i18n/es-ES.js | 7 + .../modules/RcVideoV2/i18n/fr-CA.js | 7 + .../modules/RcVideoV2/i18n/fr-FR.js | 7 + .../modules/RcVideoV2/i18n/it-IT.js | 7 + .../modules/RcVideoV2/i18n/ja-JP.js | 7 + .../modules/RcVideoV2/i18n/ko-KR.js | 7 + .../modules/RcVideoV2/i18n/nl-NL.js | 7 + .../modules/RcVideoV2/i18n/pt-BR.js | 7 + .../modules/RcVideoV2/i18n/pt-PT.js | 7 + .../modules/RcVideoV2/i18n/zh-CN.js | 7 + .../modules/RcVideoV2/i18n/zh-TW.js | 7 + .../modules/RecentMessages/index.js | 24 +- .../RecentMessages.interface.ts | 46 + .../RecentMessagesV2/RecentMessages.ts | 282 ++ .../modules/RecentMessagesV2/index.ts | 4 + .../modules/RecentMessagesV2/messageStatus.ts | 4 + .../RecentMessagesV2/recentMessagesHelper.ts | 49 + .../RingCentralExtensions.ts | 2 +- .../modules/StorageV2/Storage.ts | 42 +- .../WebSocketSubscription.ts | 5 +- .../modules/Webphone/index.js | 100 +- .../modules/Webphone/webphoneHelper.js | 1 + .../modules/WebphoneV2/Webphone.ts | 145 +- .../modules/WebphoneV2/webphoneHelper.ts | 1 + packages/ringcentral-integration/package.json | 4 +- .../templates/Project/package-template.json | 2 +- .../dev-server/Phone.js | 11 +- .../dev-server/containers/App/index.js | 5 +- .../ringcentral-widgets-docs/package.json | 2 +- .../ringcentral-widgets-test/package.json | 2 +- .../MeetingCalendarHelper.unit.spec.ts | 17 + .../components/ActionMenuList/index.js | 2 +- .../components/ActiveCallItem/styles.scss | 2 + .../ActiveCallItemV2/ActiveCallItem.tsx | 3 +- .../components/ActiveCallList/index.js | 5 +- .../components/ActiveCallPad/i18n/en-US.js | 2 + .../components/ActiveCallPad/index.js | 181 +- .../components/ActiveCallPad/styles.scss | 2 + .../components/ActiveCallPanel/index.js | 23 +- .../components/ActiveCallsPanel/index.js | 4 + .../components/BackHeaderV2/styles.scss | 4 +- .../BasicCallInfo/BasicCallInfo.tsx | 2 +- .../components/CallCtrlPanel/index.js | 12 + .../CallHistoryActions/ActionButton.tsx | 22 + .../CallHistoryActions/CallHistoryActions.tsx | 52 + .../CallHistoryActions/MenuButton.tsx | 83 + .../CallHistoryActions/index.ts | 1 + .../CallHistoryActions/styles.scss | 13 + .../CallHistoryItem/CallHistoryItem.tsx | 51 +- .../CallHistoryItem/styles.scss | 19 +- .../CallHistoryPanel.interface.ts | 10 +- .../CallHistoryPanel/CallHistoryPanel.tsx | 36 +- .../CallHistoryPanel/CallIcon/CallIcon.tsx | 19 +- .../components/CallHistoryPanel/i18n/de-DE.js | 9 + .../components/CallHistoryPanel/i18n/en-AU.js | 9 + .../components/CallHistoryPanel/i18n/en-GB.js | 9 + .../CallHistoryPanel/i18n/es-419.js | 9 + .../components/CallHistoryPanel/i18n/es-ES.js | 9 + .../components/CallHistoryPanel/i18n/fr-CA.js | 9 + .../components/CallHistoryPanel/i18n/fr-FR.js | 9 + .../components/CallHistoryPanel/i18n/it-IT.js | 9 + .../components/CallHistoryPanel/i18n/ja-JP.js | 9 + .../components/CallHistoryPanel/i18n/ko-KR.js | 9 + .../components/CallHistoryPanel/i18n/nl-NL.js | 9 + .../components/CallHistoryPanel/i18n/pt-BR.js | 9 + .../components/CallHistoryPanel/i18n/pt-PT.js | 9 + .../components/CallHistoryPanel/i18n/zh-CN.js | 9 + .../components/CallHistoryPanel/i18n/zh-HK.js | 9 + .../components/CallHistoryPanel/i18n/zh-TW.js | 9 + .../components/CallItem/styles.scss | 2 + .../CallLogFields/FieldItem/FieldItem.tsx | 10 +- .../SelectListTextField.tsx | 1 + .../LogFieldsInput/LogFieldsInput.tsx | 6 +- .../FieldItem/SelectField/SelectField.tsx | 8 +- .../CallLogPanel/CallLog.interface.ts | 2 + .../CallLogPanel/CallLogPanel.interface.tsx | 3 +- .../components/CallLogPanel/CallLogPanel.tsx | 4 +- .../components/CallLogPanel/i18n/en-US.js | 4 +- .../components/ComposeTextPanel/index.js | 2 +- .../ContactDropdownList/i18n/de-DE.js | 5 + .../ContactDropdownList/i18n/en-AU.js | 5 + .../ContactDropdownList/i18n/en-GB.js | 5 + .../ContactDropdownList/i18n/en-US.js | 3 + .../ContactDropdownList/i18n/es-419.js | 5 + .../ContactDropdownList/i18n/es-ES.js | 5 + .../ContactDropdownList/i18n/fr-CA.js | 5 + .../ContactDropdownList/i18n/fr-FR.js | 5 + .../ContactDropdownList/i18n/index.js | 4 + .../ContactDropdownList/i18n/it-IT.js | 5 + .../ContactDropdownList/i18n/ja-JP.js | 5 + .../ContactDropdownList/i18n/ko-KR.js | 5 + .../ContactDropdownList/i18n/loadLocale.js | 1 + .../ContactDropdownList/i18n/nl-NL.js | 5 + .../ContactDropdownList/i18n/pt-BR.js | 5 + .../ContactDropdownList/i18n/pt-PT.js | 5 + .../ContactDropdownList/i18n/zh-CN.js | 5 + .../ContactDropdownList/i18n/zh-HK.js | 5 + .../ContactDropdownList/i18n/zh-TW.js | 5 + .../components/ContactDropdownList/index.js | 23 +- .../ContactDropdownList/styles.scss | 18 +- .../components/ConversationPanel/index.js | 41 +- .../components/ConversationPanel/styles.scss | 4 + .../components/DialerPanel/index.js | 6 +- .../components/EntityModal/i18n/en-US.js | 1 + .../GenericMeetingPanel.tsx | 6 +- .../GenericMeetingPanel/interface.ts | 1 + .../components/IncomingCallPad/index.js | 2 +- .../components/InnerTopic/i18n/de-DE.js | 5 + .../components/InnerTopic/i18n/en-AU.js | 5 + .../components/InnerTopic/i18n/en-GB.js | 5 + .../components/InnerTopic/i18n/en-US.js | 3 + .../components/InnerTopic/i18n/es-419.js | 5 + .../components/InnerTopic/i18n/es-ES.js | 5 + .../components/InnerTopic/i18n/fr-CA.js | 5 + .../components/InnerTopic/i18n/fr-FR.js | 5 + .../components/InnerTopic/i18n/index.js | 4 + .../components/InnerTopic/i18n/it-IT.js | 5 + .../components/InnerTopic/i18n/ja-JP.js | 5 + .../components/InnerTopic/i18n/ko-KR.js | 5 + .../components/InnerTopic/i18n/loadLocale.js | 1 + .../components/InnerTopic/i18n/nl-NL.js | 5 + .../components/InnerTopic/i18n/pt-BR.js | 5 + .../components/InnerTopic/i18n/pt-PT.js | 5 + .../components/InnerTopic/i18n/zh-CN.js | 5 + .../components/InnerTopic/i18n/zh-HK.js | 5 + .../components/InnerTopic/i18n/zh-TW.js | 5 + .../components/InnerTopic/index.tsx | 66 + .../components/InnerTopic/styles.scss | 5 + .../components/InputSelect/InputSelect.tsx | 1 + .../LogBasicInfoV2/ShinyBar/ShinyBar.tsx | 1 + .../components/LogNotificationV2/styles.scss | 8 + .../MeetingConfigs/MeetingIdSection.tsx | 2 +- .../components/MeetingConfigsV2/i18n/de-DE.ts | 4 +- .../components/MeetingConfigsV2/i18n/en-AU.ts | 2 + .../components/MeetingConfigsV2/i18n/en-GB.ts | 2 + .../components/MeetingConfigsV2/i18n/en-US.ts | 1 + .../MeetingConfigsV2/i18n/es-419.ts | 2 + .../components/MeetingConfigsV2/i18n/es-ES.ts | 2 + .../components/MeetingConfigsV2/i18n/fr-CA.ts | 2 + .../components/MeetingConfigsV2/i18n/fr-FR.ts | 2 + .../components/MeetingConfigsV2/i18n/it-IT.ts | 4 +- .../components/MeetingConfigsV2/i18n/ja-JP.ts | 2 + .../components/MeetingConfigsV2/i18n/ko-KR.js | 2 + .../components/MeetingConfigsV2/i18n/nl-NL.js | 2 + .../components/MeetingConfigsV2/i18n/pt-BR.ts | 4 +- .../components/MeetingConfigsV2/i18n/pt-PT.js | 2 + .../components/MeetingConfigsV2/i18n/zh-CN.ts | 2 + .../components/MeetingConfigsV2/i18n/zh-HK.ts | 2 + .../components/MeetingConfigsV2/i18n/zh-TW.ts | 2 + .../components/MeetingConfigsV2/index.tsx | 143 +- .../components/MeetingConfigsV2/styles.scss | 18 +- .../components/NotificationPanel/styles.scss | 2 +- .../RecipientsInput/RecipientsInput.tsx | 1 + .../components/SearchPanel/SearchPanel.tsx | 24 +- .../SelectListBasic/SelectListBasic.tsx | 19 +- .../SettingsPanel/SettingsPanel.interface.ts | 5 + .../SettingsPanel/SettingsPanel.tsx | 11 + .../components/SettingsPanel/i18n/de-DE.js | 4 +- .../components/SettingsPanel/i18n/en-AU.js | 4 +- .../components/SettingsPanel/i18n/en-GB.js | 4 +- .../components/SettingsPanel/i18n/en-US.js | 5 +- .../components/SettingsPanel/i18n/es-419.js | 4 +- .../components/SettingsPanel/i18n/es-ES.js | 4 +- .../components/SettingsPanel/i18n/fr-CA.js | 4 +- .../components/SettingsPanel/i18n/fr-FR.js | 4 +- .../components/SettingsPanel/i18n/it-IT.js | 4 +- .../components/SettingsPanel/i18n/ja-JP.js | 4 +- .../components/SettingsPanel/i18n/ko-KR.js | 4 +- .../components/SettingsPanel/i18n/nl-NL.js | 4 +- .../components/SettingsPanel/i18n/pt-BR.js | 4 +- .../components/SettingsPanel/i18n/pt-PT.js | 4 +- .../components/SettingsPanel/i18n/zh-CN.js | 4 +- .../components/SettingsPanel/i18n/zh-HK.js | 4 +- .../components/SettingsPanel/i18n/zh-TW.js | 4 +- .../SpinnerOverlay/SpinnerOverlay.tsx | 8 +- .../components/SpinnerOverlay/styles.scss | 6 +- .../components/TabNavigationButton/index.tsx | 3 + .../components/TransferPanel/i18n/en-US.js | 1 + .../components/TransferPanel/index.js | 63 +- .../components/TransferPanel/styles.scss | 5 + .../components/VideoPanel/VideoConfig.tsx | 98 +- .../components/VideoPanel/i18n/de-DE.js | 4 +- .../components/VideoPanel/i18n/en-AU.js | 2 + .../components/VideoPanel/i18n/en-GB.js | 2 + .../components/VideoPanel/i18n/en-US.js | 1 + .../components/VideoPanel/i18n/es-419.js | 2 + .../components/VideoPanel/i18n/es-ES.js | 2 + .../components/VideoPanel/i18n/fr-CA.js | 2 + .../components/VideoPanel/i18n/fr-FR.js | 2 + .../components/VideoPanel/i18n/it-IT.js | 4 +- .../components/VideoPanel/i18n/ja-JP.js | 2 + .../components/VideoPanel/i18n/ko-KR.js | 6 +- .../components/VideoPanel/i18n/nl-NL.js | 2 + .../components/VideoPanel/i18n/pt-BR.js | 4 +- .../components/VideoPanel/i18n/pt-PT.js | 2 + .../components/VideoPanel/i18n/zh-CN.js | 2 + .../components/VideoPanel/i18n/zh-HK.js | 2 + .../components/VideoPanel/i18n/zh-TW.js | 2 + .../components/VideoPanel/styles.scss | 16 +- .../WebRTCNotificationSection.tsx | 4 +- .../components/WeekdaysSelect/i18n/es-ES.js | 14 +- .../CallCtrlPage/CallCtrlContainer.js | 8 + .../enums/callCtrlLayouts.ts | 2 +- packages/ringcentral-widgets/gulpfile.js | 2 +- .../lib/MeetingCalendarHelper/config.ts | 12 +- .../MeetingCalendarHelper/formatMeetingId.ts | 7 +- .../lib/MeetingCalendarHelper/i18n/pt-BR.js | 4 +- .../MeetingCalendarHelper/index.interface.ts | 2 +- .../lib/MeetingCalendarHelper/index.ts | 55 +- .../lib/countryNames/i18n/fr-FR.js | 2 +- .../ringcentral-widgets/lib/isElectron.ts | 3 + .../lib/popWindow/{index.js => index.ts} | 15 +- .../Beforeunload/Beforeunload.interface.ts | 2 +- .../modules/Beforeunload/Beforeunload.ts | 4 +- .../modules/CallCtrlUI/index.ts | 36 +- .../modules/CallLogUI/CallLogUI.tsx | 8 +- .../modules/CallLogUI/i18n/en-US.ts | 3 + .../modules/CallLogUI/i18n/index.ts | 5 + .../modules/CallLogUI/i18n/loadLocale.ts | 1 + .../modules/DialerUI/index.js | 5 + .../modules/GenericMeetingUI/index.ts | 1 + .../modules/ModalUIV2/ModalUI.interface.ts | 3 +- .../modules/ModalUIV2/ModalUI.tsx | 13 + .../modules/ModalUIV2/i18n/de-DE.ts | 6 +- .../modules/ModalUIV2/i18n/en-AU.ts | 6 +- .../modules/ModalUIV2/i18n/en-GB.ts | 6 +- .../modules/ModalUIV2/i18n/es-419.ts | 6 +- .../modules/ModalUIV2/i18n/es-ES.ts | 6 +- .../modules/ModalUIV2/i18n/fr-CA.ts | 6 +- .../modules/ModalUIV2/i18n/fr-FR.ts | 6 +- .../modules/ModalUIV2/i18n/it-IT.ts | 6 +- .../modules/ModalUIV2/i18n/ja-JP.ts | 6 +- .../modules/ModalUIV2/i18n/ko-KR.ts | 6 +- .../modules/ModalUIV2/i18n/nl-NL.ts | 6 +- .../modules/ModalUIV2/i18n/pt-BR.ts | 6 +- .../modules/ModalUIV2/i18n/pt-PT.ts | 6 +- .../modules/ModalUIV2/i18n/zh-CN.ts | 6 +- .../modules/ModalUIV2/i18n/zh-HK.ts | 6 +- .../modules/OAuth/index.js | 15 +- .../modules/OAuth/interface.ts | 7 + .../modules/TransferUI/index.ts | 15 +- packages/ringcentral-widgets/package.json | 5 +- .../CallHistoryPanelList.ut.tsx | 20 + packages/test-utils/README.md | 14 + packages/test-utils/lib/render.tsx | 35 + packages/test-utils/package.json | 3 + yarn.lock | 347 +-- 418 files changed, 9442 insertions(+), 2242 deletions(-) create mode 100644 packages/engage-voice-widget/components/CallHistoryCallLogPanel/CallHistoryCallLogPanel.tsx create mode 100644 packages/engage-voice-widget/components/CallHistoryCallLogPanel/index.ts create mode 100644 packages/engage-voice-widget/containers/CallHistoryCallLogPage/CallHistoryCallLogPage.tsx create mode 100644 packages/engage-voice-widget/containers/CallHistoryCallLogPage/index.ts rename packages/engage-voice-widget/enums/{authStatus.ts => loginStatus.ts} (71%) create mode 100644 packages/engage-voice-widget/modules/EvStorage/EvStorage.interface.ts create mode 100644 packages/engage-voice-widget/modules/EvStorage/EvStorage.ts create mode 100644 packages/engage-voice-widget/modules/EvStorage/index.ts rename packages/ringcentral-integration/{constants => enums}/usageTypes.ts (100%) create mode 100644 packages/ringcentral-integration/interfaces/CallLog.interface.ts rename packages/ringcentral-integration/{shared/clientResponse.ts => interfaces/ClientResponse.ts} (67%) create mode 100644 packages/ringcentral-integration/interfaces/MessageStore.model.ts create mode 100644 packages/ringcentral-integration/lib/Analytics/pendo.js create mode 100644 packages/ringcentral-integration/lib/LoggerBaseV2/LoggerBase.interface.ts create mode 100644 packages/ringcentral-integration/lib/LoggerBaseV2/LoggerBase.ts create mode 100644 packages/ringcentral-integration/lib/LoggerBaseV2/index.ts create mode 100644 packages/ringcentral-integration/lib/LoggerBaseV2/loggerBaseHelper.ts create mode 100644 packages/ringcentral-integration/lib/dataTransport.ts delete mode 100644 packages/ringcentral-integration/lib/proxy/proxyState.ts create mode 100644 packages/ringcentral-integration/modules/AnalyticsV2/Analytics.interface.ts create mode 100644 packages/ringcentral-integration/modules/AnalyticsV2/Analytics.ts create mode 100644 packages/ringcentral-integration/modules/AnalyticsV2/analyticsRouters.ts create mode 100644 packages/ringcentral-integration/modules/AnalyticsV2/index.ts create mode 100644 packages/ringcentral-integration/modules/AnalyticsV2/trackEvents.ts create mode 100644 packages/ringcentral-integration/modules/CallLoggerV2/CallLogger.interface.ts create mode 100644 packages/ringcentral-integration/modules/CallLoggerV2/CallLogger.ts create mode 100644 packages/ringcentral-integration/modules/CallLoggerV2/callLoggerHelper.ts create mode 100644 packages/ringcentral-integration/modules/CallLoggerV2/index.ts create mode 100644 packages/ringcentral-integration/modules/ConferenceCallV2/ConferenceCall.interfaces.ts create mode 100644 packages/ringcentral-integration/modules/ConferenceCallV2/ConferenceCall.ts create mode 100644 packages/ringcentral-integration/modules/ConferenceCallV2/conferenceCallErrors.js create mode 100644 packages/ringcentral-integration/modules/ConferenceCallV2/index.ts create mode 100644 packages/ringcentral-integration/modules/ConferenceCallV2/lib/constants.ts create mode 100644 packages/ringcentral-integration/modules/ConferenceCallV2/lib/helpers.ts create mode 100644 packages/ringcentral-integration/modules/ConferenceCallV2/lib/index.ts create mode 100644 packages/ringcentral-integration/modules/ConversationLoggerV2/ConversationLogger.interface.ts create mode 100644 packages/ringcentral-integration/modules/ConversationLoggerV2/ConversationLogger.ts create mode 100644 packages/ringcentral-integration/modules/ConversationLoggerV2/conversationLoggerHelper.ts create mode 100644 packages/ringcentral-integration/modules/ConversationLoggerV2/index.ts create mode 100644 packages/ringcentral-integration/modules/MeetingV2/i18n/de-DE.js create mode 100644 packages/ringcentral-integration/modules/MeetingV2/i18n/en-GB.js create mode 100644 packages/ringcentral-integration/modules/MeetingV2/i18n/es-419.js create mode 100644 packages/ringcentral-integration/modules/MeetingV2/i18n/es-ES.js create mode 100644 packages/ringcentral-integration/modules/MeetingV2/i18n/fr-CA.js create mode 100644 packages/ringcentral-integration/modules/MeetingV2/i18n/fr-FR.js create mode 100644 packages/ringcentral-integration/modules/MeetingV2/i18n/it-IT.js create mode 100644 packages/ringcentral-integration/modules/MeetingV2/i18n/ja-JP.js create mode 100644 packages/ringcentral-integration/modules/MeetingV2/i18n/ko-KR.js create mode 100644 packages/ringcentral-integration/modules/MeetingV2/i18n/nl-NL.js create mode 100644 packages/ringcentral-integration/modules/MeetingV2/i18n/pt-BR.js create mode 100644 packages/ringcentral-integration/modules/MeetingV2/i18n/pt-PT.js create mode 100644 packages/ringcentral-integration/modules/MeetingV2/i18n/zh-CN.js create mode 100644 packages/ringcentral-integration/modules/MeetingV2/i18n/zh-TW.js create mode 100644 packages/ringcentral-integration/modules/MessageStoreV2/MessageStore.interface.ts create mode 100644 packages/ringcentral-integration/modules/MessageStoreV2/MessageStore.ts create mode 100644 packages/ringcentral-integration/modules/MessageStoreV2/index.ts create mode 100644 packages/ringcentral-integration/modules/MessageStoreV2/messageStoreErrors.ts create mode 100644 packages/ringcentral-integration/modules/MessageStoreV2/messageStoreHelper.ts create mode 100644 packages/ringcentral-integration/modules/RcVideoV2/i18n/de-DE.js create mode 100644 packages/ringcentral-integration/modules/RcVideoV2/i18n/en-GB.js create mode 100644 packages/ringcentral-integration/modules/RcVideoV2/i18n/es-419.js create mode 100644 packages/ringcentral-integration/modules/RcVideoV2/i18n/es-ES.js create mode 100644 packages/ringcentral-integration/modules/RcVideoV2/i18n/fr-CA.js create mode 100644 packages/ringcentral-integration/modules/RcVideoV2/i18n/fr-FR.js create mode 100644 packages/ringcentral-integration/modules/RcVideoV2/i18n/it-IT.js create mode 100644 packages/ringcentral-integration/modules/RcVideoV2/i18n/ja-JP.js create mode 100644 packages/ringcentral-integration/modules/RcVideoV2/i18n/ko-KR.js create mode 100644 packages/ringcentral-integration/modules/RcVideoV2/i18n/nl-NL.js create mode 100644 packages/ringcentral-integration/modules/RcVideoV2/i18n/pt-BR.js create mode 100644 packages/ringcentral-integration/modules/RcVideoV2/i18n/pt-PT.js create mode 100644 packages/ringcentral-integration/modules/RcVideoV2/i18n/zh-CN.js create mode 100644 packages/ringcentral-integration/modules/RcVideoV2/i18n/zh-TW.js create mode 100644 packages/ringcentral-integration/modules/RecentMessagesV2/RecentMessages.interface.ts create mode 100644 packages/ringcentral-integration/modules/RecentMessagesV2/RecentMessages.ts create mode 100644 packages/ringcentral-integration/modules/RecentMessagesV2/index.ts create mode 100644 packages/ringcentral-integration/modules/RecentMessagesV2/messageStatus.ts create mode 100644 packages/ringcentral-integration/modules/RecentMessagesV2/recentMessagesHelper.ts create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/ActionButton.tsx create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/CallHistoryActions.tsx create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/MenuButton.tsx create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/index.ts create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/styles.scss create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/i18n/de-DE.js create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/i18n/en-AU.js create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/i18n/en-GB.js create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/i18n/es-419.js create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/i18n/es-ES.js create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/i18n/fr-CA.js create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/i18n/fr-FR.js create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/i18n/it-IT.js create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/i18n/ja-JP.js create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/i18n/ko-KR.js create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/i18n/nl-NL.js create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/i18n/pt-BR.js create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/i18n/pt-PT.js create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/i18n/zh-CN.js create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/i18n/zh-HK.js create mode 100644 packages/ringcentral-widgets/components/CallHistoryPanel/i18n/zh-TW.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/de-DE.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/en-AU.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/en-GB.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/en-US.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/es-419.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/es-ES.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/fr-CA.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/fr-FR.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/index.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/it-IT.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/ja-JP.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/ko-KR.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/loadLocale.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/nl-NL.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/pt-BR.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/pt-PT.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/zh-CN.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/zh-HK.js create mode 100644 packages/ringcentral-widgets/components/ContactDropdownList/i18n/zh-TW.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/de-DE.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/en-AU.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/en-GB.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/en-US.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/es-419.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/es-ES.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/fr-CA.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/fr-FR.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/index.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/it-IT.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/ja-JP.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/ko-KR.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/loadLocale.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/nl-NL.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/pt-BR.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/pt-PT.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/zh-CN.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/zh-HK.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/i18n/zh-TW.js create mode 100644 packages/ringcentral-widgets/components/InnerTopic/index.tsx create mode 100644 packages/ringcentral-widgets/components/InnerTopic/styles.scss create mode 100644 packages/ringcentral-widgets/lib/isElectron.ts rename packages/ringcentral-widgets/lib/popWindow/{index.js => index.ts} (69%) create mode 100644 packages/ringcentral-widgets/modules/CallLogUI/i18n/en-US.ts create mode 100644 packages/ringcentral-widgets/modules/CallLogUI/i18n/index.ts create mode 100644 packages/ringcentral-widgets/modules/CallLogUI/i18n/loadLocale.ts create mode 100644 packages/ringcentral-widgets/modules/OAuth/interface.ts create mode 100644 packages/ringcentral-widgets/test/components/CallHistoryPanel/CallHistoryPanelList.ut.tsx create mode 100644 packages/test-utils/lib/render.tsx diff --git a/.sync b/.sync index cd5a3d6d08..8b13f41d4b 100644 --- a/.sync +++ b/.sync @@ -1 +1 @@ -94b20ac373f4b4b2b76a4023a3f2e369eb193d91 +b33ddb1410019452acbe86c80a5efa52d1331701 diff --git a/packages/babel-settings/babel.config.js b/packages/babel-settings/babel.config.js index a32c84e9d8..b6bc079273 100644 --- a/packages/babel-settings/babel.config.js +++ b/packages/babel-settings/babel.config.js @@ -16,6 +16,7 @@ const plugins = [ '@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-optional-chaining', '@babel/plugin-proposal-nullish-coalescing-operator', + 'const-enum', ]; module.exports = function baseBabelConfig(api) { diff --git a/packages/babel-settings/package.json b/packages/babel-settings/package.json index ffe6db848c..88bd3744d4 100644 --- a/packages/babel-settings/package.json +++ b/packages/babel-settings/package.json @@ -25,6 +25,7 @@ "@babel/preset-react": "^7.10.4", "@babel/preset-typescript": "^7.10.4", "@babel/register": "^7.10.5", + "babel-plugin-const-enum": "^1.0.1", "core-js": "^2.6.11", "typescript": "^4.0.2" }, diff --git a/packages/core/README.md b/packages/core/README.md index eef32f79f3..fbb8d5a7e2 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -17,7 +17,6 @@ yarn add @ringcentral-integration/core - [state API](#state-api) - [action API](#action-api) - [computed API](#computed-api) - - [proxyState API](#proxystate-api) - [RcUIModule APIs](#rcuimodule-apis) - [Dependency Injection](#dependency-injection) - [Storage and GlobalStorage APIs](#storage-and-globalstorage-apis) @@ -26,7 +25,7 @@ yarn add @ringcentral-integration/core ### RcModule APIs -`@ringcentral-integration/core` provides `RcModuleV2` base module, decorators `state`, `action`, `computed`, `storage` and `globalStorage`, `proxyState`. +`@ringcentral-integration/core` provides `RcModuleV2` base module, decorators `state`, `action`, `computed`, `storage` and `globalStorage`. The decorator `storage` depends on `Storage` Module, And The decorator `globalStorage` depends on `GlobalStorage` Module. @@ -122,10 +121,6 @@ class Auth extends RcModuleV2 { } ``` -#### proxyState API - -`@proxyState` is used for asynchronous state changes of the browser client, and its parameter must be an asynchronous function and cannot be used with `@storage`/`@globalStorage`. - ### RcUIModule APIs `@ringcentral-integration/core` provides `RcUIModuleV2` base module and all decorators in `RcModuleV2`. @@ -253,11 +248,13 @@ class Call extends RcModuleV2 { }); } + // Pass a tracking event type @track(trackEvents.inbound) inboundCall() { // } + // Pass a function that returns an array `[customTrackEvent, trackProps]` @track((that: Call, phoneNumber: string) => [ trackEvents.outbound, { loginType: that.callType, phoneNumber }, @@ -265,6 +262,16 @@ class Call extends RcModuleV2 { async dialout(phoneNumber: string) { // } + + // Pass a higher-order function and the sub-function has access to the `analytics` module + @track(() => (analytics) => { + analytics.setUserId(); + return [trackEvents.authentication]; + }) + @action + setLoginSuccess(token: TokenInfo) { + // + } } ``` diff --git a/packages/core/lib/RcModule/RcModule.ts b/packages/core/lib/RcModule/RcModule.ts index 0ded40f400..84c5d33b42 100644 --- a/packages/core/lib/RcModule/RcModule.ts +++ b/packages/core/lib/RcModule/RcModule.ts @@ -5,7 +5,7 @@ import { Storage as StorageV2 } from 'ringcentral-integration/modules/StorageV2' import { GlobalStorage as GlobalStorageV2 } from 'ringcentral-integration/modules/GlobalStorageV2'; import Storage from 'ringcentral-integration/modules/Storage'; import GlobalStorage from 'ringcentral-integration/modules/GlobalStorage'; -import { Analytics } from 'ringcentral-integration/modules/Analytics'; +import { Analytics } from 'ringcentral-integration/modules/AnalyticsV2'; import BaseModule, { state, action } from '../usm-redux'; import { moduleStatuses } from '../../enums/moduleStatuses'; import { Params } from '../usm/core/module'; @@ -42,7 +42,11 @@ function storage( return descriptor; } -type TrackEvent = string | ((...args: any) => [string, object?]); +type TrackEvent = + | string + | (( + ...args: any + ) => [string, object?] | ((analytics: Analytics) => [string, object?])); /** * decorate a method with `Analytics` Module @@ -75,7 +79,11 @@ function track(trackEvent: TrackEvent) { if (typeof trackEvent === 'string') { analytics.track(trackEvent); } else { - const [event, trackProps] = trackEvent(this, ...args) ?? []; + let trackReturn = trackEvent(this, ...args); + if (typeof trackReturn === 'function') { + trackReturn = trackReturn(analytics); + } + const [event, trackProps] = trackReturn ?? []; if (event) { analytics.track(event, trackProps); } @@ -123,6 +131,7 @@ class RcModuleV2< S extends Record = {} > extends BaseModule { __$$state$$__: any; + protected initializeProxy?(): Promise | void; /** * `onInit` life cycle for current initialization before all deps modules are all ready. */ diff --git a/packages/core/lib/usm-redux/core/module.ts b/packages/core/lib/usm-redux/core/module.ts index f55be2d839..b6313831a5 100644 --- a/packages/core/lib/usm-redux/core/module.ts +++ b/packages/core/lib/usm-redux/core/module.ts @@ -30,29 +30,8 @@ class Module = {}> extends BaseModule { this._actionTypes.forEach((name) => { this._reducersMaps[name] = (types) => ( _state = this._initialValue[name], - { type, states, __proxyState__ }, - ) => { - if (type.indexOf(types[name]) > -1 && __proxyState__) { - return __proxyState__[name]; - } else if (type.indexOf(types[name]) > -1 && states) { - if (this._transport && this.__proxyState__?.[name]) { - // sync up state with async proxy callback - (async () => { - await this.__proxyState__[name](this, states[name]); - this._dispatch({ - type: this.parentModule.__proxyAction__, - action: { - type: [types[name]], - __proxyState__: { [name]: states[name] }, - }, - }); - })(); - return _state; - } - return states[name]; - } - return _state; - }; + { type, states }, + ) => (type.indexOf(types[name]) > -1 && states ? states[name] : _state); }); } super._makeInstance(params); diff --git a/packages/core/lib/usm/core/module.ts b/packages/core/lib/usm/core/module.ts index 4cfe3fa12a..905f6ab503 100644 --- a/packages/core/lib/usm/core/module.ts +++ b/packages/core/lib/usm/core/module.ts @@ -18,7 +18,6 @@ export interface Params { export interface Action { type: string[] | string; states?: Properties; - __proxyState__?: Record; [K: string]: any; } @@ -39,10 +38,6 @@ interface Module { * Used by browser client to transport data. */ _transport?: any; - /** - * browser client's proxy state. - */ - __proxyState__: Record any>; /** * Used by browser client to dispatch. */ diff --git a/packages/engage-voice-widget/assets/icons/icon-pvc-connecting.svg b/packages/engage-voice-widget/assets/icons/icon-pvc-connecting.svg index 428c9c44c6..84ff68e87d 100644 --- a/packages/engage-voice-widget/assets/icons/icon-pvc-connecting.svg +++ b/packages/engage-voice-widget/assets/icons/icon-pvc-connecting.svg @@ -1,5 +1,5 @@ - + diff --git a/packages/engage-voice-widget/assets/icons/icon-pvc-disabled.svg b/packages/engage-voice-widget/assets/icons/icon-pvc-disabled.svg index bb644af335..e31df23a70 100644 --- a/packages/engage-voice-widget/assets/icons/icon-pvc-disabled.svg +++ b/packages/engage-voice-widget/assets/icons/icon-pvc-disabled.svg @@ -7,7 +7,7 @@ - + diff --git a/packages/engage-voice-widget/assets/icons/icon-pvc-disconnecting.svg b/packages/engage-voice-widget/assets/icons/icon-pvc-disconnecting.svg index 4670a09397..6b3016f226 100644 --- a/packages/engage-voice-widget/assets/icons/icon-pvc-disconnecting.svg +++ b/packages/engage-voice-widget/assets/icons/icon-pvc-disconnecting.svg @@ -1,6 +1,6 @@ - + diff --git a/packages/engage-voice-widget/assets/icons/icon-pvc-off.svg b/packages/engage-voice-widget/assets/icons/icon-pvc-off.svg index 5bf19c369c..4cd40e4cee 100755 --- a/packages/engage-voice-widget/assets/icons/icon-pvc-off.svg +++ b/packages/engage-voice-widget/assets/icons/icon-pvc-off.svg @@ -1,12 +1,12 @@ - + - - - - - + + + + + diff --git a/packages/engage-voice-widget/assets/icons/icon-pvc-on.svg b/packages/engage-voice-widget/assets/icons/icon-pvc-on.svg index 57a7f16053..1087a1d860 100755 --- a/packages/engage-voice-widget/assets/icons/icon-pvc-on.svg +++ b/packages/engage-voice-widget/assets/icons/icon-pvc-on.svg @@ -1,6 +1,6 @@ - + diff --git a/packages/engage-voice-widget/components/ActivityCallLogPanel/ActivityCallLogPanel.ut.tsx b/packages/engage-voice-widget/components/ActivityCallLogPanel/ActivityCallLogPanel.ut.tsx index f8bd26c840..3cb0668c01 100644 --- a/packages/engage-voice-widget/components/ActivityCallLogPanel/ActivityCallLogPanel.ut.tsx +++ b/packages/engage-voice-widget/components/ActivityCallLogPanel/ActivityCallLogPanel.ut.tsx @@ -33,15 +33,14 @@ export const UTActivityCallLogPanel: StepFunction = async ( openField.simulate('click'); - const addMenuIcon = getSelectList().find( - 'RcIconButton[data-sign="addEntityMenu"]', - ); + const addMenuIcon = getSelectList() + .find('RcIconButton[data-sign="addEntityMenu"]') + .find('button'); addMenuIcon.simulate('click'); const menuItems = getSelectList().find('RcMenuItem'); - const entityName = props.entityName.toLowerCase(); - menuItems.find(`[title="Create ${entityName}"]`).simulate('click'); + menuItems.find(`[title="New ${props.entityName}"]`).simulate('click'); wrapper.unmount(); }; diff --git a/packages/engage-voice-widget/components/ActivityCallLogPanel/IvrInfo/IvrInfo.tsx b/packages/engage-voice-widget/components/ActivityCallLogPanel/IvrInfo/IvrInfo.tsx index 0005799619..55e398e41f 100644 --- a/packages/engage-voice-widget/components/ActivityCallLogPanel/IvrInfo/IvrInfo.tsx +++ b/packages/engage-voice-widget/components/ActivityCallLogPanel/IvrInfo/IvrInfo.tsx @@ -70,7 +70,7 @@ export const IvrInfo: FunctionComponent = ({ const bodyRender = () => { if (body.length > 0) { if (onClick) { - return {body}; + return {body}; } return
{body}
; } diff --git a/packages/engage-voice-widget/components/ActivityCallLogPanel/i18n/en-US.ts b/packages/engage-voice-widget/components/ActivityCallLogPanel/i18n/en-US.ts index c86c3cb0bb..426929c762 100644 --- a/packages/engage-voice-widget/components/ActivityCallLogPanel/i18n/en-US.ts +++ b/packages/engage-voice-widget/components/ActivityCallLogPanel/i18n/en-US.ts @@ -1,5 +1,7 @@ export default { submit: 'Submit', + create: 'Create', + update: 'Update', disposition: 'Disposition', internalTransfer: 'Internal transfer', phoneBookTransfer: 'Phone book transfer', diff --git a/packages/engage-voice-widget/components/ActivityCallLogPanel/utils/getButtonText.tsx b/packages/engage-voice-widget/components/ActivityCallLogPanel/utils/getButtonText.tsx index 74fbea6552..e2c5de1078 100644 --- a/packages/engage-voice-widget/components/ActivityCallLogPanel/utils/getButtonText.tsx +++ b/packages/engage-voice-widget/components/ActivityCallLogPanel/utils/getButtonText.tsx @@ -14,6 +14,10 @@ export function getButtonText( return ; case 'saving': return null; + case 'create': + return i18n.getString('create', currentLocale); + case 'update': + return i18n.getString('update', currentLocale); case 'submit': default: return i18n.getString('submit', currentLocale); diff --git a/packages/engage-voice-widget/components/AlertRenderer/EvIntegratedSoftphoneAlert/EvIntegratedSoftphoneAlert.tsx b/packages/engage-voice-widget/components/AlertRenderer/EvIntegratedSoftphoneAlert/EvIntegratedSoftphoneAlert.tsx index c42b115695..3f7c58d706 100644 --- a/packages/engage-voice-widget/components/AlertRenderer/EvIntegratedSoftphoneAlert/EvIntegratedSoftphoneAlert.tsx +++ b/packages/engage-voice-widget/components/AlertRenderer/EvIntegratedSoftphoneAlert/EvIntegratedSoftphoneAlert.tsx @@ -26,4 +26,5 @@ EvIntegratedSoftphoneAlert.handleMessage = ({ message }: { message: string }) => tabManagerEvents.SIP_CONNECTING, tabManagerEvents.SIP_RECONNECTING_WHEN_CALL_CONNECTED, tabManagerEvents.ASK_AUDIO_PERMISSION, + tabManagerEvents.NOTIFY_ACTIVE_TAB_CALL_ACTIVE, ]); diff --git a/packages/engage-voice-widget/components/AlertRenderer/EvIntegratedSoftphoneAlert/i18n/en-US.ts b/packages/engage-voice-widget/components/AlertRenderer/EvIntegratedSoftphoneAlert/i18n/en-US.ts index 3acfea88da..434433e21b 100644 --- a/packages/engage-voice-widget/components/AlertRenderer/EvIntegratedSoftphoneAlert/i18n/en-US.ts +++ b/packages/engage-voice-widget/components/AlertRenderer/EvIntegratedSoftphoneAlert/i18n/en-US.ts @@ -12,6 +12,8 @@ export default { [tabManagerEvents.SIP_RECONNECTING_WHEN_CALL_CONNECTED]: 'Try to reconnect Integrated Softphone...', [tabManagerEvents.ASK_AUDIO_PERMISSION]: 'Wait for accept audio permission.', + [tabManagerEvents.NOTIFY_ACTIVE_TAB_CALL_ACTIVE]: + 'You have an incoming call. Switch to the browser tab with the blue flashing dot to answer the call', // Attempt to dequeue call to agent failed! Outdial to destination [16503990023*106] failed after [2] seconds with disposition [INTERCEPT] }; diff --git a/packages/engage-voice-widget/components/BasicSessionPanel/BasicSessionPanel.spec.tsx b/packages/engage-voice-widget/components/BasicSessionPanel/BasicSessionPanel.spec.tsx index 6175ba6a6b..3499a5aa8f 100644 --- a/packages/engage-voice-widget/components/BasicSessionPanel/BasicSessionPanel.spec.tsx +++ b/packages/engage-voice-widget/components/BasicSessionPanel/BasicSessionPanel.spec.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { BasicSessionPanel, BasicSessionPanelProps } from './BasicSessionPanel'; -let wrapper; +let wrapper: ReturnType; const currentLocale = 'en-US'; const defaultSkillProfileList = [ { @@ -203,7 +203,12 @@ describe('', async () => { ); expect(skillProfilePickList.prop('value')).toBe(selectedSkillProfileId); - expect(skillProfilePickList.find('.RcLineSelect-select').text()).toBe( + expect( + skillProfilePickList + .find('RcSelect') + .find('[aria-haspopup="listbox"]') + .text(), + ).toBe( defaultSkillProfileList.find( (x) => x.profileId === selectedSkillProfileId, ).profileName, diff --git a/packages/engage-voice-widget/components/BasicSessionPanel/BasicSessionPanel.tsx b/packages/engage-voice-widget/components/BasicSessionPanel/BasicSessionPanel.tsx index 5c1c80865c..1bcecb01f4 100644 --- a/packages/engage-voice-widget/components/BasicSessionPanel/BasicSessionPanel.tsx +++ b/packages/engage-voice-widget/components/BasicSessionPanel/BasicSessionPanel.tsx @@ -70,6 +70,7 @@ export const BasicSessionPanel: FunctionComponent = ({ {showInboundQueues && ( = ({ /> {isExtensionNumber && ( = ({ {showAutoAnswer && ( & { + method: CallLogMethods; + }; + +export const CallHistoryCallLogPanel: FunctionComponent = ({ + currentLocale, + currentLog, + basicInfo, + isInbound, + disposeCall, + status, + saveStatus, + disableDispose, + isWide, + ivrAlertData, + onCopySuccess, + scrollTo, + referenceFieldOptions, + method, + ...rest +}) => { + const rootRef = useRef(null); + const isLoading = saveStatus === saveStatusValue.saving; + const headerTitle = `${method}CallLog` as CallLogTitle; + + const editLogSection = useCallback( + (props) => ( + + ), + [isWide, scrollTo, referenceFieldOptions], + ); + + return ( + null} + showSpinner={false} + isInTransferPage={false} + // TODO: that need refactor CallLogPanel and then can remove that + currentIdentify="123" + renderEditLogSection={editLogSection} + renderBasicInfo={() => { + return ( + <> + + {ivrAlertData?.length > 0 && ( + + )} + + ); + }} + > +
+ + {getButtonText(saveStatus, currentLocale)} + +
+
+ ); +}; diff --git a/packages/engage-voice-widget/components/CallHistoryCallLogPanel/index.ts b/packages/engage-voice-widget/components/CallHistoryCallLogPanel/index.ts new file mode 100644 index 0000000000..34b95388f5 --- /dev/null +++ b/packages/engage-voice-widget/components/CallHistoryCallLogPanel/index.ts @@ -0,0 +1 @@ +export * from './CallHistoryCallLogPanel'; diff --git a/packages/engage-voice-widget/components/ChooseAccountPanel/ChooseAccountPanel.tsx b/packages/engage-voice-widget/components/ChooseAccountPanel/ChooseAccountPanel.tsx index 52f9c4835a..791e4554e1 100644 --- a/packages/engage-voice-widget/components/ChooseAccountPanel/ChooseAccountPanel.tsx +++ b/packages/engage-voice-widget/components/ChooseAccountPanel/ChooseAccountPanel.tsx @@ -28,25 +28,33 @@ export const ChooseAccountPanel: FunctionComponent = ({ {i18n.getString('chooseAccount', currentLocale)} - {agents.map((agent) => { - return ( - onAccountItemClick(agent.agentId)} - key={agent.agentId} - className={styles.listItem} - > -
- - {agent.accountName} - - - {i18n.getString(agent.agentType, currentLocale)} - -
- -
- ); - })} +
+ {agents.map((agent) => { + return ( + onAccountItemClick(agent.agentId)} + key={agent.agentId} + className={styles.listItem} + > +
+
+ + {agent.accountName} + + + {i18n.getString(agent.agentType, currentLocale)} + +
+ +
+
+ ); + })} +
); }; diff --git a/packages/engage-voice-widget/components/ChooseAccountPanel/styles.scss b/packages/engage-voice-widget/components/ChooseAccountPanel/styles.scss index 916f468dd8..580ef9cc87 100644 --- a/packages/engage-voice-widget/components/ChooseAccountPanel/styles.scss +++ b/packages/engage-voice-widget/components/ChooseAccountPanel/styles.scss @@ -26,12 +26,29 @@ color: rc-palette(text, subdued); } + .lists { + height: calc(100% - 136px); + overflow-y: auto; + } + div.listItem.listItem { height: 56px; + width: 100%; + + .content { + height: 56px; + width: 100%; + border-bottom: 1px solid rc-palette(border, light); + display: flex; + align-items: center; + justify-content: space-between; + div { + display: inline-block; + } + } } .accountName { padding-bottom: 4px; } - } diff --git a/packages/engage-voice-widget/components/DialerPanel/DialerPanel.tsx b/packages/engage-voice-widget/components/DialerPanel/DialerPanel.tsx index ed753fbcfc..5ffe267472 100644 --- a/packages/engage-voice-widget/components/DialerPanel/DialerPanel.tsx +++ b/packages/engage-voice-widget/components/DialerPanel/DialerPanel.tsx @@ -18,6 +18,12 @@ const dialoutStatusMapping = { idle: ['semantic', 'positive'], } as const; +const LinkSizeMapping = { + small: 'caption1', + medium: 'body1', + large: 'headline', +} as const; + export type DialerPanelProps = EvDialerUIProps & EvDialerUIFunctions; const DialerPanel: FunctionComponent = ({ @@ -71,8 +77,8 @@ const DialerPanel: FunctionComponent = ({
{i18n.getString('manualDialSettings', currentLocale)} diff --git a/packages/engage-voice-widget/components/MainViewPanel/WorkingStateSelect/WorkingStateButton/WorkingStateButton.tsx b/packages/engage-voice-widget/components/MainViewPanel/WorkingStateSelect/WorkingStateButton/WorkingStateButton.tsx index 29b4b193a1..cfefedf51b 100644 --- a/packages/engage-voice-widget/components/MainViewPanel/WorkingStateSelect/WorkingStateButton/WorkingStateButton.tsx +++ b/packages/engage-voice-widget/components/MainViewPanel/WorkingStateSelect/WorkingStateButton/WorkingStateButton.tsx @@ -84,7 +84,6 @@ export const WorkingStateButton: FunctionComponent = ({ return ( { handleClose(); onChange(state); diff --git a/packages/engage-voice-widget/components/MainViewPanel/WorkingStateSelect/WorkingStateButton/styles.scss b/packages/engage-voice-widget/components/MainViewPanel/WorkingStateSelect/WorkingStateButton/styles.scss index 63cac2dc53..5b2eb5a92e 100644 --- a/packages/engage-voice-widget/components/MainViewPanel/WorkingStateSelect/WorkingStateButton/styles.scss +++ b/packages/engage-voice-widget/components/MainViewPanel/WorkingStateSelect/WorkingStateButton/styles.scss @@ -43,14 +43,6 @@ $green-light: #edf5fb; margin-right: 6px; } -ul.list { - padding: 4px 0; -} - -li.item { - min-height: 24px; - padding: 0; -} .stateDot { width: 8px; height: 8px; diff --git a/packages/engage-voice-widget/components/MainViewPanel/styles.scss b/packages/engage-voice-widget/components/MainViewPanel/styles.scss index c51f5507bc..1c0f727bd1 100644 --- a/packages/engage-voice-widget/components/MainViewPanel/styles.scss +++ b/packages/engage-voice-widget/components/MainViewPanel/styles.scss @@ -28,8 +28,10 @@ .icon { svg { + width: 20px; height: 20px; [sf-classic] & { + width: 18px; height: 18px; } } diff --git a/packages/engage-voice-widget/components/ManualDialSettingsPanel/ManualDialSettingsPanel.tsx b/packages/engage-voice-widget/components/ManualDialSettingsPanel/ManualDialSettingsPanel.tsx index 4aa63c69f7..e5234f5140 100644 --- a/packages/engage-voice-widget/components/ManualDialSettingsPanel/ManualDialSettingsPanel.tsx +++ b/packages/engage-voice-widget/components/ManualDialSettingsPanel/ManualDialSettingsPanel.tsx @@ -100,6 +100,7 @@ export const ManualDialSettingsPanel: FunctionComponent onBlur()} onChange={(e) => onChange(e.target.value)} fullWidth + gutterBottom InputProps={{ endAdornment: ( diff --git a/packages/engage-voice-widget/components/PickList/PickList.tsx b/packages/engage-voice-widget/components/PickList/PickList.tsx index f63d5a5d43..84aab96390 100644 --- a/packages/engage-voice-widget/components/PickList/PickList.tsx +++ b/packages/engage-voice-widget/components/PickList/PickList.tsx @@ -1,9 +1,9 @@ -import { RcLineSelect, RcLineSelectProps, RcMenuItem } from '@ringcentral/juno'; +import { RcMenuItem, RcSelect, RcSelectProps } from '@ringcentral/juno'; import React, { FunctionComponent, ReactNode } from 'react'; import styles from './styles.scss'; -export interface PickListProps extends Pick { +export interface PickListProps extends Pick { options: any[]; /** * the option value with key of options, default is 'id' @@ -37,9 +37,10 @@ export const PickList: FunctionComponent = ({ ...rest }) => { return ( - = ({ ); })} - + ); }; diff --git a/packages/engage-voice-widget/components/RequeueCallPanel/RequeueCallGroupPanel/RequeueCallGroupDetailPanel/RequeueCallGroupDetailPanel.spec.tsx b/packages/engage-voice-widget/components/RequeueCallPanel/RequeueCallGroupPanel/RequeueCallGroupDetailPanel/RequeueCallGroupDetailPanel.spec.tsx index 9387290b4d..c7a967ec99 100644 --- a/packages/engage-voice-widget/components/RequeueCallPanel/RequeueCallGroupPanel/RequeueCallGroupDetailPanel/RequeueCallGroupDetailPanel.spec.tsx +++ b/packages/engage-voice-widget/components/RequeueCallPanel/RequeueCallGroupPanel/RequeueCallGroupDetailPanel/RequeueCallGroupDetailPanel.spec.tsx @@ -1,6 +1,7 @@ -import React from 'react'; import { RcThemeProvider } from '@ringcentral/juno'; import { mount } from 'enzyme'; +import React from 'react'; + import { RequeueCallGroupDetailPanel } from './RequeueCallGroupDetailPanel'; let wrapper; @@ -42,8 +43,7 @@ function setup({ ); } -const getSearchInput = () => - wrapper.find('RcOutlineTextField').at(0).find('input'); +const getSearchInput = () => wrapper.find('RcTextField').at(0).find('input'); const getDetailItems = () => wrapper.find('RcList').at(0).find('RcListItem'); diff --git a/packages/engage-voice-widget/components/RequeueCallPanel/RequeueCallGroupPanel/RequeueCallGroupPanel.spec.tsx b/packages/engage-voice-widget/components/RequeueCallPanel/RequeueCallGroupPanel/RequeueCallGroupPanel.spec.tsx index 778625f837..9f4aa1f6d5 100644 --- a/packages/engage-voice-widget/components/RequeueCallPanel/RequeueCallGroupPanel/RequeueCallGroupPanel.spec.tsx +++ b/packages/engage-voice-widget/components/RequeueCallPanel/RequeueCallGroupPanel/RequeueCallGroupPanel.spec.tsx @@ -45,22 +45,15 @@ const getGroupItems = () => .at(0) .find('RcList') .at(0) - .find('RcListItem');; + .find('RcListItem'); -const getSearchInput = () => - wrapper - .find('RcOutlineTextField') - .at(0) - .find('input'); +const getSearchInput = () => wrapper.find('RcTextField').at(0).find('input'); describe('', async () => { it('Has no available Requeue Group', () => { wrapper = setup({ queueGroups: [] }); expect( - wrapper - .find('[data-sign="searchResult"]') - .at(0) - .find('RcList').length, + wrapper.find('[data-sign="searchResult"]').at(0).find('RcList').length, ).toBe(0); }); @@ -96,10 +89,7 @@ describe('', async () => { const goToRequeueGroupDetailPage = jest.fn(() => {}); wrapper = setup({ goToRequeueGroupDetailPage }); const selectIndex = 1; - getGroupItems() - .at(selectIndex) - .find('[role="button"]') - .simulate('click'); + getGroupItems().at(selectIndex).find('[role="button"]').simulate('click'); expect(goToRequeueGroupDetailPage).toBeCalledWith({ groupId: defaultQueueGroups[selectIndex].gateGroupId, @@ -122,12 +112,7 @@ describe('', async () => { expect(getGroupItems().length).toBe(0); expect( - wrapper - .find('[data-sign="searchResult"]') - .at(0) - .find('div') - .at(0) - .text(), + wrapper.find('[data-sign="searchResult"]').at(0).find('div').at(0).text(), ).toBe(`No result found for "${searchText}"`); }); }); diff --git a/packages/engage-voice-widget/components/SessionConfigPanel/SessionConfigPanel.tsx b/packages/engage-voice-widget/components/SessionConfigPanel/SessionConfigPanel.tsx index 65759632a6..734ef9443c 100644 --- a/packages/engage-voice-widget/components/SessionConfigPanel/SessionConfigPanel.tsx +++ b/packages/engage-voice-widget/components/SessionConfigPanel/SessionConfigPanel.tsx @@ -1,6 +1,7 @@ import { RcButton, RcIconButton, RcTypography } from '@ringcentral/juno'; import React, { FunctionComponent } from 'react'; -import chevronLeftSvg from '@ringcentral/juno/icon/ChevronLeft'; +import arrowLeftSvg from '@ringcentral/juno/icon/ArrowLeft2'; +import classNames from 'classnames'; import { EvAgentSessionUIFunctions, @@ -39,28 +40,35 @@ export const SessionConfigPanel: FunctionComponent = ({ wrapperStyle={styles.wrapperStyle} svgStyle={styles.svgStyle} /> - {showReChooseAccount && ( -
-
- - - {i18n.getString('switchAccount', currentLocale)} - -
+
+
+ + + {i18n.getString('switchAccount', currentLocale)} +
- )} -
+
+
{selectedAgent?.accountName} - + {i18n.getString(selectedAgent?.agentType, currentLocale)}
diff --git a/packages/engage-voice-widget/components/SessionConfigPanel/styles.scss b/packages/engage-voice-widget/components/SessionConfigPanel/styles.scss index cbd6341426..e3bf06106b 100644 --- a/packages/engage-voice-widget/components/SessionConfigPanel/styles.scss +++ b/packages/engage-voice-widget/components/SessionConfigPanel/styles.scss @@ -14,7 +14,10 @@ .goBackBg { background: rc-palette(bg, primaryLightest); - margin-bottom: 16px; + } + + .hideGoBack { + visibility: hidden; } .goBack { @@ -38,6 +41,7 @@ display: flex; justify-content: space-between; align-items: center; + padding-top: 16px; padding-bottom: 24px; } diff --git a/packages/engage-voice-widget/components/TransferCallPanel/TransferCallPanel.tsx b/packages/engage-voice-widget/components/TransferCallPanel/TransferCallPanel.tsx index c71fe4d55c..c4ff43b377 100644 --- a/packages/engage-voice-widget/components/TransferCallPanel/TransferCallPanel.tsx +++ b/packages/engage-voice-widget/components/TransferCallPanel/TransferCallPanel.tsx @@ -105,6 +105,7 @@ const TransferCallPanel: FunctionComponent = ({ ) => ( - wrapper - .find('RcList') - .at(0) - .find('div[data-sign="agentItem"]'); + wrapper.find('RcList').at(0).find('div[data-sign="agentItem"]'); -const getSearchInput = () => - wrapper - .find('RcOutlineTextField') - .at(0) - .find('input'); +const getSearchInput = () => wrapper.find('RcTextField').at(0).find('input'); export const UTAgentListCheckBackButton: StepFunction = () => { const goBack = jest.fn(() => {}); @@ -129,12 +123,9 @@ export const UTAgentListDisplayAndHighlight: StepFunction = () => { const selectedIndex = defaultTransferAgentList.findIndex( (x) => x.agentId === transferAgentId, ); - expect( - agentItems - .at(selectedIndex) - .render() - .attr('class'), - ).toMatch(/Mui-selected/g); + expect(agentItems.at(selectedIndex).render().attr('class')).toMatch( + /Mui-selected/g, + ); }; export const UTAgentListSearchCases = [ @@ -181,10 +172,7 @@ export const UTAgentListSearch: StepFunction = ({ if (Array.isArray(matchedResult)) { expect(agentItems).toHaveLength(matchedResult.length); const resultItems = agentItems.map((el) => - el - .find('.agentName') - .text() - .trim(), + el.find('.agentName').text().trim(), ); expect(resultItems).toStrictEqual(matchedResult); } else { @@ -247,11 +235,7 @@ export const UTCheckTransferAgentSelect: StepFunction = ], }); const agentItem = wrapper.find('[data-sign="agentItem"]'); - expect( - agentItem - .find('.agentName') - .text() - .trim(), - ).toBe(recipient); - expect( - agentItem - .find('.statusText') - .text() - .trim(), - ).toBe(availableStatus); + expect(agentItem.find('.agentName').text().trim()).toBe(recipient); + expect(agentItem.find('.statusText').text().trim()).toBe(availableStatus); if (statusColor === 'green') { expect(agentItem.find('.available')).toHaveLength(1); } else if (statusColor === 'gray') { diff --git a/packages/engage-voice-widget/components/TransferCallRecipient/PhoneBookPanel/PhoneBookPanel.ut.tsx b/packages/engage-voice-widget/components/TransferCallRecipient/PhoneBookPanel/PhoneBookPanel.ut.tsx index 039ac06d6b..0121f778fa 100644 --- a/packages/engage-voice-widget/components/TransferCallRecipient/PhoneBookPanel/PhoneBookPanel.ut.tsx +++ b/packages/engage-voice-widget/components/TransferCallRecipient/PhoneBookPanel/PhoneBookPanel.ut.tsx @@ -1,10 +1,11 @@ -import React from 'react'; -import { StepFunction } from 'crius-test'; -import { RcThemeProvider } from '@ringcentral/juno'; import { format } from '@ringcentral-integration/phone-number'; +import { RcThemeProvider } from '@ringcentral/juno'; +import { StepFunction } from 'crius-test'; import { mount } from 'enzyme'; -import { PhoneBookPanel, PhoneBookPanelProps } from './PhoneBookPanel'; +import React from 'react'; + import { EvTransferPhoneBookItem } from '../../../lib/EvClient'; +import { PhoneBookPanel, PhoneBookPanelProps } from './PhoneBookPanel'; let wrapper; const currentLocale = 'en-US'; @@ -59,16 +60,9 @@ function setup({ } const getPhoneContacts = () => - wrapper - .find('RcList') - .at(0) - .find('div[data-sign="phoneContact"]'); + wrapper.find('RcList').at(0).find('div[data-sign="phoneContact"]'); -const getSearchInput = () => - wrapper - .find('RcOutlineTextField') - .at(0) - .find('input'); +const getSearchInput = () => wrapper.find('RcTextField').at(0).find('input'); export const UTPhoneBookCheckBackButton: StepFunction = () => { const goBack = jest.fn(() => {}); @@ -89,10 +83,7 @@ export const UTPhoneBookContactListDisplayAndHighlight: StepFunction = () => { const phoneContacts = getPhoneContacts(); expect(phoneContacts.length).toBe(defaultTransferPhoneBook.length); expect( - phoneContacts - .at(transferPhoneBookSelectedIndex) - .render() - .attr('class'), + phoneContacts.at(transferPhoneBookSelectedIndex).render().attr('class'), ).toMatch(/Mui-selected/g); }; diff --git a/packages/engage-voice-widget/containers/CallHistoryCallLogPage/CallHistoryCallLogPage.tsx b/packages/engage-voice-widget/containers/CallHistoryCallLogPage/CallHistoryCallLogPage.tsx new file mode 100644 index 0000000000..bc3fe9d48e --- /dev/null +++ b/packages/engage-voice-widget/containers/CallHistoryCallLogPage/CallHistoryCallLogPage.tsx @@ -0,0 +1,13 @@ +import { CallLogMethods } from '../../interfaces/EvActivityCallUI.interface'; +import { ActivityCallLogPanelProps } from '../../components/ActivityCallLogPanel'; +import { CallHistoryCallLogPanel } from '../../components/CallHistoryCallLogPanel'; +import { connectModule } from '../../lib/connectModule'; + +export type CallHistoryCallLogPageProps = { + id: string; + method: CallLogMethods; +} & Pick; + +export const CallHistoryCallLogPage = connectModule< + CallHistoryCallLogPageProps +>((phone) => phone.evActivityCallUI)(CallHistoryCallLogPanel); diff --git a/packages/engage-voice-widget/containers/CallHistoryCallLogPage/index.ts b/packages/engage-voice-widget/containers/CallHistoryCallLogPage/index.ts new file mode 100644 index 0000000000..02149958eb --- /dev/null +++ b/packages/engage-voice-widget/containers/CallHistoryCallLogPage/index.ts @@ -0,0 +1 @@ +export * from './CallHistoryCallLogPage'; diff --git a/packages/engage-voice-widget/enums/index.ts b/packages/engage-voice-widget/enums/index.ts index 941fa785fa..a660476905 100644 --- a/packages/engage-voice-widget/enums/index.ts +++ b/packages/engage-voice-widget/enums/index.ts @@ -3,7 +3,7 @@ export * from './agentSessionEvents'; export * from './agentStateTypes'; export * from './agentStatesColors'; export * from './agentStatesTexts'; -export * from './authStatus'; +export * from './loginStatus'; export * from './callStatus'; export * from './dialoutStatus'; export * from './directTransferNotificationTypes'; diff --git a/packages/engage-voice-widget/enums/authStatus.ts b/packages/engage-voice-widget/enums/loginStatus.ts similarity index 71% rename from packages/engage-voice-widget/enums/authStatus.ts rename to packages/engage-voice-widget/enums/loginStatus.ts index 39978c3f67..249218131f 100644 --- a/packages/engage-voice-widget/enums/authStatus.ts +++ b/packages/engage-voice-widget/enums/loginStatus.ts @@ -1,11 +1,13 @@ import { ObjectMap } from '@ringcentral-integration/core/lib/ObjectMap'; -export const authStatus = ObjectMap.prefixKeys( +export const loginStatus = ObjectMap.prefixKeys( [ 'AUTH_SUCCESS', 'LOGIN_SUCCESS', 'LOGOUT_BEFORE', 'LOGOUT_AFTER', + 'LOGGING_OUT', + 'NOT_AUTH', 'BEFORE_LOGOUT_COMPLETE', ], 'auth', diff --git a/packages/engage-voice-widget/enums/tabManagerEvents.ts b/packages/engage-voice-widget/enums/tabManagerEvents.ts index 5776ee0a28..14c17db1e3 100644 --- a/packages/engage-voice-widget/enums/tabManagerEvents.ts +++ b/packages/engage-voice-widget/enums/tabManagerEvents.ts @@ -4,6 +4,7 @@ export const tabManagerEvents = ObjectMap.prefixKeys( [ 'MUTE', 'MUTE_STATE_CHANGE', + 'NOTIFY_ACTIVE_TAB_CALL_ACTIVE', // Session config related 'SET_MIAN_TAB_ID', 'MAIN_TAB_WILL_UNLOAD', @@ -31,11 +32,11 @@ export const tabManagerEvents = ObjectMap.prefixKeys( 'CLOSE_WHEN_CALL_CONNECTED', // Auth related 'LOGOUT', + 'LOGGED_OUT', 'OPEN_SOCKET', 'RELOGIN', 'CONFIGURE_FAIL', 'RE_CHOOSE_ACCOUNT', - 'SET_AGENT_ID', ], 'tabManager', ); diff --git a/packages/engage-voice-widget/interfaces/EvActivityCallUI.interface.ts b/packages/engage-voice-widget/interfaces/EvActivityCallUI.interface.ts index 93590809f0..aed5bc8a5a 100644 --- a/packages/engage-voice-widget/interfaces/EvActivityCallUI.interface.ts +++ b/packages/engage-voice-widget/interfaces/EvActivityCallUI.interface.ts @@ -1,3 +1,4 @@ +import { ObjectMap } from '@ringcentral-integration/core/lib/ObjectMap'; import { BasicCallInfoProps } from 'ringcentral-widgets/components/BasicCallInfo'; import { CallLogFieldsProps } from 'ringcentral-widgets/components/CallLogFields'; import { @@ -19,8 +20,17 @@ export type EvCallLogTask = CallLogPanelCurrentLog['task'] & { export type EvCurrentLog = CallLogPanelCurrentLog & { currentEvRawCall: EvCallData; task: { [key: string]: any }; + showInfoMeta?: { + entity: any; + title: string; + }; }; +export const callLogMethods = ObjectMap.fromKeys(['create', 'update']); +export type CallLogMethods = keyof typeof callLogMethods; +export const saveStatus = ObjectMap.fromKeys(['saved', 'saving', 'submit']); +export type SaveStatus = keyof typeof saveStatus; + export type EvActivityCallUIProps = { scrollTo: string; currentLog: EvCurrentLog; @@ -32,7 +42,7 @@ export type EvActivityCallUIProps = { currentEvCall: EvCallData; status: CallStatus; disableDispose: boolean; - saveStatus: 'saved' | 'saving' | 'submit'; + saveStatus: SaveStatus | CallLogMethods; smallCallControlSize: 'medium' | 'small'; currentCallControlPermission: { allowHangupCall?: boolean; diff --git a/packages/engage-voice-widget/interfaces/EvData.interface.ts b/packages/engage-voice-widget/interfaces/EvData.interface.ts index d53e275bdc..8fbbfd004a 100644 --- a/packages/engage-voice-widget/interfaces/EvData.interface.ts +++ b/packages/engage-voice-widget/interfaces/EvData.interface.ts @@ -14,10 +14,11 @@ export type EvCallData = EvBaseCall & { isHold?: boolean; agentName?: string; /** transform queueDts from time zone date to timeStamp */ - timestamp: number; - gate: EvEvRequeueCallGate; + timestamp?: number; + gate?: EvEvRequeueCallGate; ivrString?: string; id?: string; + recordingUrl?: string; CALL_UNIQUE_ID__c?: string; }; diff --git a/packages/engage-voice-widget/lib/EvClient/EvClient.ts b/packages/engage-voice-widget/lib/EvClient/EvClient.ts index 246aeeb1ca..62c650d97d 100644 --- a/packages/engage-voice-widget/lib/EvClient/EvClient.ts +++ b/packages/engage-voice-widget/lib/EvClient/EvClient.ts @@ -168,6 +168,10 @@ class EvClient extends RcModuleV2 { } } + onInitOnce() { + this.initSDK(); + } + initSDK() { console.log('initSDK'); const { _Sdk: Sdk } = this; diff --git a/packages/engage-voice-widget/lib/EvClient/__SDK__/agentLibrary.js b/packages/engage-voice-widget/lib/EvClient/__SDK__/agentLibrary.js index 13b2394e8d..f3b58ad1fb 100644 --- a/packages/engage-voice-widget/lib/EvClient/__SDK__/agentLibrary.js +++ b/packages/engage-voice-widget/lib/EvClient/__SDK__/agentLibrary.js @@ -1,7 +1,7 @@ /* eslint-disable */ import SIP from 'sip.js'; -(function() { +(function () { 'use strict'; /** * @fileoverview CFSimpleSip @@ -22,7 +22,7 @@ import SIP from 'sip.js'; /* * @param {Object} options */ - var CFSimpleSip = function(options) { + var CFSimpleSip = function (options) { /* * { * media: { @@ -74,7 +74,18 @@ import SIP from 'sip.js'; ) { isFirefox = true; } - var sessionDescriptionHandlerFactoryOptions = {}; + var sessionDescriptionHandlerFactoryOptions = { + peerConnectionOptions: { + iceCheckingTimeout: 500, + rtcConfiguration: { + iceServers: [ + { urls: 'stun:stun.l.google.com:19302' }, + { urls: 'stun:stun.vacd.biz:19302' }, + { urls: 'stun:stun.virtualacd.biz:3478' }, + ], + }, + }, + }; if (isSafari) { sessionDescriptionHandlerFactoryOptions.modifiers = [ SIP.Web.Modifiers.stripG722, @@ -107,7 +118,7 @@ import SIP from 'sip.js'; transportOptions: { traceSip: this.options.ua.traceSip, wsServers: this.options.ua.wsServers, - maxReconnectionAttempts: 1000, + maxReconnectionAttempts: 2, }, dtmfType: SIP.C.dtmfType.RTP, }); @@ -118,28 +129,28 @@ import SIP from 'sip.js'; this.ua.on( 'registered', - function() { + function () { this.emit('registered', this.ua); }.bind(this), ); this.ua.on( 'unregistered', - function() { + function () { this.emit('unregistered', this.ua); }.bind(this), ); this.ua.on( 'failed', - function() { + function () { this.emit('unregistered', this.ua); }.bind(this), ); this.ua.on( 'invite', - function(session) { + function (session) { // If there is already an active session reject the incoming session if (this.state !== C.STATUS_NULL && this.state !== C.STATUS_COMPLETED) { this.logger.warn( @@ -156,7 +167,7 @@ import SIP from 'sip.js'; this.ua.on( 'message', - function(message) { + function (message) { this.emit('message', message); }.bind(this), ); @@ -169,7 +180,7 @@ import SIP from 'sip.js'; // Public - CFSimpleSip.prototype.call = function(destination) { + CFSimpleSip.prototype.call = function (destination) { if (!this.ua || !this.checkRegistration()) { this.logger.warn('A registered UA is required for calling'); return; @@ -202,7 +213,7 @@ import SIP from 'sip.js'; return this.session; }; - CFSimpleSip.prototype.answer = function() { + CFSimpleSip.prototype.answer = function () { if (this.state !== C.STATUS_NEW && this.state !== C.STATUS_CONNECTING) { this.logger.warn('No call to answer'); return; @@ -225,7 +236,7 @@ import SIP from 'sip.js'; // emit call is active }; - CFSimpleSip.prototype.reject = function() { + CFSimpleSip.prototype.reject = function () { if (this.state !== C.STATUS_NEW && this.state !== C.STATUS_CONNECTING) { this.logger.warn('Call is already answered'); return; @@ -233,7 +244,7 @@ import SIP from 'sip.js'; return this.session.reject(); }; - CFSimpleSip.prototype.hangup = function() { + CFSimpleSip.prototype.hangup = function () { if ( this.state !== C.STATUS_CONNECTED && this.state !== C.STATUS_CONNECTING && @@ -249,7 +260,7 @@ import SIP from 'sip.js'; } }; - CFSimpleSip.prototype.hold = function() { + CFSimpleSip.prototype.hold = function () { if (this.state !== C.STATUS_CONNECTED || this.session.local_hold) { this.logger.warn('Cannot put call on hold'); return; @@ -259,7 +270,7 @@ import SIP from 'sip.js'; return this.session.hold(); }; - CFSimpleSip.prototype.unhold = function() { + CFSimpleSip.prototype.unhold = function () { if (this.state !== C.STATUS_CONNECTED || !this.session.local_hold) { this.logger.warn('Cannot unhold a call that is not on hold'); return; @@ -269,7 +280,7 @@ import SIP from 'sip.js'; return this.session.unhold(); }; - CFSimpleSip.prototype.mute = function() { + CFSimpleSip.prototype.mute = function () { if (this.state !== C.STATUS_CONNECTED) { this.logger.warn('An acitve call is required to mute audio'); return; @@ -279,7 +290,7 @@ import SIP from 'sip.js'; this.emit('mute', this); }; - CFSimpleSip.prototype.unmute = function() { + CFSimpleSip.prototype.unmute = function () { if (this.state !== C.STATUS_CONNECTED) { this.logger.warn('An active call is required to unmute audio'); return; @@ -289,7 +300,7 @@ import SIP from 'sip.js'; this.emit('unmute', this); }; - CFSimpleSip.prototype.sendDTMF = function(tone) { + CFSimpleSip.prototype.sendDTMF = function (tone) { if (this.state !== C.STATUS_CONNECTED) { this.logger.warn('An active call is required to send a DTMF tone'); return; @@ -298,7 +309,7 @@ import SIP from 'sip.js'; this.session.dtmf(tone); }; - CFSimpleSip.prototype.message = function(destination, message) { + CFSimpleSip.prototype.message = function (destination, message) { if (!this.ua || !this.checkRegistration()) { this.logger.warn('A registered UA is required to send a message'); return; @@ -314,18 +325,18 @@ import SIP from 'sip.js'; // Private Helpers - CFSimpleSip.prototype.checkRegistration = function() { + CFSimpleSip.prototype.checkRegistration = function () { return this.anonymous || (this.ua && this.ua.isRegistered()); }; - CFSimpleSip.prototype.setupRemoteMedia = function() { + CFSimpleSip.prototype.setupRemoteMedia = function () { // If there is a video track, it will attach the video and audio to the same element var pc = this.session.sessionDescriptionHandler.peerConnection; var remoteStream; if (pc.getReceivers) { remoteStream = new window.window.MediaStream(); - pc.getReceivers().forEach(function(receiver) { + pc.getReceivers().forEach(function (receiver) { var track = receiver.track; if (track) { remoteStream.addTrack(track); @@ -337,21 +348,21 @@ import SIP from 'sip.js'; if (this.video) { this.options.media.remote.video.srcObject = remoteStream; this.options.media.remote.video.play().catch( - function() { + function () { this.logger.log('play was rejected'); }.bind(this), ); } else if (this.audio) { this.options.media.remote.audio.srcObject = remoteStream; this.options.media.remote.audio.play().catch( - function() { + function () { this.logger.log('play was rejected'); }.bind(this), ); } }; - CFSimpleSip.prototype.setupLocalMedia = function() { + CFSimpleSip.prototype.setupLocalMedia = function () { if ( this.video && this.options.media.local && @@ -361,7 +372,7 @@ import SIP from 'sip.js'; var localStream; if (pc.getSenders) { localStream = new window.window.MediaStream(); - pc.getSenders().forEach(function(sender) { + pc.getSenders().forEach(function (sender) { var track = sender.track; if (track && track.kind === 'video') { localStream.addTrack(track); @@ -376,7 +387,7 @@ import SIP from 'sip.js'; } }; - CFSimpleSip.prototype.cleanupMedia = function() { + CFSimpleSip.prototype.cleanupMedia = function () { if (this.video) { this.options.media.remote.video.srcObject = null; this.options.media.remote.video.pause(); @@ -391,7 +402,7 @@ import SIP from 'sip.js'; } }; - CFSimpleSip.prototype.setupSession = function() { + CFSimpleSip.prototype.setupSession = function () { this.state = C.STATUS_NEW; this.emit('new', this.session); @@ -402,31 +413,31 @@ import SIP from 'sip.js'; this.session.on('terminated', this.onEnded.bind(this)); }; - CFSimpleSip.prototype.destroyMedia = function() { + CFSimpleSip.prototype.destroyMedia = function () { this.session.sessionDescriptionHandler.close(); }; - CFSimpleSip.prototype.toggleMute = function(mute) { + CFSimpleSip.prototype.toggleMute = function (mute) { var pc = this.session.sessionDescriptionHandler.peerConnection; if (pc.getSenders) { - pc.getSenders().forEach(function(sender) { + pc.getSenders().forEach(function (sender) { if (sender.track) { sender.track.enabled = !mute; } }); } else { - pc.getLocalStreams().forEach(function(stream) { - stream.getAudioTracks().forEach(function(track) { + pc.getLocalStreams().forEach(function (stream) { + stream.getAudioTracks().forEach(function (track) { track.enabled = !mute; }); - stream.getVideoTracks().forEach(function(track) { + stream.getVideoTracks().forEach(function (track) { track.enabled = !mute; }); }); } }; - CFSimpleSip.prototype.onAccepted = function() { + CFSimpleSip.prototype.onAccepted = function () { this.state = C.STATUS_CONNECTED; this.emit('connected', this.session); @@ -434,7 +445,7 @@ import SIP from 'sip.js'; this.setupRemoteMedia(); this.session.sessionDescriptionHandler.on( 'addTrack', - function() { + function () { this.logger.log( 'A track has been added, triggering new remoteMedia setup', ); @@ -444,7 +455,7 @@ import SIP from 'sip.js'; this.session.sessionDescriptionHandler.on( 'addStream', - function() { + function () { this.logger.log( 'A stream has been added, trigger new remoteMedia setup', ); @@ -454,35 +465,35 @@ import SIP from 'sip.js'; this.session.on( 'hold', - function() { + function () { this.emit('hold', this.session); }.bind(this), ); this.session.on( 'unhold', - function() { + function () { this.emit('unhold', this.session); }.bind(this), ); this.session.on( 'dtmf', - function(tone) { + function (tone) { this.emit('dtmf', tone); }.bind(this), ); this.session.on('bye', this.onEnded.bind(this)); }; - CFSimpleSip.prototype.onProgress = function() { + CFSimpleSip.prototype.onProgress = function () { this.state = C.STATUS_CONNECTING; this.emit('connecting', this.session); }; - CFSimpleSip.prototype.onFailed = function() { + CFSimpleSip.prototype.onFailed = function () { this.onEnded(); }; - CFSimpleSip.prototype.onEnded = function() { + CFSimpleSip.prototype.onEnded = function () { this.state = C.STATUS_COMPLETED; this.emit('ended', this.session); this.cleanupMedia(); @@ -492,15 +503,15 @@ import SIP from 'sip.js'; return CFSimpleSip; })(); -export default (function() { +export default (function () { /** * @fileOverview Exposed functionality for Contact Center AgentUI. * @version 3.0.14 * @namespace AgentLibrary */ - return function(global) { - var AddSessionNotification = function() {}; + return function (global) { + var AddSessionNotification = function () {}; /* * This class is responsible for handling "ADD-SESSION" packets from IntelliQueue. This is used by @@ -522,8 +533,7 @@ export default (function() { * } * } */ - AddSessionNotification.prototype.processResponse = function(notification) { - var formattedResponse = utils.buildDefaultResponse(notification); + AddSessionNotification.prototype.processResponse = function (notification) { var model = UIModel.getInstance(); var notif = notification.ui_notification; var sessionAgentId = utils.getText(notif, 'agent_id'); @@ -583,6 +593,7 @@ export default (function() { model.agentSettings.onCall = true; } + var formattedResponse = utils.buildDefaultResponse(notification); formattedResponse.status = 'OK'; formattedResponse.message = 'Received ADD-SESSION notification'; formattedResponse.sessionId = utils.getText(notif, 'session_id'); @@ -593,12 +604,13 @@ export default (function() { formattedResponse.allowControl = utils.getText(notif, 'allow_control'); formattedResponse.monitoring = utils.getText(notif, 'monitoring'); formattedResponse.agentId = utils.getText(notif, 'agent_id'); + formattedResponse.recordingUrl = utils.getText(notif, 'recording_url'); formattedResponse.transferSessions = model.transferSessions; return formattedResponse; }; - var AdminDebugEmailNotification = function() {}; + var AdminDebugEmailNotification = function () {}; /* * This class is responsible for handling "AGENT-DEBUG-EMAIL" packets from IntelliQueue @@ -617,7 +629,7 @@ export default (function() { * } * } */ - AdminDebugEmailNotification.prototype.processResponse = function( + AdminDebugEmailNotification.prototype.processResponse = function ( notification, ) { var formattedResponse = utils.buildDefaultResponse(notification); @@ -631,7 +643,7 @@ export default (function() { return formattedResponse; }; - var DialGroupChangeNotification = function() {}; + var DialGroupChangeNotification = function () {}; /* * This class is responsible for handling a DIAL_GROUP_CHANGE notification. @@ -648,7 +660,7 @@ export default (function() { * } * } */ - DialGroupChangeNotification.prototype.processResponse = function( + DialGroupChangeNotification.prototype.processResponse = function ( notification, ) { //Modify loginRequest with new DialGroupId @@ -677,19 +689,18 @@ export default (function() { UIModel.getInstance().loginRequest.dialGroupId = newDgId; - var formattedResponse = { - message: 'Dial Group Updated Successfully.', - detail: 'Dial Group changed to [' + newDgId + '].', - dialGroupId: utils.getText(notif, 'dial_group_id'), - dialGroupName: utils.getText(notif, 'dialGroupName'), // camel case from server for some reason :/ - dialGroupDesc: utils.getText(notif, 'dial_group_desc'), - agentId: utils.getText(notif, 'agent_id'), - }; + var formattedResponse = utils.buildDefaultResponse(notification); + formattedResponse.message = 'Dial Group Updated Successfully.'; + formattedResponse.detail = 'Dial Group changed to [' + newDgId + '].'; + formattedResponse.dialGroupId = utils.getText(notif, 'dial_group_id'); + formattedResponse.dialGroupName = utils.getText(notif, 'dialGroupName'); // camel case from server for some reason :/ + formattedResponse.dialGroupDesc = utils.getText(notif, 'dial_group_desc'); + formattedResponse.agentId = utils.getText(notif, 'agent_id'); return formattedResponse; }; - var DialGroupChangePendingNotification = function() {}; + var DialGroupChangePendingNotification = function () {}; /* * This class is responsible for handling a DIAL_GROUP_CHANGE_PENDING notification. @@ -705,7 +716,7 @@ export default (function() { * } * } */ - DialGroupChangePendingNotification.prototype.processResponse = function( + DialGroupChangePendingNotification.prototype.processResponse = function ( notification, ) { var model = UIModel.getInstance(); @@ -723,19 +734,22 @@ export default (function() { model.agentSettings.updateDGFromAdminUI = false; } - var formattedResponse = { - message: 'Dial Group Change Pending notification received.', - detail: - 'DialGroup switch for existing session pending until active call ends.', - agentId: utils.getText(notif, 'agent_id'), - dialGroupId: utils.getText(notif, 'dial_group_id'), - updateFromAdminUI: utils.getText(notif, 'update_from_adminui'), - }; + var formattedResponse = utils.buildDefaultResponse(notification); + formattedResponse.message = + 'Dial Group Change Pending notification received.'; + formattedResponse.detail = + 'DialGroup switch for existing session pending until active call ends.'; + formattedResponse.agentId = utils.getText(notif, 'agent_id'); + formattedResponse.dialGroupId = utils.getText(notif, 'dial_group_id'); + formattedResponse.updateFromAdminUI = utils.getText( + notif, + 'update_from_adminui', + ); return formattedResponse; }; - var DirectAgentTransferNotification = function() {}; + var DirectAgentTransferNotification = function () {}; /* * This class is responsible for handling a DIRECT-AGENT-ROUTE notification. @@ -759,7 +773,7 @@ export default (function() { * } * } */ - DirectAgentTransferNotification.prototype.processResponse = function( + DirectAgentTransferNotification.prototype.processResponse = function ( notification, ) { var formattedResponse = utils.buildDefaultResponse(notification); @@ -779,7 +793,7 @@ export default (function() { return formattedResponse; }; - var DropSessionNotification = function() {}; + var DropSessionNotification = function () {}; /* * This class handles the DROP-SESSION packet from IQ. It doesn't really do anything @@ -795,16 +809,22 @@ export default (function() { * } * } */ - DropSessionNotification.prototype.processResponse = function(notification) { + DropSessionNotification.prototype.processResponse = function ( + notification, + ) { var formattedResponse = utils.buildDefaultResponse(notification); var notif = notification.ui_notification; var sessionId = utils.getText(notif, 'session_id'); + var sessionUII = utils.getText(notif, 'uii'); var transfer = UIModel.getInstance().transferSessions[sessionId]; // Check to see if we just disconnected a transfer session // If so, we need to remove the session from our map if (transfer) { + console.log( + sessionUII + ' TRANSFER TO ' + transfer.destination + ' TERMINATED', + ); utils.logMessage( LOG_LEVELS.DEBUG, 'Transfer to ' + transfer.destination + ' has terminated', @@ -814,6 +834,7 @@ export default (function() { formattedResponse.transferEnd = transfer; } + console.log(sessionUII + ' END SESSION ' + sessionId); formattedResponse.message = 'Received DROP-SESSION Notification'; formattedResponse.status = 'OK'; formattedResponse.sessionId = utils.getText(notif, 'session_id'); @@ -822,7 +843,7 @@ export default (function() { return formattedResponse; }; - var EarlyUiiNotification = function() {}; + var EarlyUiiNotification = function () {}; /* * This class is responsible for handling "EARLY_UII" packets from IntelliQueue. @@ -837,7 +858,7 @@ export default (function() { * } * } */ - EarlyUiiNotification.prototype.processResponse = function(notification) { + EarlyUiiNotification.prototype.processResponse = function (notification) { var formattedResponse = utils.buildDefaultResponse(notification); var notif = notification.ui_notification; @@ -849,7 +870,7 @@ export default (function() { return formattedResponse; }; - var EndCallNotification = function(libInstance) { + var EndCallNotification = function (libInstance) { this.libInstance = libInstance; }; @@ -875,7 +896,7 @@ export default (function() { * } * } */ - EndCallNotification.prototype.processResponse = function(notification) { + EndCallNotification.prototype.processResponse = function (notification) { var model = UIModel.getInstance(); var notif = notification.ui_notification; model.endCallNotification = notification; @@ -928,24 +949,26 @@ export default (function() { model.pingIntervalId = setInterval(utils.sendPingCallMessage, 30000); } - var formattedResponse = { - message: 'End Call Notification Received.', - detail: '', - uii: utils.getText(notif, 'uii'), - sessionId: utils.getText(notif, 'session_id'), - agentId: utils.getText(notif, 'agent_id'), - callDts: utils.getText(notif, 'call_dts'), - duration: model.currentCall.duration, - termParty: model.currentCall.termParty, - termReason: model.currentCall.termReason, - recordingUrl: utils.getText(notif, 'recording_url'), - dispositionTimeout: utils.getText(notif, 'disposition_timeout'), - }; + var formattedResponse = utils.buildDefaultResponse(notification); + formattedResponse.message = 'End Call Notification Received.'; + formattedResponse.detail = ''; + formattedResponse.uii = utils.getText(notif, 'uii'); + formattedResponse.sessionId = utils.getText(notif, 'session_id'); + formattedResponse.agentId = utils.getText(notif, 'agent_id'); + formattedResponse.callDts = utils.getText(notif, 'call_dts'); + formattedResponse.duration = model.currentCall.duration; + formattedResponse.termParty = model.currentCall.termParty; + formattedResponse.termReason = model.currentCall.termReason; + formattedResponse.recordingUrl = utils.getText(notif, 'recording_url'); + formattedResponse.dispositionTimeout = utils.getText( + notif, + 'disposition_timeout', + ); return formattedResponse; }; - var GatesChangeNotification = function() {}; + var GatesChangeNotification = function () {}; /* * This class is responsible for handling a gates change notification @@ -959,7 +982,9 @@ export default (function() { * } * } */ - GatesChangeNotification.prototype.processResponse = function(notification) { + GatesChangeNotification.prototype.processResponse = function ( + notification, + ) { var model = UIModel.getInstance(); var notif = notification.ui_notification; var newAssignedGates = []; @@ -992,16 +1017,15 @@ export default (function() { JSON.stringify(newAssignedGates), ); - var formattedResponse = { - agentId: utils.getText(notif, 'agent_id'), - message: 'Gates Change notification received.', - queues: newAssignedGates, - }; + var formattedResponse = utils.buildDefaultResponse(notification); + formattedResponse.agentId = utils.getText(notif, 'agent_id'); + formattedResponse.message = 'Gates Change notification received.'; + formattedResponse.queues = newAssignedGates; return formattedResponse; }; - var GenericNotification = function() {}; + var GenericNotification = function () {}; /* * This class is responsible for handling a generic notification @@ -1017,7 +1041,7 @@ export default (function() { * } * } */ - GenericNotification.prototype.processResponse = function(notification) { + GenericNotification.prototype.processResponse = function (notification) { var formattedResponse = utils.buildDefaultResponse(notification); // add message and detail if present @@ -1029,7 +1053,7 @@ export default (function() { return formattedResponse; }; - var NewCallNotification = function() {}; + var NewCallNotification = function () {}; /* * This class processes a "NEW-CALL" packet received from Intelliqueue. It will determine @@ -1131,45 +1155,48 @@ export default (function() { * } * } */ - NewCallNotification.prototype.processResponse = function(notification) { + NewCallNotification.prototype.processResponse = function (notification) { var model = UIModel.getInstance(); var notif = notification.ui_notification; - // set up new call obj - var newCall = { - uii: utils.getText(notif, 'uii'), - agentId: utils.getText(notif, 'agent_id'), - dialDest: utils.getText(notif, 'dial_dest'), - queueDts: utils.getText(notif, 'queue_dts'), - queueTime: utils.getText(notif, 'queue_time'), - ani: utils.getText(notif, 'ani'), - dnis: utils.getText(notif, 'dnis'), - callType: utils.getText(notif, 'call_type'), - appUrl: utils.getText(notif, 'app_url'), - isMonitoring: utils.getText(notif, 'is_monitoring'), - allowHold: utils.getText(notif, 'allow_hold'), - allowTransfer: utils.getText(notif, 'allow_transfer'), - allowManualInternationalTransfer: utils.getText( - notif, - 'allow_manual_international_transfer', - ), - allowDirectAgentTransfer: utils.getText( - notif, - 'allow_direct_agent_transfer', - ), - allowHangup: utils.getText(notif, 'allow_hangup'), - allowRequeue: utils.getText(notif, 'allow_requeue'), - allowEndCallForEveryone: utils.getText( - notif, - 'allow_endcallforeveryone', - ), - scriptId: utils.getText(notif, 'script_id'), - scriptVersion: utils.getText(notif, 'script_version'), - surveyId: utils.getText(notif, 'survey_id'), - surveyPopType: utils.getText(notif, 'survey_pop_type'), - requeueType: utils.getText(notif, 'requeue_type'), - hangupOnDisposition: utils.getText(notif, 'hangup_on_disposition'), - }; + var newCall = utils.buildDefaultResponse(notification); + newCall.message = 'Received NEW-CALL notification'; + newCall.status = 'OK'; + newCall.uii = utils.getText(notif, 'uii'); + newCall.agentId = utils.getText(notif, 'agent_id'); + newCall.dialDest = utils.getText(notif, 'dial_dest'); + newCall.queueDts = utils.getText(notif, 'queue_dts'); + newCall.queueTime = utils.getText(notif, 'queue_time'); + newCall.ani = utils.getText(notif, 'ani'); + newCall.dnis = utils.getText(notif, 'dnis'); + newCall.callType = utils.getText(notif, 'call_type'); + newCall.appUrl = utils.getText(notif, 'app_url'); + newCall.isMonitoring = utils.getText(notif, 'is_monitoring'); + newCall.allowHold = utils.getText(notif, 'allow_hold'); + newCall.allowTransfer = utils.getText(notif, 'allow_transfer'); + newCall.allowManualInternationalTransfer = utils.getText( + notif, + 'allow_manual_international_transfer', + ); + newCall.allowDirectAgentTransfer = utils.getText( + notif, + 'allow_direct_agent_transfer', + ); + newCall.allowHangup = utils.getText(notif, 'allow_hangup'); + newCall.allowRequeue = utils.getText(notif, 'allow_requeue'); + newCall.allowEndCallForEveryone = utils.getText( + notif, + 'allow_endcallforeveryone', + ); + newCall.scriptId = utils.getText(notif, 'script_id'); + newCall.scriptVersion = utils.getText(notif, 'script_version'); + newCall.surveyId = utils.getText(notif, 'survey_id'); + newCall.surveyPopType = utils.getText(notif, 'survey_pop_type'); + newCall.requeueType = utils.getText(notif, 'requeue_type'); + newCall.hangupOnDisposition = utils.getText( + notif, + 'hangup_on_disposition', + ); if (newCall.isMonitoring) { newCall.monitoringType = utils.getText(notif, 'monitoring_type'); // FULL, COACHING, MONITOR @@ -1409,10 +1436,10 @@ export default (function() { return false; } - var PendingChatDispNotification = function() {}; + var PendingChatDispNotification = function () {}; /* - * This class is responsible for handling a generic notification + * This class is responsible for handling a pending chat disposition notification * * { * "ui_notification":{ @@ -1424,10 +1451,12 @@ export default (function() { * } * } */ - PendingChatDispNotification.prototype.processResponse = function( + PendingChatDispNotification.prototype.processResponse = function ( notification, ) { - var formattedResponse = {}; + var formattedResponse = utils.buildDefaultResponse(notification); + formattedResponse.message = 'Received PENDING-CHAT-DISP notification'; + formattedResponse.status = 'OK'; formattedResponse.agentId = utils.getText( notification.ui_notification, 'agent_id', @@ -1442,7 +1471,7 @@ export default (function() { return formattedResponse; }; - var PendingDispNotification = function() {}; + var PendingDispNotification = function () {}; /* * This class is responsible for handling a generic notification @@ -1456,8 +1485,12 @@ export default (function() { * } * } */ - PendingDispNotification.prototype.processResponse = function(notification) { - var formattedResponse = {}; + PendingDispNotification.prototype.processResponse = function ( + notification, + ) { + var formattedResponse = utils.buildDefaultResponse(notification); + formattedResponse.message = 'Received PENDING-DISP notification'; + formattedResponse.status = 'OK'; formattedResponse.agentId = utils.getText( notification.ui_notification, 'agent_id', @@ -1470,7 +1503,7 @@ export default (function() { return formattedResponse; }; - var PreviewLeadStateNotification = function() {}; + var PreviewLeadStateNotification = function () {}; /* * This class is responsible for processing the lead state packet @@ -1490,25 +1523,24 @@ export default (function() { * } * } */ - PreviewLeadStateNotification.prototype.processResponse = function( + PreviewLeadStateNotification.prototype.processResponse = function ( notification, ) { var notif = notification.ui_notification; UIModel.getInstance().agentSettings.onManualOutdial = true; - var response = { - callType: notif['@call_type'], - messageId: notif['@message_id'], - requestId: utils.getText(notif, 'request_id'), - leadState: utils.getText(notif, 'lead_state'), - callback: utils.getText(notif, 'callback'), - }; + var formattedResponse = utils.buildDefaultResponse(notification); + formattedResponse.callType = notif['@call_type']; + formattedResponse.messageId = notif['@message_id']; + formattedResponse.requestId = utils.getText(notif, 'request_id'); + formattedResponse.leadState = utils.getText(notif, 'lead_state'); + formattedResponse.callback = utils.getText(notif, 'callback'); - return response; + return formattedResponse; }; - var ReverseMatchNotification = function() {}; + var ReverseMatchNotification = function () {}; /* * This class is responsible for processing a REVERSE_MATCH packet from IQ. It @@ -1531,7 +1563,7 @@ export default (function() { * } * } */ - ReverseMatchNotification.prototype.processResponse = function( + ReverseMatchNotification.prototype.processResponse = function ( notification, ) { var notif = notification.ui_notification; @@ -1552,7 +1584,7 @@ export default (function() { return model.tokens; }; - var TcpaSafeLeadStateNotification = function() {}; + var TcpaSafeLeadStateNotification = function () {}; /* * This class is responsible for processing the lead state packet @@ -1571,23 +1603,21 @@ export default (function() { * } * } */ - TcpaSafeLeadStateNotification.prototype.processResponse = function( + TcpaSafeLeadStateNotification.prototype.processResponse = function ( notification, ) { var notif = notification.ui_notification; + var formattedResponse = utils.buildDefaultResponse(notification); + formattedResponse.callType = notif['@call_type']; + formattedResponse.messageId = notif['@message_id']; + formattedResponse.requestId = utils.getText(notif, 'request_id'); + formattedResponse.leadState = utils.getText(notif, 'lead_state'); + formattedResponse.callback = utils.getText(notif, 'callback'); - var response = { - callType: notif['@call_type'], - messageId: notif['@message_id'], - requestId: utils.getText(notif, 'request_id'), - leadState: utils.getText(notif, 'lead_state'), - callback: utils.getText(notif, 'callback'), - }; - - return response; + return formattedResponse; }; - var AckRequest = function(audioType, agentId, uii, monitorAgentId) { + var AckRequest = function (audioType, agentId, uii, monitorAgentId) { this.audioType = audioType || 'FULL'; this.agentId = agentId; this.uii = uii; @@ -1611,7 +1641,7 @@ export default (function() { * } * } */ - AckRequest.prototype.processResponse = function(response) { + AckRequest.prototype.processResponse = function (response) { var resp = response.ui_response; var formattedResponse = utils.buildDefaultResponse(response); @@ -1630,7 +1660,7 @@ export default (function() { return formattedResponse; }; - var AgentStateRequest = function(agentState, agentAuxState) { + var AgentStateRequest = function (agentState, agentAuxState) { if ( agentState.toUpperCase() == 'ON-BREAK' && UIModel.getInstance().onCall == true @@ -1643,7 +1673,7 @@ export default (function() { } }; - AgentStateRequest.prototype.formatJSON = function() { + AgentStateRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -1684,7 +1714,7 @@ export default (function() { * } * } */ - AgentStateRequest.prototype.processResponse = function(response) { + AgentStateRequest.prototype.processResponse = function (response) { var resp = response.ui_response; var status = utils.getText(resp, 'status'); var prevState = utils.getText(resp, 'prev_state'); @@ -1732,7 +1762,7 @@ export default (function() { return formattedResponse; }; - var AuthenticateRequest = function(config) { + var AuthenticateRequest = function (config) { this.username = config.username; this.password = config.password; this.platformId = config.platformId; @@ -1742,7 +1772,7 @@ export default (function() { this.authType = config.authType; }; - AuthenticateRequest.prototype.sendHttpRequest = function() { + AuthenticateRequest.prototype.sendHttpRequest = function () { UIModel.getInstance().authenticateRequest = this; switch (this.authType) { case AUTHENTICATE_TYPES.USERNAME_PASSWORD: @@ -1786,7 +1816,7 @@ export default (function() { * "mainAccountId": "99990000" * } */ - AuthenticateRequest.prototype.processResponse = function(response) { + AuthenticateRequest.prototype.processResponse = function (response) { var model = UIModel.getInstance(); model.authenticatePacket = response; // raw response packet model.authenticateRequest.accessToken = response.accessToken; // TODO - dlb - store in local storage @@ -1796,6 +1826,7 @@ export default (function() { model.authenticateRequest.socketPort = response.port; model.authenticateRequest.agents = response.agentDetails; model.authenticateRequest.platformId = response.platformId; + model.authenticateRequest.mainAccountId = response.mainAccountId; return model.authenticateRequest; }; @@ -1818,7 +1849,7 @@ export default (function() { baseUrl + path; new HttpService(baseUrl).httpPost(path, params).then( - function(response) { + function (response) { try { response = JSON.parse(response.response); @@ -1834,7 +1865,7 @@ export default (function() { utils.logMessage(LOG_LEVELS.WARN, errorMsg, err); } }, - function(err) { + function (err) { var errResponse = { type: 'Authenticate Error', message: errorMsg, @@ -1860,7 +1891,7 @@ export default (function() { UIModel.getInstance().authenticateRequest.engageAccessToken, ); new HttpService(baseUrl).httpGet(path, params).then( - function(response) { + function (response) { try { response = JSON.parse(response.response); @@ -1876,7 +1907,7 @@ export default (function() { utils.logMessage(LOG_LEVELS.WARN, errMsg, err); } }, - function(err) { + function (err) { utils.logMessage(LOG_LEVELS.WARN, errMsg, err); utils.fireCallback( UIModel.getInstance().libraryInstance, @@ -1889,7 +1920,7 @@ export default (function() { } } - var BargeInRequest = function(audioType, agentId, uii, monitorAgentId) { + var BargeInRequest = function (audioType, agentId, uii, monitorAgentId) { this.audioType = audioType || 'FULL'; this.agentId = agentId; this.uii = uii; @@ -1910,7 +1941,7 @@ export default (function() { * } * } */ - BargeInRequest.prototype.formatJSON = function() { + BargeInRequest.prototype.formatJSON = function () { var model = UIModel.getInstance(); var msg = { ui_request: { @@ -1951,7 +1982,7 @@ export default (function() { * } * } */ - BargeInRequest.prototype.processResponse = function(response) { + BargeInRequest.prototype.processResponse = function (response) { var resp = response.ui_response; var formattedResponse = utils.buildDefaultResponse(response); @@ -1972,14 +2003,14 @@ export default (function() { return formattedResponse; }; - var CallNotesRequest = function(notes) { + var CallNotesRequest = function (notes) { this.notes = notes; }; /* * This event is responsible for allowing an agent to tag a call with notes */ - CallNotesRequest.prototype.formatJSON = function() { + CallNotesRequest.prototype.formatJSON = function () { var model = UIModel.getInstance(); var msg = { ui_request: { @@ -2014,7 +2045,7 @@ export default (function() { * } * } */ - CallNotesRequest.prototype.processResponse = function(response) { + CallNotesRequest.prototype.processResponse = function (response) { var formattedResponse = utils.buildDefaultResponse(response); if (formattedResponse.status === 'OK') { @@ -2028,12 +2059,12 @@ export default (function() { return formattedResponse; }; - var CallbackCancelRequest = function(leadId, agentId) { + var CallbackCancelRequest = function (leadId, agentId) { this.agentId = agentId || UIModel.getInstance().agentSettings.agentId; this.leadId = leadId; }; - CallbackCancelRequest.prototype.formatJSON = function() { + CallbackCancelRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -2054,11 +2085,11 @@ export default (function() { // NOTE: cancel callback response sent as a generic notification message - var CallbacksPendingRequest = function(agentId) { + var CallbacksPendingRequest = function (agentId) { this.agentId = agentId || UIModel.getInstance().agentSettings.agentId; }; - CallbacksPendingRequest.prototype.formatJSON = function() { + CallbacksPendingRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -2111,7 +2142,7 @@ export default (function() { * } * } */ - CallbacksPendingRequest.prototype.processResponse = function(response) { + CallbacksPendingRequest.prototype.processResponse = function (response) { var leadsRaw = response.ui_response.lead; var leads = []; if (!Array.isArray(leadsRaw)) { @@ -2168,12 +2199,12 @@ export default (function() { * E.g. in the lead search form for manual passes * */ - var CampaignDispositionsRequest = function(campaignId) { + var CampaignDispositionsRequest = function (campaignId) { this.agentId = UIModel.getInstance().agentSettings.agentId; this.campaignId = campaignId; }; - CampaignDispositionsRequest.prototype.formatJSON = function() { + CampaignDispositionsRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -2210,7 +2241,9 @@ export default (function() { * } * } */ - CampaignDispositionsRequest.prototype.processResponse = function(response) { + CampaignDispositionsRequest.prototype.processResponse = function ( + response, + ) { var resp = response.ui_response; var model = UIModel.getInstance(); var dispositions = utils.processResponseCollection( @@ -2224,11 +2257,11 @@ export default (function() { return dispositions; }; - var ChatStateRequest = function(chatState) { + var ChatStateRequest = function (chatState) { this.chatState = (chatState && chatState.toUpperCase()) || ''; }; - ChatStateRequest.prototype.formatJSON = function() { + ChatStateRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -2263,7 +2296,7 @@ export default (function() { * } * } */ - ChatStateRequest.prototype.processResponse = function(response) { + ChatStateRequest.prototype.processResponse = function (response) { var resp = response.ui_response; var status = utils.getText(resp, 'status'); var prevState = utils.getText(resp, 'prev_state'); @@ -2295,14 +2328,14 @@ export default (function() { return formattedResponse; }; - var XferColdRequest = function(dialDest, callerId, sipHeaders, countryId) { + var XferColdRequest = function (dialDest, callerId, sipHeaders, countryId) { this.dialDest = dialDest; this.callerId = callerId || ''; this.sipHeaders = sipHeaders || []; this.countryId = countryId || ''; }; - XferColdRequest.prototype.formatJSON = function() { + XferColdRequest.prototype.formatJSON = function () { var fields = []; for (var i = 0; i < this.sipHeaders.length; i++) { var fieldObj = this.sipHeaders[i]; @@ -2357,7 +2390,7 @@ export default (function() { * } * } */ - XferColdRequest.prototype.processResponse = function(response) { + XferColdRequest.prototype.processResponse = function (response) { var resp = response.ui_response; var formattedResponse = utils.buildDefaultResponse(response); @@ -2380,13 +2413,13 @@ export default (function() { return formattedResponse; }; - var DirectAgentTransfer = function(targetAgentId, transferType, uii) { + var DirectAgentTransfer = function (targetAgentId, transferType, uii) { this.targetAgentId = targetAgentId; this.transferType = transferType; this.uii = uii || UIModel.getInstance().currentCall.uii; }; - DirectAgentTransfer.prototype.formatJSON = function() { + DirectAgentTransfer.prototype.formatJSON = function () { var model = UIModel.getInstance(); var msg = { ui_request: { @@ -2425,7 +2458,7 @@ export default (function() { * } * } */ - DirectAgentTransfer.prototype.processResponse = function(response) { + DirectAgentTransfer.prototype.processResponse = function (response) { var resp = response.ui_response; var formattedResponse = utils.buildDefaultResponse(response); formattedResponse.type = utils.getText(resp, 'type'); @@ -2443,9 +2476,9 @@ export default (function() { return formattedResponse; }; - var DirectAgentTransferList = function() {}; + var DirectAgentTransferList = function () {}; - DirectAgentTransferList.prototype.formatJSON = function() { + DirectAgentTransferList.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -2502,7 +2535,7 @@ export default (function() { * } * } */ - DirectAgentTransferList.prototype.processResponse = function(response) { + DirectAgentTransferList.prototype.processResponse = function (response) { var formattedResponse = utils.buildDefaultResponse(response); formattedResponse.agents = utils.processResponseCollection( response, @@ -2523,7 +2556,7 @@ export default (function() { return formattedResponse; }; - var DispositionRequest = function( + var DispositionRequest = function ( uii, dispId, notes, @@ -2592,7 +2625,7 @@ export default (function() { * } * } */ - DispositionRequest.prototype.formatJSON = function() { + DispositionRequest.prototype.formatJSON = function () { var model = UIModel.getInstance(); var msg = { ui_request: { @@ -2673,7 +2706,7 @@ export default (function() { return JSON.stringify(msg); }; - var DispositionManualPassRequest = function( + var DispositionManualPassRequest = function ( dispId, notes, callback, @@ -2714,7 +2747,7 @@ export default (function() { * } * } */ - DispositionManualPassRequest.prototype.formatJSON = function() { + DispositionManualPassRequest.prototype.formatJSON = function () { var model = UIModel.getInstance(); var msg = { ui_request: { @@ -2760,9 +2793,9 @@ export default (function() { return JSON.stringify(msg); }; - var ExtensionPresenceRequest = function() {}; + var ExtensionPresenceRequest = function () {}; - ExtensionPresenceRequest.prototype.getExtensionPresenceInfo = function( + ExtensionPresenceRequest.prototype.getExtensionPresenceInfo = function ( extensionIds, ) { UIModel.getInstance().ExtensionPresenceRequest = this; @@ -2771,7 +2804,7 @@ export default (function() { }); }; - ExtensionPresenceRequest.prototype.processResponse = function(response) { + ExtensionPresenceRequest.prototype.processResponse = function (response) { UIModel.getInstance().extensionPresenceResponse = response; return UIModel.getInstance().extensionPresenceResponse; }; @@ -2798,7 +2831,7 @@ export default (function() { path; new HttpService(baseUrl).httpGet(path, params).then( - function(response) { + function (response) { try { response = JSON.parse(response.response); var extensionPresenceResponse = UIModel.getInstance().extensionPresenceRequest.processResponse( @@ -2813,7 +2846,7 @@ export default (function() { utils.logMessage(LOG_LEVELS.WARN, errorMsg, err); } }, - function(err) { + function (err) { var errResponse = { type: 'Error while fetching extension presence response.', message: errorMsg, @@ -2830,12 +2863,12 @@ export default (function() { ); } - var HangupRequest = function(sessionId, resetPendingDisp) { + var HangupRequest = function (sessionId, resetPendingDisp) { this.sessionId = sessionId || null; this.resetPendingDisp = resetPendingDisp || false; }; - HangupRequest.prototype.formatJSON = function() { + HangupRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -2866,7 +2899,7 @@ export default (function() { return JSON.stringify(msg); }; - var HoldRequest = function(holdState, sessionId) { + var HoldRequest = function (holdState, sessionId) { this.holdState = holdState; this.sessionId = sessionId || '1'; }; @@ -2884,7 +2917,7 @@ export default (function() { * } * } */ - HoldRequest.prototype.formatJSON = function() { + HoldRequest.prototype.formatJSON = function () { var model = UIModel.getInstance(); var msg = { ui_request: { @@ -2929,7 +2962,7 @@ export default (function() { * } * } */ - HoldRequest.prototype.processResponse = function(response) { + HoldRequest.prototype.processResponse = function (response) { var resp = response.ui_response; var formattedResponse = utils.buildDefaultResponse(response); var currUII = ''; @@ -2988,7 +3021,7 @@ export default (function() { return formattedResponse; }; - var LeadHistoryRequest = function(leadId) { + var LeadHistoryRequest = function (leadId) { this.leadId = leadId; }; @@ -3003,7 +3036,7 @@ export default (function() { * } * } */ - LeadHistoryRequest.prototype.formatJSON = function() { + LeadHistoryRequest.prototype.formatJSON = function () { var model = UIModel.getInstance(); var msg = { ui_request: { @@ -3076,7 +3109,7 @@ export default (function() { * } * } */ - LeadHistoryRequest.prototype.processResponse = function(response) { + LeadHistoryRequest.prototype.processResponse = function (response) { var resp = response.ui_response; var histResponse = { leadId: resp['@lead_id'], @@ -3097,7 +3130,7 @@ export default (function() { return histResponse; }; - var LeadInsertRequest = function(dataObj) { + var LeadInsertRequest = function (dataObj) { // handle boolean value conversion if (dataObj.agent_reserved && dataObj.agent_reserved === true) { dataObj.agent_reserved = '1'; @@ -3146,7 +3179,7 @@ export default (function() { * } * } */ - LeadInsertRequest.prototype.formatJSON = function() { + LeadInsertRequest.prototype.formatJSON = function () { var model = UIModel.getInstance(); var msg = { ui_request: { @@ -3242,7 +3275,7 @@ export default (function() { * } * } */ - LeadInsertRequest.prototype.processResponse = function(response) { + LeadInsertRequest.prototype.processResponse = function (response) { var resp = response.ui_response; var formattedResponse = utils.buildDefaultResponse(response); @@ -3251,7 +3284,7 @@ export default (function() { return formattedResponse; }; - var LeadUpdateRequest = function(leadId, leadPhone, baggage) { + var LeadUpdateRequest = function (leadId, leadPhone, baggage) { this.leadId = leadId; this.leadPhone = leadPhone; this.baggage = baggage; @@ -3292,7 +3325,7 @@ export default (function() { * } * } */ - LeadUpdateRequest.prototype.formatJSON = function() { + LeadUpdateRequest.prototype.formatJSON = function () { // make sure required baggage fields are present this.baggage = _formatBaggage(this.baggage); var msg = { @@ -3330,7 +3363,7 @@ export default (function() { * } * } */ - LeadUpdateRequest.prototype.processResponse = function(response) { + LeadUpdateRequest.prototype.processResponse = function (response) { var resp = response.ui_response; var formattedResponse = utils.buildDefaultResponse(response); @@ -3339,7 +3372,7 @@ export default (function() { return formattedResponse; }; - var _formatBaggage = function(baggage) { + var _formatBaggage = function (baggage) { var bag = {}; bag.first_name = { '#text': baggage.first_name || '' }; bag.mid_name = { '#text': baggage.mid_name || '' }; @@ -3363,7 +3396,7 @@ export default (function() { return bag; }; - var LoginRequest = function( + var LoginRequest = function ( dialDest, queueIds, chatIds, @@ -3438,7 +3471,7 @@ export default (function() { } }; - LoginRequest.prototype.formatJSON = function() { + LoginRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -3543,7 +3576,7 @@ export default (function() { * } * } */ - LoginRequest.prototype.processResponse = function(response) { + LoginRequest.prototype.processResponse = function (response) { var resp = response.ui_response; var status = utils.getText(resp, 'status'); var detail = utils.getText(resp, 'detail'); @@ -3575,6 +3608,10 @@ export default (function() { resp, 'corporate_dir', ); + model.agentSettings.consultCall = utils.getText( + resp, + 'is_consultcall', + ); model.connectionSettings.isMultiSocket = utils.getText(resp, 'is_multisocket') === 'true'; @@ -3675,7 +3712,7 @@ export default (function() { //agent still is on call and there are transferSessions, verify no transferSession were drop var activeAgentUiSessions = Lib.getTransferSessions(); var activeAgentSessions = response.ui_response.active_call_sessions.call_session_id.map( - function(sessionObj) { + function (sessionObj) { return sessionObj['#text']; }, ); @@ -3819,9 +3856,9 @@ export default (function() { ); // copy array } - var LoginPhase1Request = function() {}; + var LoginPhase1Request = function () {}; - LoginPhase1Request.prototype.formatJSON = function() { + LoginPhase1Request.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -3974,7 +4011,7 @@ export default (function() { * } * } */ - LoginPhase1Request.prototype.processResponse = function(response) { + LoginPhase1Request.prototype.processResponse = function (response) { var resp = response.ui_response; var status = utils.getText(resp, 'status'); var model = UIModel.getInstance(); @@ -4049,7 +4086,9 @@ export default (function() { model.agentPermissions.allowCallControl = utils.getText(resp, 'allow_call_control') === '1'; model.agentPermissions.allowChat = - utils.getText(resp, 'allow_chat') === '1'; + utils.getText(resp, 'allow_chat') === '1'; //Agent permission + model.agentPermissions.enableChat = + utils.getText(resp, 'enable_chat') === '1'; //Account permission model.agentPermissions.showLeadHistory = utils.getText(resp, 'show_lead_history') === '1'; model.agentPermissions.allowManualOutboundGates = @@ -4307,19 +4346,19 @@ export default (function() { } // update the dnis array to just be a list - queue.dnis = rawQueue.dnis.map(function(d) { + queue.dnis = rawQueue.dnis.map(function (d) { return d['#text']; }); } } } - var LogoutRequest = function(agentId, message) { + var LogoutRequest = function (agentId, message) { this.agentId = agentId; this.message = message || ''; }; - LogoutRequest.prototype.formatJSON = function() { + LogoutRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -4338,14 +4377,14 @@ export default (function() { return JSON.stringify(msg); }; - LogoutRequest.prototype.processResponse = function(notification) { + LogoutRequest.prototype.processResponse = function (notification) { var formattedResponse = utils.buildDefaultResponse(notification); return formattedResponse; }; - var MultiSocketRequest = function() {}; + var MultiSocketRequest = function () {}; - MultiSocketRequest.prototype.formatJSON = function() { + MultiSocketRequest.prototype.formatJSON = function () { var model = UIModel.getInstance(); var msg = { ui_request: { @@ -4373,9 +4412,9 @@ export default (function() { return JSON.stringify(msg); }; - var OffhookInitRequest = function() {}; + var OffhookInitRequest = function () {}; - OffhookInitRequest.prototype.formatJSON = function() { + OffhookInitRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -4410,7 +4449,7 @@ export default (function() { * } * } */ - OffhookInitRequest.prototype.processResponse = function(response) { + OffhookInitRequest.prototype.processResponse = function (response) { var status = response.ui_response.status['#text']; var formattedResponse = utils.buildDefaultResponse(response); @@ -4437,9 +4476,9 @@ export default (function() { return formattedResponse; }; - var OffhookTermRequest = function() {}; + var OffhookTermRequest = function () {}; - OffhookTermRequest.prototype.formatJSON = function() { + OffhookTermRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -4469,7 +4508,7 @@ export default (function() { * } * } */ - OffhookTermRequest.prototype.processResponse = function(data) { + OffhookTermRequest.prototype.processResponse = function (data) { var notif = data.ui_notification; var monitoring = utils.getText(notif, 'monitoring') === '1'; var model = UIModel.getInstance(); @@ -4490,7 +4529,7 @@ export default (function() { return formattedResponse; }; - var OneToOneOutdialRequest = function( + var OneToOneOutdialRequest = function ( destination, callerId, ringTime, @@ -4504,7 +4543,7 @@ export default (function() { this.gateId = gateId || ''; }; - OneToOneOutdialRequest.prototype.formatJSON = function() { + OneToOneOutdialRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -4537,7 +4576,7 @@ export default (function() { return JSON.stringify(msg); }; - var OneToOneOutdialCancelRequest = function(uii) { + var OneToOneOutdialCancelRequest = function (uii) { this.uii = uii; }; @@ -4545,7 +4584,7 @@ export default (function() { * This class is responsible for creating a new packet to cancel * an in-progress outbound call. */ - OneToOneOutdialCancelRequest.prototype.formatJSON = function() { + OneToOneOutdialCancelRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -4566,7 +4605,7 @@ export default (function() { return JSON.stringify(msg); }; - var PauseRecordRequest = function(record) { + var PauseRecordRequest = function (record) { this.record = record; }; @@ -4583,7 +4622,7 @@ export default (function() { * } * } */ - PauseRecordRequest.prototype.formatJSON = function() { + PauseRecordRequest.prototype.formatJSON = function () { var model = UIModel.getInstance(); var pauseTime = '10'; if ( @@ -4632,7 +4671,7 @@ export default (function() { * } * } */ - PauseRecordRequest.prototype.processResponse = function(response) { + PauseRecordRequest.prototype.processResponse = function (response) { var resp = response.ui_response; var formattedResponse = utils.buildDefaultResponse(response); var currUII = ''; @@ -4677,9 +4716,9 @@ export default (function() { return formattedResponse; }; - var PingCallRequest = function() {}; + var PingCallRequest = function () {}; - PingCallRequest.prototype.formatJSON = function() { + PingCallRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -4698,7 +4737,7 @@ export default (function() { return JSON.stringify(msg); }; - var PreviewDialRequest = function( + var PreviewDialRequest = function ( action, searchFields, requestId, @@ -4717,7 +4756,7 @@ export default (function() { * {key: "number", value: "5555555555" * ]; */ - PreviewDialRequest.prototype.formatJSON = function() { + PreviewDialRequest.prototype.formatJSON = function () { var fields = {}; for (var i = 0; i < this.searchFields.length; i++) { var fieldObj = this.searchFields[i]; @@ -4781,7 +4820,7 @@ export default (function() { * } * } */ - PreviewDialRequest.prototype.processResponse = function(notification) { + PreviewDialRequest.prototype.processResponse = function (notification) { var notif = notification.dialer_request; var model = UIModel.getInstance(); var leads = utils.processResponseCollection( @@ -4810,7 +4849,7 @@ export default (function() { if (notifLead.extra_data) { // if this lead doesn't match the current lead, find it from the notification if (notifLead['@lead_id'] !== lead.leadId) { - notifLead = notif.destinations.lead.filter(function(destLead) { + notifLead = notif.destinations.lead.filter(function (destLead) { return destLead['@lead_id'] === lead.leadId; }); } @@ -4855,7 +4894,7 @@ export default (function() { return formattedResponse; }; - var RecordRequest = function(record) { + var RecordRequest = function (record) { this.record = record; }; @@ -4871,7 +4910,7 @@ export default (function() { * } * } */ - RecordRequest.prototype.formatJSON = function() { + RecordRequest.prototype.formatJSON = function () { var model = UIModel.getInstance(); var msg = { ui_request: { @@ -4909,7 +4948,7 @@ export default (function() { * } * } */ - RecordRequest.prototype.processResponse = function(response) { + RecordRequest.prototype.processResponse = function (response) { var resp = response.ui_response; var formattedResponse = utils.buildDefaultResponse(response); var currUII = ''; @@ -4953,13 +4992,13 @@ export default (function() { return formattedResponse; }; - var RequeueRequest = function(queueId, skillId, maintain) { + var RequeueRequest = function (queueId, skillId, maintain) { this.queueId = queueId; this.skillId = skillId; this.maintain = maintain; }; - RequeueRequest.prototype.formatJSON = function() { + RequeueRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -5004,7 +5043,7 @@ export default (function() { * } * } */ - RequeueRequest.prototype.processResponse = function(response) { + RequeueRequest.prototype.processResponse = function (response) { var resp = response.ui_response; var formattedResponse = utils.buildDefaultResponse(response); @@ -5023,7 +5062,7 @@ export default (function() { return formattedResponse; }; - var ScriptConfigRequest = function(scriptId, version) { + var ScriptConfigRequest = function (scriptId, version) { this.scriptId = scriptId; this.version = version || null; }; @@ -5031,7 +5070,7 @@ export default (function() { /* * This event is responsible for requesting a script object */ - ScriptConfigRequest.prototype.formatJSON = function() { + ScriptConfigRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -5067,7 +5106,7 @@ export default (function() { * } * } */ - ScriptConfigRequest.prototype.processResponse = function(response) { + ScriptConfigRequest.prototype.processResponse = function (response) { var resp = response.ui_response; var formattedResponse = utils.buildDefaultResponse(response); @@ -5088,7 +5127,7 @@ export default (function() { return formattedResponse; }; - var ScriptResultRequest = function(uii, scriptId, jsonResult) { + var ScriptResultRequest = function (uii, scriptId, jsonResult) { this.uii = uii; this.scriptId = scriptId; this.jsonResult = jsonResult; @@ -5097,7 +5136,7 @@ export default (function() { /* * This event is responsible for sending the script result object */ - ScriptResultRequest.prototype.formatJSON = function() { + ScriptResultRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -5124,14 +5163,14 @@ export default (function() { return JSON.stringify(msg); }; - var SearchDirectoryRequest = function() {}; + var SearchDirectoryRequest = function () {}; - SearchDirectoryRequest.prototype.searchDirectory = function(searchString) { + SearchDirectoryRequest.prototype.searchDirectory = function (searchString) { UIModel.getInstance().searchDirectoryRequest = this; _searchDirectory('rcdirectory/getRcCorporateDirectory', searchString); }; - SearchDirectoryRequest.prototype.processResponse = function(response) { + SearchDirectoryRequest.prototype.processResponse = function (response) { UIModel.getInstance().filteredDirectory = response; return UIModel.getInstance().filteredDirectory; }; @@ -5156,7 +5195,7 @@ export default (function() { params.queryParams['accountId'] = accountId; var errorMsg = 'Error on request to search Directory: ' + baseUrl + path; new HttpService(baseUrl).httpGet(path, params).then( - function(response) { + function (response) { try { response = JSON.parse(response.response); var searchDirResponse = UIModel.getInstance().searchDirectoryRequest.processResponse( @@ -5171,7 +5210,7 @@ export default (function() { utils.logMessage(LOG_LEVELS.WARN, errorMsg, err); } }, - function(err) { + function (err) { var errResponse = { type: 'Error retrieving directory list', message: errorMsg, @@ -5188,7 +5227,7 @@ export default (function() { ); } - var StatsRequest = function() {}; + var StatsRequest = function () {}; /* * { "ui_request": { @@ -5198,7 +5237,7 @@ export default (function() { * } * } */ - StatsRequest.prototype.formatJSON = function() { + StatsRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IS', @@ -5211,7 +5250,12 @@ export default (function() { return JSON.stringify(msg); }; - var TcpaSafeRequest = function(action, searchFields, requestId, leadPhone) { + var TcpaSafeRequest = function ( + action, + searchFields, + requestId, + leadPhone, + ) { this.agentId = UIModel.getInstance().agentSettings.agentId; this.searchFields = searchFields || []; this.requestId = requestId || ''; @@ -5225,7 +5269,7 @@ export default (function() { * {key: "number", value: "5555555555" * ]; */ - TcpaSafeRequest.prototype.formatJSON = function() { + TcpaSafeRequest.prototype.formatJSON = function () { var fields = {}; for (var i = 0; i < this.searchFields.length; i++) { var fieldObj = this.searchFields[i]; @@ -5290,7 +5334,7 @@ export default (function() { * } * */ - TcpaSafeRequest.prototype.processResponse = function(notification) { + TcpaSafeRequest.prototype.processResponse = function (notification) { var notif = notification.dialer_request; var model = UIModel.getInstance(); var leads = utils.processResponseCollection( @@ -5306,6 +5350,12 @@ export default (function() { lead.requestId = lead.requestKey; lead.ani = lead.destination; // add ani prop since used in new call packet & update lead + // In case of a single lead returned, the XML converter to JSON will add lead as an object and not an array + // + if (!Array.isArray(notif.destinations.lead)) { + notif.destinations.lead = [notif.destinations.lead]; + } + // parse extra data correctly try { var notifLead = notif.destinations.lead[l]; @@ -5313,7 +5363,7 @@ export default (function() { if (notifLead.extra_data) { // if this lead doesn't match the current lead, find it from the notification if (notifLead['@lead_id'] !== lead.leadId) { - notifLead = notif.destinations.lead.filter(function(destLead) { + notifLead = notif.destinations.lead.filter(function (destLead) { return destLead['@lead_id'] === lead.leadId; }); } @@ -5345,8 +5395,8 @@ export default (function() { // clear callbacks?? //model.callbacks = []; for (var l = 0; l < leads.length; l++) { - var lead = leads[l]; - model.callbacks.push(lead); + var callbackLead = leads[l]; + model.callbacks.push(callbackLead); } } else { model.outboundSettings.tcpaSafeLeads = leads; @@ -5355,7 +5405,7 @@ export default (function() { return formattedResponse; }; - var UpdateDialDestinationRequest = function(dialDest, isSoftphoneError) { + var UpdateDialDestinationRequest = function (dialDest, isSoftphoneError) { this.dialDest = dialDest; this.isSoftphoneError = isSoftphoneError || false; }; @@ -5372,7 +5422,7 @@ export default (function() { * } * } */ - UpdateDialDestinationRequest.prototype.formatJSON = function() { + UpdateDialDestinationRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -5398,14 +5448,14 @@ export default (function() { return JSON.stringify(msg); }; - var XferWarmRequest = function(dialDest, callerId, sipHeaders, countryId) { + var XferWarmRequest = function (dialDest, callerId, sipHeaders, countryId) { this.dialDest = dialDest; this.callerId = callerId || ''; this.sipHeaders = sipHeaders || []; this.countryId = countryId; }; - XferWarmRequest.prototype.formatJSON = function() { + XferWarmRequest.prototype.formatJSON = function () { var fields = []; for (var i = 0; i < this.sipHeaders.length; i++) { var fieldObj = this.sipHeaders[i]; @@ -5473,7 +5523,7 @@ export default (function() { * } * } */ - XferWarmRequest.prototype.processResponse = function(response) { + XferWarmRequest.prototype.processResponse = function (response) { var resp = response.ui_response; var formattedResponse = utils.buildDefaultResponse(response); @@ -5504,11 +5554,11 @@ export default (function() { return formattedResponse; }; - var XferWarmCancelRequest = function(dialDest) { + var XferWarmCancelRequest = function (dialDest) { this.dialDest = dialDest; }; - XferWarmCancelRequest.prototype.formatJSON = function() { + XferWarmCancelRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -5530,9 +5580,9 @@ export default (function() { return JSON.stringify(msg); }; - var WebRTCRequest = function() {}; + var WebRTCRequest = function () {}; - WebRTCRequest.prototype.getSipRegistrationInfo = function() { + WebRTCRequest.prototype.getSipRegistrationInfo = function () { UIModel.getInstance().WebRTCRequest = this; _getSipRegistrationInfo('sip/sipRegistrationInfo', { agentId: UIModel.getInstance().agentSettings.agentId, @@ -5567,7 +5617,7 @@ export default (function() { * ] * } * */ - WebRTCRequest.prototype.processResponse = function(response) { + WebRTCRequest.prototype.processResponse = function (response) { var softphoneSettings = UIModel.getInstance().softphoneSettings; softphoneSettings.sipInfo = response.sipInfo; @@ -5594,12 +5644,13 @@ export default (function() { 'Error on request get to sip registration info. URL: ' + baseUrl + path; new HttpService(baseUrl).httpGet(path, params).then( - function(response) { + function (response) { try { response = JSON.parse(response.response); var webRTCResponse = UIModel.getInstance().WebRTCRequest.processResponse( response, ); + model.softphoneSettings.lastSipFetchTime = Date.now(); utils.fireCallback( UIModel.getInstance().libraryInstance, CALLBACK_TYPES.WEBRTC_INFO, @@ -5609,7 +5660,7 @@ export default (function() { utils.logMessage(LOG_LEVELS.WARN, errorMsg, err); } }, - function(err) { + function (err) { var errResponse = { type: 'Error retrieving sip registration information', message: errorMsg, @@ -5625,32 +5676,32 @@ export default (function() { }, ); } - var ChatAgentEndRequest = function(agentId, uii) { + var ChatAgentEndRequest = function (agentId, uii) { this.uii = uii; this.agentId = agentId; }; /* - External Chat : - when agent submits a chat end request, send "CHAT-AGENT-END" request to IntelliQueue +External Chat : +when agent submits a chat end request, send "CHAT-AGENT-END" request to IntelliQueue - { - "ui_request" : { - "@destination" : "IQ", - "@type" : MESSAGE_TYPES.CHAT_AGENT_END, - "uii":{ - "#text":utils.toString(this.uii) - }, - "agent_id":{ - "#text":utils.toString(this.agentId) - } +{ + "ui_request" : { + "@destination" : "IQ", + "@type" : MESSAGE_TYPES.CHAT_AGENT_END, + "uii":{ + "#text":utils.toString(this.uii) + }, + "agent_id":{ + "#text":utils.toString(this.agentId) } } +} - */ +*/ - ChatAgentEndRequest.prototype.formatJSON = function() { + ChatAgentEndRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -5661,13 +5712,16 @@ export default (function() { agent_id: { '#text': utils.toString(this.agentId), }, + operation_id: { + '#text': utils.generateOpId(), + }, }, }; return JSON.stringify(msg); }; - - var ChatAliasRequest = function(alias) { + /** Deprecated */ + var ChatAliasRequest = function (alias) { this.alias = alias; }; @@ -5683,7 +5737,7 @@ export default (function() { * } * } */ - ChatAliasRequest.prototype.formatJSON = function() { + ChatAliasRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IS', @@ -5699,7 +5753,7 @@ export default (function() { return JSON.stringify(msg); }; - var ChatDispositionRequest = function( + var ChatDispositionRequest = function ( uii, agentId, dispositionId, @@ -5755,7 +5809,7 @@ export default (function() { * } * } */ - ChatDispositionRequest.prototype.formatJSON = function() { + ChatDispositionRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -5808,7 +5862,7 @@ export default (function() { return JSON.stringify(msg); }; - var ChatListRequest = function(agentId, monitorAgentId) { + var ChatListRequest = function (agentId, monitorAgentId) { this.agentId = agentId; this.monitorAgentId = monitorAgentId; }; @@ -5828,7 +5882,7 @@ export default (function() { * } */ - ChatListRequest.prototype.formatJSON = function() { + ChatListRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -5863,26 +5917,30 @@ export default (function() { * } */ - ChatListRequest.prototype.processResponse = function(response) { + ChatListRequest.prototype.processResponse = function (response) { var notif = response.ui_response; var model = UIModel.getInstance(); model.chatListResponse = response; + var formattedResponse = utils.buildDefaultResponse(response); - return { - message: 'Received CHAT-LIST notification', - status: 'OK', - messageId: notif['@message_id'], - agentId: utils.getText(notif, 'agent_id'), - monitorAgentId: utils.getText(notif, 'monitor_agent_id'), - chatList: utils.processResponseCollection( - notif, - 'chat_list', - 'active_chat', - ), - }; + formattedResponse.message = 'Received CHAT-LIST response'; + formattedResponse.status = 'OK'; + formattedResponse.messageId = notif['@message_id']; + formattedResponse.agentId = utils.getText(notif, 'agent_id'); + formattedResponse.monitorAgentId = utils.getText( + notif, + 'monitor_agent_id', + ); + formattedResponse.chatList = utils.processResponseCollection( + notif, + 'chat_list', + 'active_chat', + ); + + return formattedResponse; }; - var ChatMessageRequest = function(uii, agentId, message, whisper) { + var ChatMessageRequest = function (uii, agentId, message, whisper) { this.uii = uii; this.agentId = agentId; this.message = message; @@ -5905,7 +5963,7 @@ export default (function() { * } * } */ - ChatMessageRequest.prototype.formatJSON = function() { + ChatMessageRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -5949,25 +6007,29 @@ export default (function() { * } */ - ChatMessageRequest.prototype.processResponse = function(response) { + ChatMessageRequest.prototype.processResponse = function (response) { var resp = response.ui_notification; var dts = utils.getText(resp, 'dts').trim(); var dtsDate = new Date(dts.replace(' ', 'T')); - var formattedResponse = { - uii: utils.getText(resp, 'uii'), - accountId: utils.getText(resp, 'account_id'), - from: utils.getText(resp, 'from'), - type: utils.getText(resp, 'type'), - message: utils.getText(resp, 'message'), - whisper: utils.getText(resp, 'whisper'), - dequeueAgentId: utils.getText(resp, 'dequeue_agent_id'), - dts: dtsDate, - mediaLinks: utils.processResponseCollection( - resp, - 'media_links', - 'link', - ), - }; + + var formattedResponse = utils.buildDefaultResponse(response); + formattedResponse.status = 'OK'; + formattedResponse.uii = utils.getText(resp, 'uii'); + formattedResponse.accountId = utils.getText(resp, 'account_id'); + formattedResponse.from = utils.getText(resp, 'from'); + formattedResponse.type = utils.getText(resp, 'type'); + formattedResponse.message = utils.getText(resp, 'message'); + formattedResponse.whisper = utils.getText(resp, 'whisper'); + formattedResponse.dequeueAgentId = utils.getText( + resp, + 'dequeue_agent_id', + ); + formattedResponse.dts = dtsDate; + formattedResponse.mediaLinks = utils.processResponseCollection( + resp, + 'media_links', + 'link', + ); utils.logMessage( LOG_LEVELS.DEBUG, @@ -5978,7 +6040,7 @@ export default (function() { return formattedResponse; }; - var ChatPresentedResponseRequest = function( + var ChatPresentedResponseRequest = function ( uii, messageId, response, @@ -6003,12 +6065,11 @@ export default (function() { * "agent_id":{"#text":""}, * "response":{"#text":"ACCEPT|REJECT"}, * "response_reason":{"#text":""} + * "operation_id":{"#text":""} * } * } */ - ChatPresentedResponseRequest.prototype.formatJSON = function() { - //TODO-TASK add operation id to response (generated in SDK) - + ChatPresentedResponseRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -6027,13 +6088,16 @@ export default (function() { response_reason: { '#text': utils.toString(this.responseReason), }, + operation_id: { + '#text': utils.generateOpId(), + }, }, }; return JSON.stringify(msg); }; - var ChatRequeueRequest = function( + var ChatRequeueRequest = function ( uii, agentId, chatQueueId, @@ -6055,6 +6119,7 @@ export default (function() { * "@type":"CHAT-REQUEUE", * "@message_id":"", * "@response_to":"", + * "operation_id":{"#text":"1234567890asdfghjklzxcvb"}, // 24 character hex string, only set for Engage Digital Tasks * "uii":{"#text":""}, * "agent_id":{"#text":""}, * "chat_queue_id":{"#text":""}, @@ -6063,13 +6128,16 @@ export default (function() { * } * } */ - ChatRequeueRequest.prototype.formatJSON = function() { + ChatRequeueRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', '@type': MESSAGE_TYPES.CHAT_REQUEUE, '@message_id': utils.getMessageId(), '@response_to': '', + operation_id: { + '#text': utils.generateOpId(), + }, uii: { '#text': utils.toString(this.uii), }, @@ -6091,7 +6159,8 @@ export default (function() { return JSON.stringify(msg); }; - var ChatRoomRequest = function( + /** Deprecated */ + var ChatRoomRequest = function ( action, roomType, roomId, @@ -6136,7 +6205,7 @@ export default (function() { * } * */ - ChatRoomRequest.prototype.formatJSON = function() { + ChatRoomRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IS', @@ -6162,7 +6231,8 @@ export default (function() { return JSON.stringify(msg); }; - var ChatRoomStateRequest = function() {}; + /** Deprecated */ + var ChatRoomStateRequest = function () {}; /* * This class is responsible for processing CHAT-ROOM-STATE packets received @@ -6179,7 +6249,7 @@ export default (function() { * } * } */ - ChatRoomStateRequest.prototype.processResponse = function(response) { + ChatRoomStateRequest.prototype.processResponse = function (response) { var resp = response.ui_request; var formattedResponse = { roomId: utils.getText(resp, 'room_id'), @@ -6196,8 +6266,8 @@ export default (function() { ); return formattedResponse; }; - - var ChatSendRequest = function(roomId, message) { + /** Deprecated */ + var ChatSendRequest = function (roomId, message) { this.roomId = roomId; this.message = message; }; @@ -6207,7 +6277,7 @@ export default (function() { * it to IntelliServices. * * {"ui_request":{ - * "@destination":"IQ", + * "@destination":"IS", * "@message_id":"UI200809291036128", * "@response_to":"", * "@type":"CHAT", @@ -6216,7 +6286,7 @@ export default (function() { * } * } */ - ChatSendRequest.prototype.formatJSON = function() { + ChatSendRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IS', @@ -6268,7 +6338,7 @@ export default (function() { * } */ - ChatSendRequest.prototype.processResponse = function(response) { + ChatSendRequest.prototype.processResponse = function (response) { var resp = response.ui_request; var formattedResponse = { roomType: utils.getText(resp, 'room_type'), @@ -6289,7 +6359,7 @@ export default (function() { return formattedResponse; }; - var ChatTypingRequest = function(uii, message) { + var ChatTypingRequest = function (uii, message) { this.uii = uii; this.message = message; }; @@ -6309,7 +6379,7 @@ export default (function() { * } * } */ - ChatTypingRequest.prototype.formatJSON = function() { + ChatTypingRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -6331,7 +6401,7 @@ export default (function() { return JSON.stringify(msg); }; - var LeaveChatRequest = function(uii, agentId, sessionId) { + var LeaveChatRequest = function (uii, agentId, sessionId) { this.uii = uii; this.agentId = agentId; this.sessionId = sessionId; @@ -6352,7 +6422,7 @@ export default (function() { * } * } */ - LeaveChatRequest.prototype.formatJSON = function() { + LeaveChatRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -6374,7 +6444,7 @@ export default (function() { return JSON.stringify(msg); }; - var ChatManualSmsRequest = function( + var ChatManualSmsRequest = function ( agentId, chatQueueId, ani, @@ -6405,7 +6475,7 @@ export default (function() { * } * } */ - ChatManualSmsRequest.prototype.formatJSON = function() { + ChatManualSmsRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -6433,7 +6503,7 @@ export default (function() { return JSON.stringify(msg); }; - var MonitorChatRequest = function(monitorAgentId) { + var MonitorChatRequest = function (monitorAgentId) { this.monitorAgentId = monitorAgentId; }; @@ -6451,7 +6521,7 @@ export default (function() { * } * } */ - MonitorChatRequest.prototype.formatJSON = function() { + MonitorChatRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -6470,7 +6540,7 @@ export default (function() { return JSON.stringify(msg); }; - var StopMonitorChatRequest = function(monitorAgentId) { + var StopMonitorChatRequest = function (monitorAgentId) { this.monitorAgentId = monitorAgentId || ''; }; @@ -6488,7 +6558,7 @@ export default (function() { * } * } */ - StopMonitorChatRequest.prototype.formatJSON = function() { + StopMonitorChatRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IQ', @@ -6518,13 +6588,14 @@ export default (function() { * } * } */ - StopMonitorChatRequest.prototype.processResponse = function(data) { + StopMonitorChatRequest.prototype.processResponse = function (data) { var notif = data.ui_notification; return { monitoredAgentId: utils.getText(notif, 'monitored_agent_id') }; }; - var SupervisorListRequest = function() {}; + /** Deprecated */ + var SupervisorListRequest = function () {}; /* * This class is responsible for creating a packet to request a list of @@ -6532,7 +6603,7 @@ export default (function() { * agent can grab a list of supervisors and then select one for a private chat. * * {"ui_request":{ - * "@destination":"IQ", + * "@destination":"IS", * "@message_id":"UI200809291036128", * "@response_to":"", * "@type":"SUPERVISOR-LIST", @@ -6540,7 +6611,7 @@ export default (function() { * } * } */ - SupervisorListRequest.prototype.formatJSON = function() { + SupervisorListRequest.prototype.formatJSON = function () { var msg = { ui_request: { '@destination': 'IS', @@ -6575,7 +6646,7 @@ export default (function() { * } */ - SupervisorListRequest.prototype.processResponse = function(response) { + SupervisorListRequest.prototype.processResponse = function (response) { var model = UIModel.getInstance(); var tempList = utils.processResponseCollection( response, @@ -6604,7 +6675,7 @@ export default (function() { return model.supervisors; }; - var ChatClientReconnectNotification = function() {}; + var ChatClientReconnectNotification = function () {}; /* * External Chat: @@ -6624,21 +6695,22 @@ export default (function() { * } */ - ChatClientReconnectNotification.prototype.processResponse = function( + ChatClientReconnectNotification.prototype.processResponse = function ( notification, ) { var notif = notification.ui_notification; + var formattedResponse = utils.buildDefaultResponse(notification); - return { - message: 'Received CHAT-CLIENT-RECONNECT notification', - status: 'OK', - accountId: utils.getText(notif, 'account_id'), - uii: utils.getText(notif, 'uii'), - agentId: utils.getText(notif, 'agent_id'), - }; + formattedResponse.message = 'Received CHAT-CLIENT-RECONNECT notification'; + formattedResponse.status = 'OK'; + formattedResponse.accountId = utils.getText(notif, 'account_id'); + formattedResponse.uii = utils.getText(notif, 'uii'); + formattedResponse.agentId = utils.getText(notif, 'agent_id'); + + return formattedResponse; }; - var AddChatSessionNotification = function() {}; + var AddChatSessionNotification = function () {}; /* * This class is responsible for handling "ADD-CHAT-SESSION" packets from IntelliQueue. @@ -6656,7 +6728,7 @@ export default (function() { * } * } */ - AddChatSessionNotification.prototype.processResponse = function( + AddChatSessionNotification.prototype.processResponse = function ( notification, ) { var notif = notification.ui_notification; @@ -6677,7 +6749,7 @@ export default (function() { return formattedResponse; }; - var ChatActiveNotification = function() {}; + var ChatActiveNotification = function () {}; /* * External Chat: @@ -6696,19 +6768,55 @@ export default (function() { * } * } */ - ChatActiveNotification.prototype.processResponse = function(notification) { + ChatActiveNotification.prototype.processResponse = function (notification) { var notif = notification.ui_notification; + var formattedResponse = utils.buildDefaultResponse(notification, true); - return { - message: 'Received CHAT-ACTIVE notification', - status: 'OK', - accountId: utils.getText(notif, 'account_id'), - uii: utils.getText(notif, 'uii'), - isMonitoring: utils.getText(notif, 'is_monitoring'), - }; + formattedResponse.message = 'Received CHAT-ACTIVE notification'; + formattedResponse.status = 'OK'; + formattedResponse.accountId = utils.getText(notif, 'account_id'); + formattedResponse.uii = utils.getText(notif, 'uii'); + formattedResponse.isMonitoring = utils.getText(notif, 'is_monitoring'); + + return formattedResponse; + }; + + var ChatAgentAssignFailureNotification = function () {}; + + /* + * This class is responsible for handling "CHAT-AGENT-ASSIGN-FAILURE" packets from IntelliQueue. + * This is sent to the agent when the last session is disconnected from a chat. + * + * { + * "ui_notification":{ + * "@message_id":"IQ10012016081611595000289", + * "@type":"CHAT-AGENT-ASSIGN-FAILURE", + * "@destination":"IQ", + * "operation_id":{"#text":"1234567890asdfghjklzxcvb"}, // 24 character hex string, only set for Engage Digital Tasks + * "account_id":{"#text":"99999999"}, + * "uii":{"#text":"201608161200240139000000000120"}, + * "task_id": {"#text":""} + * } + * } + */ + + ChatAgentAssignFailureNotification.prototype.processResponse = function ( + notification, + ) { + var notif = notification.ui_notification; + + var agentAssignFailure = utils.buildDefaultResponse(notification, true); + agentAssignFailure.message = + 'Received CHAT-AGENT-ASSIGN-FAILURE notification'; + agentAssignFailure.status = 'OK'; + agentAssignFailure.uii = utils.getText(notif, 'uii'); + agentAssignFailure.taskId = utils.getText(notif, 'task_id'); + agentAssignFailure.accountId = utils.getText(notif, 'account_id'); + + return agentAssignFailure; }; - var ChatCancelledNotification = function() {}; + var ChatCancelledNotification = function () {}; /* * External Chat: @@ -6723,25 +6831,27 @@ export default (function() { * "@destination":"IQ", * "@response_to":"", * "account_id":{"#text":"99999999"}, + * "operation_id":{"#text":"1234567890asdfghjklzxcvb"}, * "uii":{"#text":"201608161200240139000000000120"} * } * } */ - ChatCancelledNotification.prototype.processResponse = function( + ChatCancelledNotification.prototype.processResponse = function ( notification, ) { var notif = notification.ui_notification; + var formattedResponse = utils.buildDefaultResponse(notification, true); - return { - message: 'Received CHAT-CANCELLED notification', - status: 'OK', - messageId: notif['@message_id'], - accountId: utils.getText(notif, 'account_id'), - uii: utils.getText(notif, 'uii'), - }; + formattedResponse.message = 'Received CHAT-CANCELLED notification'; + formattedResponse.status = 'OK'; + formattedResponse.messageId = notif['@message_id']; + formattedResponse.accountId = utils.getText(notif, 'account_id'); + formattedResponse.uii = utils.getText(notif, 'uii'); + + return formattedResponse; }; - var ChatInactiveNotification = function() {}; + var ChatInactiveNotification = function () {}; /* * External Chat: @@ -6754,6 +6864,7 @@ export default (function() { * "@type":"CHAT-INACTIVE", * "@destination":"IQ", * "@response_to":"", + * "operation_id":{"#text":"1234567890asdfghjklzxcvb"}, // 24 character hex string, only set for Engage Digital Tasks * "account_id":{"#text":"99999999"}, * "uii":{"#text":"201608161200240139000000000120"}, * "disposition_timeout":{"#text":"30"}, @@ -6761,22 +6872,29 @@ export default (function() { * } * } */ - ChatInactiveNotification.prototype.processResponse = function( + ChatInactiveNotification.prototype.processResponse = function ( notification, ) { var notif = notification.ui_notification; + var formattedResponse = utils.buildDefaultResponse(notification, true); - return { - message: 'Received CHAT-INACTIVE notification', - status: 'OK', - accountId: utils.getText(notif, 'account_id'), - uii: utils.getText(notif, 'uii'), - dispositionTimeout: utils.getText(notif, 'disposition_timeout'), - dequeueAgentId: utils.getText(notif, 'dequeue_agent_id'), - }; + formattedResponse.message = 'Received CHAT-INACTIVE notification'; + formattedResponse.status = 'OK'; + formattedResponse.accountId = utils.getText(notif, 'account_id'); + formattedResponse.uii = utils.getText(notif, 'uii'); + formattedResponse.dispositionTimeout = utils.getText( + notif, + 'disposition_timeout', + ); + formattedResponse.dequeueAgentId = utils.getText( + notif, + 'dequeue_agent_id', + ); + + return formattedResponse; }; - var ChatPresentedNotification = function() {}; + var ChatPresentedNotification = function () {}; /* * External Chat: @@ -6791,37 +6909,311 @@ export default (function() { * "@destination":"IQ", * "@response_to":"", * "task_id":{"#text":"123"}, // only applicable for Engage Digital tasks + * "operation_id:{"#text":"1234567890asdfghjklzxcvb"} // 24 character hex string, only set for Engage Digital Tasks * "chat_queue_id":{"#text":"3"}, * "chat_queue_name":{"#text":"Support Chat"}, * "account_id":{"#text":"99999999"}, * "uii":{"#text":"201608161200240139000000000120"}, - * "channel_type":{"#text":""}, - * "allow_accept":{"#text":"TRUE|FALSE"} + * "channel_type":{"#text":"email"}, + * "allow_accept":{"#text":"TRUE|FALSE"}, + * "ed_source_id":{"#text":"5ec6d788f3b1490008773a10"}, + * "ed_category_ids":{"#text": "5ec6b48ff3b14900087739eb,5ec6b4c2f3b14900087739f1,5f11dff7fcd72e0007c53b3e"}, + * "ed_language_id":{"#text":"en"}, + * "ed_author_screen_name":{"#text":"Joe Smith"}, + * "ed_created_at":{"#text":"Thu Aug 20 08:21:00 UTC 2020"}, * } * } */ - ChatPresentedNotification.prototype.processResponse = function( + ChatPresentedNotification.prototype.processResponse = function ( notification, ) { var notif = notification.ui_notification; + var edSourceId = utils.getText(notif, 'ed_source_id'); + var edLanguageId = utils.getText(notif, 'ed_language_id'); + var edCategoryIds = utils.getText(notif, 'ed_category_ids').split(','); + var taskId = utils.getText(notif, 'task_id'); + var model = UIModel.getInstance(); + var edSourceName = ''; + var edLanguage = ''; + var edCategories = []; + var processedResponse; + var instance = model.libraryInstance; + + if (taskId) { + // look in cached values to see if we have ED info already loaded + var mainAccountId = model.authenticateRequest.mainAccountId; + var cachedSourceList = + model.applicationSettings.edSources[mainAccountId]; + var cachedLanguageList = + model.applicationSettings.edLanguages[mainAccountId]; + var cachedCategoryList = + model.applicationSettings.edCategories[mainAccountId]; + + var sourceCache = _getEdValue(cachedSourceList, edSourceId); + var languageCache = _getEdValue(cachedLanguageList, edLanguageId); + var categoryCache = _getMultiEdValues( + cachedCategoryList, + edCategoryIds, + ); - // TODO-TASK add operation id + var edProxyPath = 'digital-api-proxy/evMainAccount/' + mainAccountId; - return { - message: 'Received CHAT-PRESENTED notification', - status: 'OK', - messageId: notif['@message_id'], - accountId: utils.getText(notif, 'account_id'), - uii: utils.getText(notif, 'uii'), - channelType: utils.getText(notif, 'channel_type'), - taskId: utils.getText(notif, 'task_id'), - chatQueueId: utils.getText(notif, 'chat_queue_id'), - chatQueueName: utils.getText(notif, 'chat_queue_name'), - allowAccept: utils.getText(notif, 'allow_accept'), - }; + if ( + !sourceCache.isCached || + !languageCache.isCached || + !categoryCache.isCached + ) { + // make sure access token is valid by just getting fresh token + utils.refreshAccessToken().then(function () { + var authToken = model.authenticateRequest.engageAccessToken; + var sourceErrorMsg = 'Error retrieving ED source name from ETR.'; + var languageErrorMsg = + 'Error retrieving ED language name from ETR.'; + var categoriesErrorMsg = 'Error retrieving ED categories from ETR.'; + var sourcePromise, languagePromise, categoriesPromise; + + if (sourceCache.isCached) { + edSourceName = sourceCache.name; + } else { + sourcePromise = _buildEtrHttpRequest( + edProxyPath + '/sources', + authToken, + ); + } + + if (languageCache.isCached) { + edLanguage = languageCache.name; + } else { + languagePromise = _buildEtrHttpRequest( + edProxyPath + '/languages', + authToken, + ); + } + + if (categoryCache.isCached) { + edCategories = categoryCache.names; + } else { + categoriesPromise = _buildEtrHttpRequest( + edProxyPath + '/categories', + authToken, + ); + } + + if (sourcePromise) { + sourcePromise.then( + function (sourceResponse) { + try { + var sourceList = JSON.parse(sourceResponse.response); + model.applicationSettings.edSources[ + mainAccountId + ] = sourceList; // store returned sources in memory for later use + edSourceName = _getEdValue(sourceList, edSourceId).name; + } catch (err) { + utils.logMessage(LOG_LEVELS.WARN, sourceErrorMsg, err); + } + }, + function (err) { + utils.logMessage(LOG_LEVELS.WARN, sourceErrorMsg, err); + }, + ); + } + + if (languagePromise) { + languagePromise.then( + function (languageResponse) { + try { + var languageList = JSON.parse(languageResponse.response); + model.applicationSettings.edLanguages[ + mainAccountId + ] = languageList; // store returned languages in memory for later use + edLanguage = _getEdValue(languageList, edLanguageId).name; + } catch (err) { + utils.logMessage(LOG_LEVELS.WARN, languageErrorMsg, err); + } + }, + function (err) { + utils.logMessage(LOG_LEVELS.WARN, languageErrorMsg, err); + }, + ); + } + + if (categoriesPromise) { + categoriesPromise.then( + function (categoryResponse) { + try { + var categoryList = JSON.parse(categoryResponse.response); + model.applicationSettings.edCategories[ + mainAccountId + ] = categoryList; // store returned categories in memory for later use + edCategories = _getMultiEdValues( + categoryList, + edCategoryIds, + ).names; + } catch (err) { + utils.logMessage(LOG_LEVELS.WARN, categoriesErrorMsg, err); + } + }, + function (err) { + utils.logMessage(LOG_LEVELS.WARN, categoriesErrorMsg, err); + }, + ); + } + + Promise.all([ + sourcePromise, + languagePromise, + categoriesPromise, + ]).then( + function () { + processedResponse = _buildPresentedResponse( + notification, + edSourceName, + edLanguage, + edCategories, + ); + utils.fireCallback( + instance, + CALLBACK_TYPES.CHAT_PRESENTED, + processedResponse, + ); + return processedResponse; + }, + function (err) { + utils.logMessage( + LOG_LEVELS.WARN, + 'Error resolving ETR API promises', + err, + ); + processedResponse = _buildPresentedResponse( + notification, + edSourceName, + edLanguage, + edCategories, + ); + utils.fireCallback( + instance, + CALLBACK_TYPES.CHAT_PRESENTED, + processedResponse, + ); + return processedResponse; + }, + ); + }); + } else { + // all values cached + edSourceName = sourceCache.name; + edLanguage = languageCache.name; + edCategories = categoryCache.names; + processedResponse = _buildPresentedResponse( + notification, + edSourceName, + edLanguage, + edCategories, + ); + utils.fireCallback( + instance, + CALLBACK_TYPES.CHAT_PRESENTED, + processedResponse, + ); + return processedResponse; // for testing + } + } else { + // not task + processedResponse = _buildPresentedResponse( + notification, + edSourceName, + edLanguage, + edCategories, + ); + utils.fireCallback( + instance, + CALLBACK_TYPES.CHAT_PRESENTED, + processedResponse, + ); + return processedResponse; // for testing + } }; - var ChatTypingNotification = function() {}; + function _buildEtrHttpRequest(path, authorizationToken) { + var model = UIModel.getInstance(); + var baseUrl = model.authHost + model.baseEtrUri; + var params = { + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + authorizationToken, + }, + }; + + return new HttpService(baseUrl).httpGet(path, params); + } + + function _getEdValue(list, id) { + var idx; + var isCached = false; + var name = ''; + + if (list) { + for (idx = 0; idx < list.length; idx++) { + if (list[idx].id === id) { + name = list[idx].name; + isCached = true; + } + } + } + + return { isCached: isCached, name: name }; + } + + function _getMultiEdValues(searchList, ids) { + var idIndex; + var edValue; + var isCached = false; + var names = []; + + if (searchList) { + for (idIndex = 0; idIndex < ids.length; idIndex++) { + edValue = _getEdValue(searchList, ids[idIndex]); + names.push(edValue.name); + if (edValue.isCached) { + isCached = true; + } + } + } + return { isCached: isCached, names: names }; + } + + function _buildPresentedResponse( + notification, + edSourceName, + edLanguage, + edCategories, + ) { + var notif = notification.ui_notification; + var formattedResponse = utils.buildDefaultResponse(notification, true); + + formattedResponse.message = 'Received CHAT-PRESENTED notification'; + formattedResponse.status = 'OK'; + formattedResponse.messageId = notif['@message_id']; + formattedResponse.accountId = utils.getText(notif, 'account_id'); + formattedResponse.uii = utils.getText(notif, 'uii'); + formattedResponse.channelType = utils.getText(notif, 'channel_type'); + formattedResponse.taskId = utils.getText(notif, 'task_id'); + formattedResponse.chatQueueId = utils.getText(notif, 'chat_queue_id'); + formattedResponse.chatQueueName = utils.getText(notif, 'chat_queue_name'); + formattedResponse.allowAccept = utils.getText(notif, 'allow_accept'); + formattedResponse.edAuthorScreenName = utils.getText( + notif, + 'ed_author_screen_name', + ); + formattedResponse.edCreatedAt = utils.getText(notif, 'ed_created_at'); + formattedResponse.edSourceName = edSourceName; + formattedResponse.edLanguage = edLanguage; + formattedResponse.edCategories = edCategories; + + return formattedResponse; + } + + var ChatTypingNotification = function () {}; /* * External Chat: @@ -6843,22 +7235,26 @@ export default (function() { * } * } */ - ChatTypingNotification.prototype.processResponse = function(notification) { + ChatTypingNotification.prototype.processResponse = function (notification) { var notif = notification.ui_notification; + var formattedResponse = utils.buildDefaultResponse(notification); - return { - message: 'Received CHAT-TYPING notification', - status: 'OK', - accountId: utils.getText(notif, 'account_id'), - uii: utils.getText(notif, 'uii'), - from: utils.getText(notif, 'from'), - type: utils.getText(notif, 'type'), - pendingMessage: utils.getText(notif, 'message'), - dequeueAgentId: utils.getText(notif, 'dequeue_agent_id'), - }; + formattedResponse.message = 'Received CHAT-TYPING notification'; + formattedResponse.status = 'OK'; + formattedResponse.accountId = utils.getText(notif, 'account_id'); + formattedResponse.uii = utils.getText(notif, 'uii'); + formattedResponse.from = utils.getText(notif, 'from'); + formattedResponse.type = utils.getText(notif, 'type'); + formattedResponse.pendingMessage = utils.getText(notif, 'message'); + formattedResponse.dequeueAgentId = utils.getText( + notif, + 'dequeue_agent_id', + ); + + return formattedResponse; }; - var LoadMediaNotification = function() {}; + var LoadMediaNotification = function () {}; /* * Engage Digital Chat: @@ -6897,28 +7293,28 @@ export default (function() { * } * } */ - LoadMediaNotification.prototype.processResponse = function(notification) { + LoadMediaNotification.prototype.processResponse = function (notification) { var notif = notification.ui_notification; var dts = utils.getText(notif, 'queue_dts'); dts = new Date(dts.replace(' ', 'T')); - // set up new task obj - var newTask = { - uii: utils.getText(notif, 'uii'), - taskId: utils.getText(notif, 'task_id'), - operationId: utils.getText(notif, 'operation_id'), - accountId: utils.getText(notif, 'account_id'), - sessionId: utils.getText(notif, 'session_id'), - agentId: utils.getText(notif, 'agent_id'), - queueDts: dts, - queueTime: utils.getText(notif, 'queue_time'), - chatQueueId: utils.getText(notif, 'chat_queue_id'), - chatQueueName: utils.getText(notif, 'chat_queue_name'), - chatRequeueType: utils.getText(notif, 'chat_requeue_type'), - channelType: utils.getText(notif, 'channel_type'), - idleTimeout: utils.getText(notif, 'idle_timeout'), - }; + var newTask = utils.buildDefaultResponse(notification, true); + + newTask.message = 'Received LOAD-MEDIA notification'; + newTask.status = 'OK'; + newTask.uii = utils.getText(notif, 'uii'); + newTask.taskId = utils.getText(notif, 'task_id'); + newTask.accountId = utils.getText(notif, 'account_id'); + newTask.sessionId = utils.getText(notif, 'session_id'); + newTask.agentId = utils.getText(notif, 'agent_id'); + newTask.queueDts = dts; + newTask.queueTime = utils.getText(notif, 'queue_time'); + newTask.chatQueueId = utils.getText(notif, 'chat_queue_id'); + newTask.chatQueueName = utils.getText(notif, 'chat_queue_name'); + newTask.chatRequeueType = utils.getText(notif, 'chat_requeue_type'); + newTask.channelType = utils.getText(notif, 'channel_type'); + newTask.idleTimeout = utils.getText(notif, 'idle_timeout'); newTask.requeueShortcuts = utils.processResponseCollection( notification, @@ -6987,7 +7383,7 @@ export default (function() { return tokens; } - var NewChatNotification = function() {}; + var NewChatNotification = function () {}; /* * External Chat: @@ -7040,35 +7436,35 @@ export default (function() { * } * } */ - NewChatNotification.prototype.processResponse = function(notification) { + NewChatNotification.prototype.processResponse = function (notification) { var notif = notification.ui_notification; var dts = utils.getText(notif, 'queue_dts'); dts = new Date(dts.replace(' ', 'T')); - // set up new chat obj - var newChat = { - uii: utils.getText(notif, 'uii'), - accountId: utils.getText(notif, 'account_id'), - sessionId: utils.getText(notif, 'session_id'), - agentId: utils.getText(notif, 'agent_id'), - queueDts: dts, - queueTime: utils.getText(notif, 'queue_time'), - chatQueueId: utils.getText(notif, 'chat_queue_id'), - chatQueueName: utils.getText(notif, 'chat_queue_name'), - chatRequeueType: utils.getText(notif, 'chat_requeue_type'), - appUrl: utils.getText(notif, 'app_url'), - channelType: utils.getText(notif, 'channel_type'), - ani: utils.getText(notif, 'ani'), - dnis: utils.getText(notif, 'dnis'), - surveyPopType: utils.getText(notif, 'survey_pop_type'), - scriptId: utils.getText(notif, 'script_id'), - scriptVersion: utils.getText(notif, 'script_version'), - idleTimeout: utils.getText(notif, 'idle_timeout'), - isMonitoring: utils.getText(notif, 'is_monitoring'), - monitoredAgentId: utils.getText(notif, 'monitored_agent_id'), - preChatData: utils.getText(notif, 'json_baggage'), - }; + var newChat = utils.buildDefaultResponse(notification); + newChat.message = 'Received NEW-CHAT notification'; + newChat.status = 'OK'; + newChat.uii = utils.getText(notif, 'uii'); + newChat.accountId = utils.getText(notif, 'account_id'); + newChat.sessionId = utils.getText(notif, 'session_id'); + newChat.agentId = utils.getText(notif, 'agent_id'); + newChat.queueDts = dts; + newChat.queueTime = utils.getText(notif, 'queue_time'); + newChat.chatQueueId = utils.getText(notif, 'chat_queue_id'); + newChat.chatQueueName = utils.getText(notif, 'chat_queue_name'); + newChat.chatRequeueType = utils.getText(notif, 'chat_requeue_type'); + newChat.appUrl = utils.getText(notif, 'app_url'); + newChat.channelType = utils.getText(notif, 'channel_type'); + newChat.ani = utils.getText(notif, 'ani'); + newChat.dnis = utils.getText(notif, 'dnis'); + newChat.surveyPopType = utils.getText(notif, 'survey_pop_type'); + newChat.scriptId = utils.getText(notif, 'script_id'); + newChat.scriptVersion = utils.getText(notif, 'script_version'); + newChat.idleTimeout = utils.getText(notif, 'idle_timeout'); + newChat.isMonitoring = utils.getText(notif, 'is_monitoring'); + newChat.monitoredAgentId = utils.getText(notif, 'monitored_agent_id'); + newChat.preChatData = utils.getText(notif, 'json_baggage'); newChat.requeueShortcuts = utils.processResponseCollection( notification, @@ -7186,46 +7582,46 @@ export default (function() { return tokens; } - var AgentStats = function() {}; + var AgentStats = function () {}; /* - * This class is responsible for handling an Agent Stats packet rec'd from IntelliServices. - * It will save a copy of it in the UIModel. Could be a single agent or array of agents. - * - {"ui_stats":{ - "@type":"AGENT", - "agent":{ - "@alt":"INBOUND", - "@atype":"AGENT", - "@avgtt":"00.0", - "@calls":"0", - "@da":"0", - "@droute":"6789050673", - "@f":"John", - "@gdesc":"", - "@gname":"", - "@id":"1856", - "@l":"Doe", - "@ldur":"6", - "@ltype":"INBOUND", - "@oh":"0", - "@pd":"0", - "@pdt":"0", - "@pres":"0", - "@rna":"0", - "@sdur":"6", - "@sp":"", - "@state":"AVAILABLE", - "@ttt":"0", - "@u":"jdoe", - "@uii":"", - "@util":"0.00", - "@call_duration:0" - } - } - } - */ - AgentStats.prototype.processResponse = function(stats) { + * This class is responsible for handling an Agent Stats packet rec'd from IntelliServices. + * It will save a copy of it in the UIModel. Could be a single agent or array of agents. + * + {"ui_stats":{ + "@type":"AGENT", + "agent":{ + "@alt":"INBOUND", + "@atype":"AGENT", + "@avgtt":"00.0", + "@calls":"0", + "@da":"0", + "@droute":"6789050673", + "@f":"John", + "@gdesc":"", + "@gname":"", + "@id":"1856", + "@l":"Doe", + "@ldur":"6", + "@ltype":"INBOUND", + "@oh":"0", + "@pd":"0", + "@pdt":"0", + "@pres":"0", + "@rna":"0", + "@sdur":"6", + "@sp":"", + "@state":"AVAILABLE", + "@ttt":"0", + "@u":"jdoe", + "@uii":"", + "@util":"0.00", + "@call_duration:0" + } + } + } + */ + AgentStats.prototype.processResponse = function (stats) { var resp = stats.ui_stats.agent; var agentStats = []; @@ -7270,7 +7666,7 @@ export default (function() { return agentStats; }; - var AgentDailyStats = function() {}; + var AgentDailyStats = function () {}; /* * This class is responsible for handling an Agent Daily Stats packet rec'd from IntelliServices. @@ -7291,7 +7687,7 @@ export default (function() { * } * } */ - AgentDailyStats.prototype.processResponse = function(stats) { + AgentDailyStats.prototype.processResponse = function (stats) { var model = UIModel.getInstance().agentDailyStats; var resp = stats.ui_stats; @@ -7318,7 +7714,7 @@ export default (function() { return model; }; - var CampaignStats = function() {}; + var CampaignStats = function () {}; /* * This class is responsible for handling a Campaign Stats packet rec'd from IntelliServices. @@ -7357,7 +7753,7 @@ export default (function() { * } * } */ - CampaignStats.prototype.processResponse = function(stats) { + CampaignStats.prototype.processResponse = function (stats) { var resp = stats.ui_stats; var totals = utils.processResponseCollection( stats, @@ -7411,7 +7807,7 @@ export default (function() { return campaignStats; }; - var ChatQueueStats = function() {}; + var ChatQueueStats = function () {}; /* * This class is responsible for handling an Chat Stats packet rec'd from IntelliServices. @@ -7478,7 +7874,7 @@ export default (function() { * } *} */ - ChatQueueStats.prototype.processResponse = function(stats) { + ChatQueueStats.prototype.processResponse = function (stats) { var resp = stats.ui_stats; var totals = utils.processResponseCollection( stats, @@ -7502,7 +7898,7 @@ export default (function() { return chatQueueStats; }; - var QueueStats = function() {}; + var QueueStats = function () {}; /* * This class is responsible for handling an Queue Stats packet rec'd from IntelliServices. @@ -7539,7 +7935,7 @@ export default (function() { * } * } */ - QueueStats.prototype.processResponse = function(stats) { + QueueStats.prototype.processResponse = function (stats) { var resp = stats.ui_stats; var totals = utils.processResponseCollection( stats, @@ -7600,7 +7996,7 @@ export default (function() { return queueStats; }; - var UIModel = (function() { + var UIModel = (function () { var instance; function init() { @@ -7630,6 +8026,7 @@ export default (function() { socketProtocol: 'wss://', // default to secure socket unless local test flag passed in on initialization baseAuthUri: '/api/auth/', // the path to engage-auth e.g.: http://localhost:81/api/auth/ or window.location.origin + "/api/auth/", baseApiUri: '/api/v1/', // the path to engage-api + baseEtrUri: '/platform/api/ed-task-routing/v1/', // the path to ETR task routing endpoints // internal chat requests chatAliasRequest: null, @@ -7643,6 +8040,7 @@ export default (function() { dataStore: new LocalStorageService('agentSDK'), // external chat requests/notifications + chatCancelledNotification: new ChatCancelledNotification(), chatActiveNotification: new ChatActiveNotification(), chatInactiveNotification: new ChatInactiveNotification(), chatDispositionRequest: null, @@ -7654,6 +8052,7 @@ export default (function() { chatTypingRequest: null, newChatNotification: new NewChatNotification(), loadMediaNotification: new LoadMediaNotification(), + chatAgentAssignFailureNotification: new ChatAgentAssignFailureNotification(), chatClientReconnectNotification: new ChatClientReconnectNotification(), // request instances @@ -7737,6 +8136,9 @@ export default (function() { isSso: false, // Passed in on phase 1 login response, if agent signed in through RC single sign-on path set to true dialDestType: '', // What type of phone are we setting up: e.g. "RC_SOFTPHONE", "LEGACY_SOFTPHONE", "RC_PHONE" (for RC office ext)// Comes in at the account-level - will get set to true if this interface should be in tcpa-safe-mode only. allowMultiSocket: false, // Determines whether agent can open a new socket under the same login + edSources: {}, // Cache for ED sources, loaded when first task received. Object indexed by mainAccountId, then array of all sources + edLanguages: {}, // Cache for ED language, loaded when first task received. Object indexed by mainAccountId, then array of all languages + edCategories: {}, // Cache for ED categories, loaded when first task received. Object indexed by mainAccountId, then array of all categories }, // stat objects @@ -7757,6 +8159,7 @@ export default (function() { callerIds: [], callState: null, // display the current state of the call corporateDirectory: false, + consultCall: false, //consult call allow currentState: 'OFFLINE', // Agent system/base state currentStateLabel: '', // Agent aux state label defaultLoginDest: '', @@ -7797,6 +8200,7 @@ export default (function() { allowBlended: true, // Controls whether or not the agent can log into both inbound queues and an outbound dialgroup allowCallControl: true, // Set from the the login response packet allowChat: false, // Controls whether or not the agent has the option to open the Chat Queue Manager + enableChat: false, // Controls whether or not the agent has legacy chat and SMS access on the account level allowCrossQueueRequeue: false, // Controls whether or not the agent can requeue to a different queue group allowInbound: true, // Controls whether or not the agent can log into an inbound queue allowLeadInserts: false, // Controls whether or not the agents can insert leads @@ -7874,7 +8278,8 @@ export default (function() { sipPassword: '', // password for sip softphone registration sipDialDest: '', // dialDest used for softphone connection attemptingSoftphoneReconnect: false, // set to true when attempting to rotate softphone registrar and reconnect - //manualSoftphoneReconnect: false // set to true when agent triggered registrar rotation + //manualSoftphoneReconnect: false, // set to true when agent triggered registrar rotation + lastSipFetchTime: 0, //set current time when sip registration info fetched. }, // Filtered Directory @@ -7886,7 +8291,7 @@ export default (function() { extensionPresenceResponse: [], // Public methods - incrementTotalCalls: function() { + incrementTotalCalls: function () { this.agentSettings.totalCalls = this.agentSettings.totalCalls + 1; }, }; @@ -7895,26 +8300,26 @@ export default (function() { return { // Get the Singleton instance if one exists // or create one if it doesn't - getInstance: function() { + getInstance: function () { if (!instance) { instance = init(); } return instance; }, - resetInstance: function() { + resetInstance: function () { instance = null; }, }; })(); - var LocalStorageService = function(name) { + var LocalStorageService = function (name) { if (!window.localStorage) { console.log('Browser does not support HTML5 Web Storage'); } this.prefix = name + ':'; }; - LocalStorageService.prototype.save = function(key, value) { + LocalStorageService.prototype.save = function (key, value) { // TODO: that window.localStorage is add by ringcentral-integration if (!window.localStorage || !key || !value) { console.log('Missing parameters key or value on add'); @@ -7922,7 +8327,7 @@ export default (function() { } window.localStorage.setItem(this.prefix + key, JSON.stringify(value)); }; - LocalStorageService.prototype.get = function(key) { + LocalStorageService.prototype.get = function (key) { // TODO: that window.localStorage is add by ringcentral-integration if ( !window.localStorage || @@ -7935,7 +8340,7 @@ export default (function() { return window.localStorage.getItem(this.prefix + key); }; - LocalStorageService.prototype.remove = function(key) { + LocalStorageService.prototype.remove = function (key) { // TODO: that window.localStorage is add by ringcentral-integration if ( !window.localStorage || @@ -7960,7 +8365,7 @@ export default (function() { // Registers the request / response channels // - init: function() { + init: function () { if (this.requestChannel != null) { return; } @@ -7971,7 +8376,7 @@ export default (function() { // Listen for requests coming from the requestChannel // - this.requestChannel.onmessage = function(e) { + this.requestChannel.onmessage = function (e) { var type = e.data.type; var messageId = e.data.messageId; @@ -7984,7 +8389,7 @@ export default (function() { // Listen for requests coming from the responseChannel // - this.responseChannel.onmessage = function(e) { + this.responseChannel.onmessage = function (e) { var type = e.data.type; switch (type) { @@ -7994,7 +8399,7 @@ export default (function() { }; }, - destroy: function() { + destroy: function () { if (this.requestChannel == null) { return; } @@ -8018,7 +8423,7 @@ export default (function() { // a response is retrieved, it will be handled in the "processCurrentCall" method, and returned // back to the original callback function // - requestCurrentCall: function(fn) { + requestCurrentCall: function (fn) { this.currentCallMessageId = Math.random(); this.currentCallRequestCallback = fn; @@ -8030,7 +8435,7 @@ export default (function() { // Any instance that has knowledge of the current call can respond to the request // - _sendCurrentCall: function(messageId) { + _sendCurrentCall: function (messageId) { if (UIModel.getInstance().currentCall != null) { var obj = { type: 'currentCall', @@ -8045,7 +8450,7 @@ export default (function() { // When a current call response is received, every tab will try to process it. Only the original // requestor will be able to process it successfully. // - _processCurrentCallResponse: function(data, messageId) { + _processCurrentCallResponse: function (data, messageId) { if (this.currentCallMessageId === messageId) { // Set the current call model UIModel.getInstance().currentCall = data; @@ -8076,8 +8481,8 @@ export default (function() { * @param {Object} config - Object describing different properties of the request. * @returns {Promise} Promise that represents status of the request. Resolves if server responds with 200 status code, and is rejected otherwise. */ - this.httpGet = function(path, config) { - return new Promise(function(resolve, reject) { + this.httpGet = function (path, config) { + return new Promise(function (resolve, reject) { var req = new that.XMLHttpRequest(); var queryParams = ''; if (config.queryParams) { @@ -8097,8 +8502,8 @@ export default (function() { * @param {Object} config - Object describing different properties of the request. * @returns {Promise} Promise that represents status of the request. Resolves if server responds with 200 status code, and is rejected otherwise. */ - this.httpPost = function(path, config) { - return new Promise(function(resolve, reject) { + this.httpPost = function (path, config) { + return new Promise(function (resolve, reject) { var req = new that.XMLHttpRequest(); var queryParams = ''; if (config.queryParams) { @@ -8150,13 +8555,13 @@ export default (function() { * @param {XMLHttpRequest} req - Instance of XMLHttpRequest that will be configured. */ function _addCompletionListeners(resolve, reject, req) { - req.addEventListener('error', function(e) { + req.addEventListener('error', function (e) { reject(e); }); - req.addEventListener('timeout', function() { + req.addEventListener('timeout', function () { reject(new Error('request timeout')); }); - req.addEventListener('load', function() { + req.addEventListener('load', function () { if (this.status !== 200) { reject({ status: this.status, @@ -8197,7 +8602,7 @@ export default (function() { } return Object.keys(params) - .map(function(key) { + .map(function (key) { return ( encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) ); @@ -8212,11 +8617,13 @@ export default (function() { var that = this; - this.setupAddSessionCallback = function() { + this.setupAddSessionCallback = function () { var sessionUii = utils.getText(data.ui_notification, 'uii'), sessionId = utils.getText(data.ui_notification, 'session_id'), call = UIModel.getInstance().currentCall; + console.log(sessionUii + ' ADD SESSION ' + sessionId); + if (call.uii === sessionUii) { // we already have a new call packet for this session _delayedAddSessionCallback(that.instance, that.data); @@ -8230,10 +8637,12 @@ export default (function() { } }; - this.processSessionsForCall = function() { + this.processSessionsForCall = function () { var uii = UIModel.getInstance().currentCall.uii, delayedSessions = UIModel.getInstance().pendingNewCallSessions[uii]; + console.log(uii + ' NEW CALL'); + if (delayedSessions && Object.keys(delayedSessions).length > 0) { // we have delayed sessions to process for (var sessionId in delayedSessions) { @@ -8262,7 +8671,7 @@ export default (function() { } var utils = { - logMessage: function(logLevel, message, data) { + logMessage: function (logLevel, message, data) { var instance = UIModel.getInstance().libraryInstance; if (instance._db) { try { @@ -8285,7 +8694,7 @@ export default (function() { } }, - sendMessage: function(instance, msg) { + sendMessage: function (instance, msg) { var msgObj = JSON.parse(msg); if (instance.socket && instance.socket.readyState === 1) { @@ -8318,7 +8727,7 @@ export default (function() { } }, - processResponse: function(instance, response) { + processResponse: function (instance, response) { var type = response.ui_response['@type']; var messageId = response.ui_response['@message_id']; var dest = @@ -8562,7 +8971,7 @@ export default (function() { } }, - processNotification: function(instance, data) { + processNotification: function (instance, data) { var type = data.ui_notification['@type']; var messageId = data.ui_notification['@message_id']; var dest = messageId === '' ? 'IS' : messageId.slice(0, 2); @@ -8775,12 +9184,7 @@ export default (function() { break; case MESSAGE_TYPES.CHAT_PRESENTED: var presentedNotif = new ChatPresentedNotification(); - var presentedResponse = presentedNotif.processResponse(data); - utils.fireCallback( - instance, - CALLBACK_TYPES.CHAT_PRESENTED, - presentedResponse, - ); + presentedNotif.processResponse(data); break; case MESSAGE_TYPES.CHAT_TYPING: var typingNotif = new ChatTypingNotification(); @@ -8861,10 +9265,21 @@ export default (function() { var loadMediaResp = loadMediaNotif.processResponse(data); utils.fireCallback( instance, - CALLBACK_TYPES.CHAT_LOAD_MEDIA_NOTIF, + CALLBACK_TYPES.CHAT_LOAD_MEDIA, loadMediaResp, ); break; + case MESSAGE_TYPES.CHAT_AGENT_ASSIGN_FAILURE: + var chatAgentAssignFailureNotif = new ChatAgentAssignFailureNotification(); + var chatAgentAssignFailureResp = chatAgentAssignFailureNotif.processResponse( + data, + ); + utils.fireCallback( + instance, + CALLBACK_TYPES.CHAT_AGENT_ASSIGN_FAILURE, + chatAgentAssignFailureResp, + ); + break; case MESSAGE_TYPES.LOGOUT: var logoutNotification = new LogoutRequest(); var logoutNotifResponse = logoutNotification.processResponse(data); @@ -8896,7 +9311,7 @@ export default (function() { } }, - processDialerResponse: function(instance, response) { + processDialerResponse: function (instance, response) { var type = response.dialer_request['@type']; var messageId = response.dialer_request['@message_id']; var dest = messageId === '' ? 'IS' : messageId.slice(0, 2); @@ -8951,7 +9366,7 @@ export default (function() { } }, - processRequest: function(instance, message) { + processRequest: function (instance, message) { var type = message.ui_request['@type']; // Fire callback function @@ -8975,7 +9390,7 @@ export default (function() { } }, - processStats: function(instance, data) { + processStats: function (instance, data) { var type = data.ui_stats['@type']; var message = 'Received ' + type.toUpperCase() + ' response message from IS'; @@ -9149,7 +9564,7 @@ export default (function() { * } */ - processResponseCollection: function( + processResponseCollection: function ( response, groupProp, itemProp, @@ -9168,11 +9583,11 @@ export default (function() { } }, - escapeSoftphoneUsername: function(str) { + escapeSoftphoneUsername: function (str) { return str && str.replace(/[@]/g, '_at_'); }, - _processItems: function(itemsRaw, textName) { + _processItems: function (itemsRaw, textName) { var result = []; if (typeof itemsRaw === 'undefined' || itemsRaw === null) { return result; @@ -9189,7 +9604,7 @@ export default (function() { return result; }, - _processItem: function(itemRaw, textName) { + _processItem: function (itemRaw, textName) { /* * Be sure that the item we are processing is not a #text node only, where the "texName" is also "text". If this * is the case, it means there's a default value that needs to get converted and isn't going to be mapped to a custom @@ -9245,7 +9660,7 @@ export default (function() { return item; }, - _convertToFormattedKey: function(key, textName) { + _convertToFormattedKey: function (key, textName) { var formattedKey; if (key.match(/^#/)) { // dealing with text property @@ -9253,7 +9668,7 @@ export default (function() { } else { // dealing with attribute formattedKey = key.replace(/@/, ''); // remove leading '@' - formattedKey = formattedKey.replace(/_([a-z])/g, function(g) { + formattedKey = formattedKey.replace(/_([a-z])/g, function (g) { return g[1].toUpperCase(); }); // convert to camelCase } @@ -9261,7 +9676,7 @@ export default (function() { return formattedKey; }, - _convertKeyForCollection: function(formattedKey) { + _convertKeyForCollection: function (formattedKey) { if (formattedKey.substr(formattedKey.length - 1) !== 's') { return formattedKey + 's'; } @@ -9269,7 +9684,7 @@ export default (function() { return formattedKey; }, - _tryConvertValueToBoolean: function(value) { + _tryConvertValueToBoolean: function (value) { if (value === null) { return null; } @@ -9284,7 +9699,7 @@ export default (function() { } }, - fireCallback: function(instance, type, response) { + fireCallback: function (instance, type, response) { response = response || ''; if ( typeof type !== 'undefined' && @@ -9294,13 +9709,13 @@ export default (function() { } }, - setCallback: function(instance, type, callback) { + setCallback: function (instance, type, callback) { if (typeof type !== 'undefined' && typeof callback !== 'undefined') { instance.callbacks[type] = callback; } }, - getMessageId: function() { + getMessageId: function () { function s4() { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) @@ -9326,7 +9741,7 @@ export default (function() { // check whether the given array of ids exist in the given array of objects // if not available, remove the id // returns the clean list of acceptable ids - checkExistingIds: function(objArray, idArray, idProperty) { + checkExistingIds: function (objArray, idArray, idProperty) { var availIds = []; var removeIds = []; @@ -9355,7 +9770,7 @@ export default (function() { }, // find an object by given id in an array of objects - findObjById: function(objArray, id, propName) { + findObjById: function (objArray, id, propName) { for (var o = 0; o < objArray.length; o++) { var obj = objArray[o]; if (obj[propName] === id) { @@ -9367,7 +9782,7 @@ export default (function() { }, // check whether agent dialDest is either a 10-digit number or valid sip - validateDest: function(dialDest) { + validateDest: function (dialDest) { var isValid = false; var isNum = /^\d+$/.test(dialDest); if (isNum && dialDest.length === 10) { @@ -9386,7 +9801,7 @@ export default (function() { // pass in MESSAGE_TYPE string (e.g. "CANCEL-CALLBACK"), // return corresponding CALLBACK_TYPE function name string (e.g. "callbackCancelResponse") - findCallbackBasedOnMessageType: function(messageType) { + findCallbackBasedOnMessageType: function (messageType) { var callbackFnName = ''; for (var key in MESSAGE_TYPES) { if (MESSAGE_TYPES[key] === messageType) { @@ -9398,23 +9813,29 @@ export default (function() { // add message, detail, and status values to the formattedResponse // returned from each request processResponse method - buildDefaultResponse: function(response) { + buildDefaultResponse: function (response, includeOpId) { + includeOpId = includeOpId || false; // default to false, the ES5 way var message = ''; var detail = ''; var status = ''; + var operationId = ''; + var msg = ''; var det = ''; var stat = ''; + var opId = ''; - // add message and detail if present + // add properties if present if (response.ui_response) { msg = response.ui_response.message; det = response.ui_response.detail; stat = response.ui_response.status; + opId = response.ui_response.operation_id; } else if (response.ui_notification) { msg = response.ui_notification.message; det = response.ui_notification.detail; stat = response.ui_notification.status; + opId = response.ui_notification.operation_id; } if (msg) { @@ -9426,15 +9847,24 @@ export default (function() { if (stat) { status = stat['#text'] || ''; } + if (opId) { + operationId = opId['#text'] || ''; + } - return { + var returnMsg = { message: message, detail: detail, status: status, }; + + if (includeOpId) { + returnMsg.operationId = operationId; + } + + return returnMsg; }, - toString: function(val) { + toString: function (val) { if (val) { return val.toString(); } else { @@ -9445,7 +9875,7 @@ export default (function() { // safely check if property exists and return empty string // instead of undefined if it doesn't exist // convert "TRUE" | "FALSE" to boolean - getText: function(obj, prop) { + getText: function (obj, prop) { var o = obj[prop]; if (o && o['#text']) { if (o['#text'].toUpperCase() === 'TRUE') { @@ -9463,7 +9893,7 @@ export default (function() { // safely check if property exists and return empty string // instead of undefined if it doesn't exist // convert "TRUE" | "FALSE" to boolean - getAttribute: function(obj, prop) { + getAttribute: function (obj, prop) { var o = obj[prop]; if (o && o[prop]) { if (o[prop].toUpperCase() === 'TRUE') { @@ -9482,7 +9912,7 @@ export default (function() { // @param str The string of keyvalue pairs to parse // @param outerDelimiter The delimiter that separates each keyValue pair // @param innerDelimiter The delimiter that separates each key from its value - parseKeyValuePairsFromString: function( + parseKeyValuePairsFromString: function ( str, outerDelimiter, innerDelimiter, @@ -9491,7 +9921,7 @@ export default (function() { return []; } - var arr = str.split(outerDelimiter).reduce(function(dict, pair) { + var arr = str.split(outerDelimiter).reduce(function (dict, pair) { var keyValue = pair.split(innerDelimiter); dict[keyValue[0]] = keyValue[1]; return dict; @@ -9501,7 +9931,7 @@ export default (function() { }, // Finds a request by responseTo id - findRequestById: function(instance, id) { + findRequestById: function (instance, id) { var request = null; for (var i = 0; i < instance._requests.length; i++) { if (instance._requests[i].id === id) { @@ -9515,7 +9945,7 @@ export default (function() { // called every 30 seconds letting intelliQueue know // not to archive the call so dispositions and other call // clean up actions can happen - sendPingCallMessage: function() { + sendPingCallMessage: function () { UIModel.getInstance().pingCallRequest = new PingCallRequest(); var msg = UIModel.getInstance().pingCallRequest.formatJSON(); var msgObj = JSON.parse(msg); @@ -9533,14 +9963,14 @@ export default (function() { }, // called every 5 seconds to request stats from IntelliServices - sendStatsRequestMessage: function() { + sendStatsRequestMessage: function () { UIModel.getInstance().statsRequest = new StatsRequest(); var msg = UIModel.getInstance().statsRequest.formatJSON(); utils.sendMessage(UIModel.getInstance().libraryInstance, msg); }, // called every 20 seconds to ping IntelliSocket whent stats are disabled - sendPingRequestMessage: function() { + sendPingRequestMessage: function () { var msg = 'BEAT'; var instance = UIModel.getInstance().libraryInstance; if (instance.socket && instance.socket.readyState === 1) { @@ -9552,7 +9982,7 @@ export default (function() { // if we have received agent daily stats // start incrementing various data points since not all // data is incremented when we want on the IntelliServices side - onAgentDailyStats: function() { + onAgentDailyStats: function () { if (Object.keys(UIModel.getInstance().agentDailyStats).length !== 0) { var agentSettings = UIModel.getInstance().agentSettings, stats = UIModel.getInstance().agentDailyStats; @@ -9565,7 +9995,7 @@ export default (function() { stats.totalOffhookTime = Number(curOffhookTime) + 1; } - if (agentSettings.currentState == 'ENGAGED') { + if (agentSettings.currentState === 'ENGAGED') { var curTalkTime = stats.totalTalkTime; stats.totalTalkTime = Number(curTalkTime) + 1; @@ -9576,7 +10006,7 @@ export default (function() { }, // called in loginAgent if 'integrated' dial destination passed in - getDialDestination: function() { + getDialDestination: function () { var model = UIModel.getInstance(); var softphoneSettings = model.softphoneSettings; var dialDestType = model.applicationSettings.dialDestType; @@ -9603,10 +10033,10 @@ export default (function() { }, // get valid access token based on agentId and login hash code - refreshAccessToken: function() { + refreshAccessToken: function () { var model = UIModel.getInstance(); var baseUrl = model.authHost + model.baseAuthUri; - var errorMsg = 'Error in opening WebSocket on retrieving access token'; + var errorMsg = 'Error in refreshing access token'; var params = { queryParams: { loginHashcode: model.connectionSettings.hashCode, @@ -9615,24 +10045,39 @@ export default (function() { }, }; - new HttpService(baseUrl).httpPost('login/agent/hashcode', params).then( - function(response) { - try { - response = JSON.parse(response.response); - UIModel.getInstance().authenticateRequest.engageAccessToken = - response.accessToken; - } catch (err) { - utils.logMessage(LOG_LEVELS.WARN, errorMsg, err); - } - }, - function(err) { - var errResponse = { - type: 'refreshAccessToken Error', - message: errorMsg, - }; - utils.logMessage(LOG_LEVELS.WARN, errorMsg, err); - }, - ); + return new HttpService(baseUrl) + .httpPost('login/agent/hashcode', params) + .then( + function (response) { + try { + response = JSON.parse(response.response); + UIModel.getInstance().authenticateRequest.engageAccessToken = + response.accessToken; + } catch (err) { + utils.logMessage(LOG_LEVELS.WARN, errorMsg, err); + } + }, + function (err) { + var errResponse = { + type: 'refreshAccessToken Error', + message: errorMsg, + }; + utils.logMessage(LOG_LEVELS.WARN, errResponse, err); + }, + ); + }, + + // return random string of length given + generateOpId: function (length) { + length = length || 24; + var CHARACTERS = '0123456789abcdefghijklmnopqrstuvwxyz'; + + var result = ''; + for (var i = length; i > 0; --i) { + result += + CHARACTERS[Math.round(Math.random() * (CHARACTERS.length - 1))]; + } + return result; }, }; @@ -9673,6 +10118,7 @@ export default (function() { CHAT_INACTIVE: 'chatInactiveNotification', // external chat CHAT_LIST: 'chatListResponse', // external chat CHAT_LOAD_MEDIA: 'chatLoadMediaResponse', // engage digital task + CHAT_AGENT_ASSIGN_FAILURE: 'chatAgentAssignFailureResponse', // engage digital task CHAT_MESSAGE: 'chatMessageNotification', // external chat CHAT_NEW: 'chatNewNotification', // external chat CHAT_PRESENTED: 'chatPresentedNotification', // external chat @@ -9745,6 +10191,7 @@ export default (function() { SIP_SWITCH_REGISTRAR: 'sipSwitchRegistrarNotification', SIP_UNMUTE: 'sipUnmuteResponse', SIP_UNREGISTERED: 'sipUnregisteredNotification', + ROTATE_WEBRTC_SERVER: 'rotateWebRtcServer', }; const MESSAGE_TYPES = { @@ -9778,6 +10225,7 @@ export default (function() { CHAT_STATE: 'CHAT-STATE', // external chat CHAT_TYPING: 'CHAT-TYPING', // external chat CHAT_LOAD_MEDIA: 'LOAD-MEDIA', // engage digital task + CHAT_AGENT_ASSIGN_FAILURE: 'CHAT-AGENT-ASSIGN-FAILURE', // engage digital task DIAL_GROUP_CHANGE: 'DIAL_GROUP_CHANGE', DIAL_GROUP_CHANGE_PENDING: 'DIAL_GROUP_CHANGE_PENDING', DIRECT_AGENT_ROUTE: 'DIRECT-AGENT-ROUTE', @@ -9857,28 +10305,28 @@ export default (function() { 'use strict'; /** - * This is the constructor for the Library Object. Note that the constructor is also being - * attached to the context that the library was loaded in. - * @param {Object} [config={}] Set authHost, if the WebSocket should be secure or not with the testingLocal boolean, and callback functions. - * @constructor - * @namespace Core - * @memberof AgentLibrary - * @property {object} callbacks Internal map of registered callback functions - * @property {array} _requests Internal map of requests by message id, private property. - * @property {array} _queuedMsgs Array of pending messages to be sent when socket reconnected - * @property {boolean} _isReconnect Whether or not we are doing a reconnect for the socket - * @example - var Lib = new AgentLibrary({ - authHost:'d01-test.cf.dev:8080', // window.location.origin - testingLocal: true, - allowMultiSocket: true, - callbacks: { - closeResponse: onCloseFunction, - openResponse: onOpenFunction - } - }); - */ - var AgentLibrary = (context.AgentLibrary = function(config) { + * This is the constructor for the Library Object. Note that the constructor is also being + * attached to the context that the library was loaded in. + * @param {Object} [config={}] Set authHost, if the WebSocket should be secure or not with the testingLocal boolean, and callback functions. + * @constructor + * @namespace Core + * @memberof AgentLibrary + * @property {object} callbacks Internal map of registered callback functions + * @property {array} _requests Internal map of requests by message id, private property. + * @property {array} _queuedMsgs Array of pending messages to be sent when socket reconnected + * @property {boolean} _isReconnect Whether or not we are doing a reconnect for the socket + * @example + var Lib = new AgentLibrary({ + authHost:'d01-test.cf.dev:8080', // window.location.origin + testingLocal: true, + allowMultiSocket: true, + callbacks: { + closeResponse: onCloseFunction, + openResponse: onOpenFunction + } + }); + */ + var AgentLibrary = (context.AgentLibrary = function (config) { config = config || {}; // define properties @@ -9982,7 +10430,7 @@ export default (function() { *
  • "chatQueueStats"
  • * @type {object} */ - AgentLibrary.prototype.setCallbacks = function(callbackMap) { + AgentLibrary.prototype.setCallbacks = function (callbackMap) { for (var property in callbackMap) { this.callbacks[property] = callbackMap[property]; } @@ -9994,7 +10442,7 @@ export default (function() { * @param {string} type The name of the event that fires the callback function * @param {function} callback The function to call for the given type */ - AgentLibrary.prototype.setCallback = function(type, callback) { + AgentLibrary.prototype.setCallback = function (type, callback) { this.callbacks[type] = callback; }; @@ -10003,7 +10451,7 @@ export default (function() { * @memberof AgentLibrary.Core * @returns {array} */ - AgentLibrary.prototype.getCallbacks = function() { + AgentLibrary.prototype.getCallbacks = function () { return this.callbacks; }; @@ -10012,7 +10460,7 @@ export default (function() { * @memberof AgentLibrary.Core * @returns {object} */ - AgentLibrary.prototype.getCallback = function(type) { + AgentLibrary.prototype.getCallback = function (type) { return this.callbacks[type]; }; @@ -10021,7 +10469,7 @@ export default (function() { * @memberof AgentLibrary.Core * @returns {object} */ - AgentLibrary.prototype.getSocket = function(type) { + AgentLibrary.prototype.getSocket = function (type) { return this.socket; }; @@ -10038,7 +10486,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getAuthenticateRequest = function() { + AgentLibrary.prototype.getAuthenticateRequest = function () { return UIModel.getInstance().authenticateRequest; }; /** @@ -10046,7 +10494,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getLoginRequest = function() { + AgentLibrary.prototype.getLoginRequest = function () { return UIModel.getInstance().loginRequest; }; /** @@ -10054,7 +10502,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getMultiSocketRequest = function() { + AgentLibrary.prototype.getMultiSocketRequest = function () { return UIModel.getInstance().multiSocketRequest; }; /** @@ -10062,7 +10510,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getAgentConfigRequest = function() { + AgentLibrary.prototype.getAgentConfigRequest = function () { return UIModel.getInstance().loginPhase1Request; }; /** @@ -10070,7 +10518,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getLogoutRequest = function() { + AgentLibrary.prototype.getLogoutRequest = function () { return UIModel.getInstance().logoutRequest; }; /** @@ -10078,7 +10526,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getAgentDailyStats = function() { + AgentLibrary.prototype.getAgentDailyStats = function () { return UIModel.getInstance().agentDailyStats; }; /** @@ -10086,7 +10534,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getCallTokens = function() { + AgentLibrary.prototype.getCallTokens = function () { return UIModel.getInstance().callTokens; }; /** @@ -10094,7 +10542,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getAgentStateRequest = function() { + AgentLibrary.prototype.getAgentStateRequest = function () { return UIModel.getInstance().agentStateRequest; }; /** @@ -10102,7 +10550,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getOffhookInitRequest = function() { + AgentLibrary.prototype.getOffhookInitRequest = function () { return UIModel.getInstance().offhookInitRequest; }; /** @@ -10110,7 +10558,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getOffhookTermRequest = function() { + AgentLibrary.prototype.getOffhookTermRequest = function () { return UIModel.getInstance().offhookTermRequest; }; /** @@ -10118,7 +10566,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getHangupRequest = function() { + AgentLibrary.prototype.getHangupRequest = function () { return UIModel.getInstance().hangupRequest; }; /** @@ -10126,7 +10574,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getPreviewDialRequest = function() { + AgentLibrary.prototype.getPreviewDialRequest = function () { return UIModel.getInstance().previewDialRequest; }; /** @@ -10134,7 +10582,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getTcpaSafeRequest = function() { + AgentLibrary.prototype.getTcpaSafeRequest = function () { return UIModel.getInstance().tcpaSafeRequest; }; /** @@ -10142,7 +10590,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getManualOutdialRequest = function() { + AgentLibrary.prototype.getManualOutdialRequest = function () { return UIModel.getInstance().oneToOneOutdialRequest; }; /** @@ -10150,7 +10598,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getManualOutdialCancelRequest = function() { + AgentLibrary.prototype.getManualOutdialCancelRequest = function () { return UIModel.getInstance().oneToOneOutdialCancelRequest; }; /** @@ -10158,7 +10606,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getCallNotesRequest = function() { + AgentLibrary.prototype.getCallNotesRequest = function () { return UIModel.getInstance().callNotesRequest; }; /** @@ -10166,7 +10614,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getCampaignDispositionsRequest = function() { + AgentLibrary.prototype.getCampaignDispositionsRequest = function () { return UIModel.getInstance().campaignDispositionsRequest; }; /** @@ -10174,7 +10622,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getDispositionRequest = function() { + AgentLibrary.prototype.getDispositionRequest = function () { return UIModel.getInstance().dispositionRequest; }; /** @@ -10182,7 +10630,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getDispositionManualPassRequest = function() { + AgentLibrary.prototype.getDispositionManualPassRequest = function () { return UIModel.getInstance().dispositionManualPassRequest; }; /** @@ -10190,7 +10638,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getWarmTransferRequest = function() { + AgentLibrary.prototype.getWarmTransferRequest = function () { return UIModel.getInstance().warmXferRequest; }; /** @@ -10198,7 +10646,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getColdTransferRequest = function() { + AgentLibrary.prototype.getColdTransferRequest = function () { return UIModel.getInstance().coldXferRequest; }; /** @@ -10206,7 +10654,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getWarmTransferCancelRequest = function() { + AgentLibrary.prototype.getWarmTransferCancelRequest = function () { return UIModel.getInstance().warmXferCancelRequest; }; /** @@ -10214,7 +10662,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getRequeueRequest = function() { + AgentLibrary.prototype.getRequeueRequest = function () { return UIModel.getInstance().requeueRequest; }; /** @@ -10222,7 +10670,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getBargeInRequest = function() { + AgentLibrary.prototype.getBargeInRequest = function () { return UIModel.getInstance().bargeInRequest; }; /** @@ -10230,7 +10678,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getHoldRequest = function() { + AgentLibrary.prototype.getHoldRequest = function () { return UIModel.getInstance().holdRequest; }; /** @@ -10238,7 +10686,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getPauseRecordRequest = function() { + AgentLibrary.prototype.getPauseRecordRequest = function () { return UIModel.getInstance().pauseRecordRequest; }; /** @@ -10246,7 +10694,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getRecordRequest = function() { + AgentLibrary.prototype.getRecordRequest = function () { return UIModel.getInstance().recordRequest; }; /** @@ -10254,7 +10702,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getChatPresentedRequest = function() { + AgentLibrary.prototype.getChatPresentedRequest = function () { return UIModel.getInstance().chatPresentedRequest; }; /** @@ -10262,7 +10710,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getChatDispositionRequest = function() { + AgentLibrary.prototype.getChatDispositionRequest = function () { return UIModel.getInstance().chatDispositionRequest; }; /** @@ -10270,7 +10718,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getChatMessageRequest = function() { + AgentLibrary.prototype.getChatMessageRequest = function () { return UIModel.getInstance().chatMessageRequest; }; /** @@ -10278,7 +10726,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getChatRequeueRequest = function() { + AgentLibrary.prototype.getChatRequeueRequest = function () { return UIModel.getInstance().chatRequeueRequest; }; /** @@ -10286,7 +10734,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getChatTypingRequest = function() { + AgentLibrary.prototype.getChatTypingRequest = function () { return UIModel.getInstance().chatTypingRequest; }; /** @@ -10294,7 +10742,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getAgentStatsPacket = function() { + AgentLibrary.prototype.getAgentStatsPacket = function () { return UIModel.getInstance().agentStatsPacket; }; /** @@ -10302,7 +10750,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getAgentDailyStatsPacket = function() { + AgentLibrary.prototype.getAgentDailyStatsPacket = function () { return UIModel.getInstance().agentDailyStatsPacket; }; /** @@ -10310,7 +10758,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getQueueStatsPacket = function() { + AgentLibrary.prototype.getQueueStatsPacket = function () { return UIModel.getInstance().queueStatsPacket; }; /** @@ -10318,7 +10766,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getChatQueueStatsPacket = function() { + AgentLibrary.prototype.getChatQueueStatsPacket = function () { return UIModel.getInstance().chatQueueStatsPacket; }; /** @@ -10326,7 +10774,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getCampaignStatsPacket = function() { + AgentLibrary.prototype.getCampaignStatsPacket = function () { return UIModel.getInstance().campaignStatsPacket; }; /** @@ -10334,7 +10782,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getAgentConfigPacket = function() { + AgentLibrary.prototype.getAgentConfigPacket = function () { return UIModel.getInstance().loginPhase1Packet; }; /** @@ -10342,7 +10790,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getLoginPacket = function() { + AgentLibrary.prototype.getLoginPacket = function () { return UIModel.getInstance().loginPacket; }; /** @@ -10350,7 +10798,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getAgentStatePacket = function() { + AgentLibrary.prototype.getAgentStatePacket = function () { return UIModel.getInstance().agentStatePacket; }; /** @@ -10358,7 +10806,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getCurrentCallPacket = function() { + AgentLibrary.prototype.getCurrentCallPacket = function () { return UIModel.getInstance().currentCallPacket; }; /** @@ -10366,7 +10814,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getOffhookInitPacket = function() { + AgentLibrary.prototype.getOffhookInitPacket = function () { return UIModel.getInstance().offhookInitPacket; }; /** @@ -10374,7 +10822,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getOffhookTermPacket = function() { + AgentLibrary.prototype.getOffhookTermPacket = function () { return UIModel.getInstance().offhookTermPacket; }; @@ -10384,7 +10832,7 @@ export default (function() { * @returns {object} */ - AgentLibrary.prototype.getChatAgentEnd = function() { + AgentLibrary.prototype.getChatAgentEnd = function () { return UIModel.getInstance().chatAgentEnd; }; @@ -10394,11 +10842,11 @@ export default (function() { * @returns {object} */ - AgentLibrary.prototype.getWebRTCRequest = function() { + AgentLibrary.prototype.getWebRTCRequest = function () { return UIModel.getInstance().webRTCRequest; }; - AgentLibrary.prototype.getChatListRequest = function() { + AgentLibrary.prototype.getChatListRequest = function () { return UIModel.getInstance().chatListRequest; }; @@ -10408,7 +10856,7 @@ export default (function() { * @returns {object} */ - AgentLibrary.prototype.getSearchDirectoryRequest = function() { + AgentLibrary.prototype.getSearchDirectoryRequest = function () { return UIModel.getInstance().searchDirectoryRequest; }; @@ -10424,7 +10872,7 @@ export default (function() { * @memberof AgentLibrary.Core.Notifications * @returns {object} */ - AgentLibrary.prototype.getDialGroupChangeNotification = function() { + AgentLibrary.prototype.getDialGroupChangeNotification = function () { return UIModel.getInstance().dialGroupChangeNotification; }; /** @@ -10432,7 +10880,7 @@ export default (function() { * @memberof AgentLibrary.Core.Notifications * @returns {object} */ - AgentLibrary.prototype.getDialGroupChangePendingNotification = function() { + AgentLibrary.prototype.getDialGroupChangePendingNotification = function () { return UIModel.getInstance().dialGroupChangePendingNotification; }; /** @@ -10440,7 +10888,7 @@ export default (function() { * @memberof AgentLibrary.Core.Notifications * @returns {object} */ - AgentLibrary.prototype.getEndCallNotification = function() { + AgentLibrary.prototype.getEndCallNotification = function () { return UIModel.getInstance().endCallNotification; }; /** @@ -10448,7 +10896,7 @@ export default (function() { * @memberof AgentLibrary.Core.Notifications * @returns {object} */ - AgentLibrary.prototype.getGatesChangeNotification = function() { + AgentLibrary.prototype.getGatesChangeNotification = function () { return UIModel.getInstance().gatesChangeNotification; }; /** @@ -10456,7 +10904,7 @@ export default (function() { * @memberof AgentLibrary.Core.Notifications * @returns {object} */ - AgentLibrary.prototype.getGenericNotification = function() { + AgentLibrary.prototype.getGenericNotification = function () { return UIModel.getInstance().genericNotification; }; /** @@ -10464,7 +10912,7 @@ export default (function() { * @memberof AgentLibrary.Core.Notifications * @returns {object} */ - AgentLibrary.prototype.getNewCallNotification = function() { + AgentLibrary.prototype.getNewCallNotification = function () { return UIModel.getInstance().newCallNotification; }; /** @@ -10472,7 +10920,7 @@ export default (function() { * @memberof AgentLibrary.Core.Notifications * @returns {object} */ - AgentLibrary.prototype.getCurrentCall = function() { + AgentLibrary.prototype.getCurrentCall = function () { return UIModel.getInstance().currentCall; }; /** @@ -10480,7 +10928,7 @@ export default (function() { * @memberof AgentLibrary.Core.Notifications * @returns {object} */ - AgentLibrary.prototype.getAddSessionNotification = function() { + AgentLibrary.prototype.getAddSessionNotification = function () { return UIModel.getInstance().addSessionNotification; }; /** @@ -10488,7 +10936,7 @@ export default (function() { * @memberof AgentLibrary.Core.Notifications * @returns {object} */ - AgentLibrary.prototype.getDropSessionNotification = function() { + AgentLibrary.prototype.getDropSessionNotification = function () { return UIModel.getInstance().dropSessionNotification; }; /** @@ -10496,7 +10944,7 @@ export default (function() { * @memberof AgentLibrary.Core.Notifications * @returns {object} */ - AgentLibrary.prototype.getEarlyUiiNotification = function() { + AgentLibrary.prototype.getEarlyUiiNotification = function () { return UIModel.getInstance().earlyUiiNotification; }; /** @@ -10504,7 +10952,7 @@ export default (function() { * @memberof AgentLibrary.Core.Notifications * @returns {object} */ - AgentLibrary.prototype.getChatActiveNotification = function() { + AgentLibrary.prototype.getChatActiveNotification = function () { return UIModel.getInstance().chatActiveNotification; }; /** @@ -10512,7 +10960,7 @@ export default (function() { * @memberof AgentLibrary.Core.Notifications * @returns {object} */ - AgentLibrary.prototype.getChatInactiveNotification = function() { + AgentLibrary.prototype.getChatInactiveNotification = function () { return UIModel.getInstance().chatInactiveNotification; }; /** @@ -10520,7 +10968,7 @@ export default (function() { * @memberof AgentLibrary.Core.Notifications * @returns {object} */ - AgentLibrary.prototype.getChatClientReconnectNotification = function() { + AgentLibrary.prototype.getChatClientReconnectNotification = function () { return UIModel.getInstance().chatClientReconnectNotification; }; /** @@ -10528,7 +10976,7 @@ export default (function() { * @memberof AgentLibrary.Core.Notifications * @returns {object} */ - AgentLibrary.prototype.getChatPresentedNotification = function() { + AgentLibrary.prototype.getChatPresentedNotification = function () { return UIModel.getInstance().chatPresentedNotification; }; /** @@ -10536,7 +10984,7 @@ export default (function() { * @memberof AgentLibrary.Core.Notifications * @returns {object} */ - AgentLibrary.prototype.getChatTypingNotification = function() { + AgentLibrary.prototype.getChatTypingNotification = function () { return UIModel.getInstance().chatTypingNotification; }; /** @@ -10544,7 +10992,7 @@ export default (function() { * @memberof AgentLibrary.Core.Notifications * @returns {object} */ - AgentLibrary.prototype.getNewChatNotification = function() { + AgentLibrary.prototype.getNewChatNotification = function () { return UIModel.getInstance().newChatNotification; }; /** @@ -10552,9 +11000,25 @@ export default (function() { * @memberof AgentLibrary.Core.Notifications * @returns {object} */ - AgentLibrary.prototype.getLoadMediaNotification = function() { + AgentLibrary.prototype.getLoadMediaNotification = function () { return UIModel.getInstance().loadMediaNotification; }; + /** + * Get Load Media notification class + * @memberof AgentLibrary.Core.Notifications + * @returns {object} + */ + AgentLibrary.prototype.getChatAgentAssignFailureNotification = function () { + return UIModel.getInstance().chatAgentAssignFailureNotification; + }; + /** + * Get Chat Typing notification class + * @memberof AgentLibrary.Core.Notifications + * @returns {object} + */ + AgentLibrary.prototype.getChatCancelledNotification = function () { + return UIModel.getInstance().chatCancelledNotification; + }; /** * @namespace Settings @@ -10568,7 +11032,7 @@ export default (function() { * @memberof AgentLibrary.Core.Settings * @returns {object} */ - AgentLibrary.prototype.getApplicationSettings = function() { + AgentLibrary.prototype.getApplicationSettings = function () { return UIModel.getInstance().applicationSettings; }; /** @@ -10576,7 +11040,7 @@ export default (function() { * @memberof AgentLibrary.Core.Settings * @returns {object} */ - AgentLibrary.prototype.getSoftphoneSettings = function() { + AgentLibrary.prototype.getSoftphoneSettings = function () { return UIModel.getInstance().softphoneSettings; }; /** @@ -10584,7 +11048,7 @@ export default (function() { * @memberof AgentLibrary.Core.Settings * @returns {object} */ - AgentLibrary.prototype.getChatSettings = function() { + AgentLibrary.prototype.getChatSettings = function () { return UIModel.getInstance().chatSettings; }; /** @@ -10592,7 +11056,7 @@ export default (function() { * @memberof AgentLibrary.Core.Settings * @returns {object} */ - AgentLibrary.prototype.getConnectionSettings = function() { + AgentLibrary.prototype.getConnectionSettings = function () { return UIModel.getInstance().connectionSettings; }; /** @@ -10600,7 +11064,7 @@ export default (function() { * @memberof AgentLibrary.Core.Settings * @returns {object} */ - AgentLibrary.prototype.getInboundSettings = function() { + AgentLibrary.prototype.getInboundSettings = function () { return UIModel.getInstance().inboundSettings; }; /** @@ -10608,7 +11072,7 @@ export default (function() { * @memberof AgentLibrary.Core.Settings * @returns {object} */ - AgentLibrary.prototype.getOutboundSettings = function() { + AgentLibrary.prototype.getOutboundSettings = function () { return UIModel.getInstance().outboundSettings; }; /** @@ -10616,7 +11080,7 @@ export default (function() { * @memberof AgentLibrary.Core.Settings * @returns {object} */ - AgentLibrary.prototype.getAgentSettings = function() { + AgentLibrary.prototype.getAgentSettings = function () { return UIModel.getInstance().agentSettings; }; /** @@ -10624,11 +11088,11 @@ export default (function() { * @memberof AgentLibrary.Core.Settings * @returns {object} */ - AgentLibrary.prototype.getTransferSessions = function() { + AgentLibrary.prototype.getTransferSessions = function () { return UIModel.getInstance().transferSessions; }; - AgentLibrary.prototype.getPendingSessions = function() { + AgentLibrary.prototype.getPendingSessions = function () { return UIModel.getInstance().pendingNewCallSessions; }; /** @@ -10636,7 +11100,7 @@ export default (function() { * @memberof AgentLibrary.Core.Settings * @returns {object} */ - AgentLibrary.prototype.getAgentPermissions = function() { + AgentLibrary.prototype.getAgentPermissions = function () { return UIModel.getInstance().agentPermissions; }; @@ -10653,7 +11117,7 @@ export default (function() { * @memberof AgentLibrary.Core.Settings * @returns {object} */ - AgentLibrary.prototype.getAgentStats = function() { + AgentLibrary.prototype.getAgentStats = function () { return UIModel.getInstance().agentStats; }; /** @@ -10661,7 +11125,7 @@ export default (function() { * @memberof AgentLibrary.Core.Stats * @returns {object} */ - AgentLibrary.prototype.getAgentDailyStats = function() { + AgentLibrary.prototype.getAgentDailyStats = function () { return UIModel.getInstance().agentDailyStats; }; /** @@ -10669,7 +11133,7 @@ export default (function() { * @memberof AgentLibrary.Core.Stats * @returns {object} */ - AgentLibrary.prototype.getQueueStats = function() { + AgentLibrary.prototype.getQueueStats = function () { return UIModel.getInstance().queueStats; }; /** @@ -10677,7 +11141,7 @@ export default (function() { * @memberof AgentLibrary.Core.Stats * @returns {object} */ - AgentLibrary.prototype.getChatQueueStats = function() { + AgentLibrary.prototype.getChatQueueStats = function () { return UIModel.getInstance().chatQueueStats; }; /** @@ -10685,7 +11149,7 @@ export default (function() { * @memberof AgentLibrary.Core.Stats * @returns {object} */ - AgentLibrary.prototype.getCampaignStats = function() { + AgentLibrary.prototype.getCampaignStats = function () { return UIModel.getInstance().campaignStats; }; @@ -10694,7 +11158,7 @@ export default (function() { * @memberof AgentLibrary.Core.Settings * @returns {object} */ - AgentLibrary.prototype.getFilteredDirectory = function() { + AgentLibrary.prototype.getFilteredDirectory = function () { return UIModel.getInstance().filteredDirectory; }; @@ -10703,7 +11167,7 @@ export default (function() { * @memberof AgentLibrary.Core.Settings * @returns {object} */ - AgentLibrary.prototype.getExtensionPresenceResponse = function() { + AgentLibrary.prototype.getExtensionPresenceResponse = function () { return UIModel.getInstance().extensionPresenceResponse; }; @@ -10712,7 +11176,7 @@ export default (function() { * @memberof AgentLibrary.Core.Requests * @returns {object} */ - AgentLibrary.prototype.getExtensionPresenceRequest = function() { + AgentLibrary.prototype.getExtensionPresenceRequest = function () { return UIModel.getInstance().extensionPresenceRequest; }; @@ -10726,7 +11190,7 @@ export default (function() { AgentLibrary.prototype._HttpService = HttpService; - AgentLibrary.prototype._getUIModel = function() { + AgentLibrary.prototype._getUIModel = function () { return UIModel; }; } @@ -10735,7 +11199,7 @@ export default (function() { 'use strict'; var AgentLibrary = context.AgentLibrary; - AgentLibrary.prototype.openSocket = function(agentId, callback) { + AgentLibrary.prototype.openSocket = function (agentId, callback) { var instance = this; utils.setCallback(instance, CALLBACK_TYPES.OPEN_SOCKET, callback); if ('WebSocket' in context) { @@ -10762,13 +11226,13 @@ export default (function() { } }; - AgentLibrary.prototype.closeSocket = function() { + AgentLibrary.prototype.closeSocket = function () { this.socket.close(); }; // when socket is successfully opened, check to see if there are any queued messaged // and if so, send them. - AgentLibrary.prototype.socketOpened = function() { + AgentLibrary.prototype.socketOpened = function () { var instance = this; var currDts = new Date(); var threeMins = 3 * 60 * 1000; // milliseconds @@ -10848,7 +11312,7 @@ export default (function() { instance.socket = new WebSocket(socketDest); - instance.socket.onopen = function() { + instance.socket.onopen = function () { // stop reconnect timer clearInterval(UIModel.getInstance().reconnectIntervalId); UIModel.getInstance().reconnectIntervalId = null; @@ -10860,13 +11324,13 @@ export default (function() { instance.socketOpened(); }; - instance.socket.onerror = function(err) { + instance.socket.onerror = function (err) { utils.fireCallback(instance, CALLBACK_TYPES.OPEN_SOCKET, { error: err, }); }; - instance.socket.onmessage = function(evt) { + instance.socket.onmessage = function (evt) { if (evt.data !== 'BOP') { var data = JSON.parse(evt.data); if (data.ui_response) { @@ -10883,7 +11347,7 @@ export default (function() { } }; - instance.socket.onclose = function() { + instance.socket.onclose = function () { utils.fireCallback(instance, CALLBACK_TYPES.CLOSE_SOCKET, ''); UIModel.getInstance().applicationSettings.socketConnected = false; instance.socket = null; @@ -10908,9 +11372,14 @@ export default (function() { ); // try reconnect every 5 seconds - UIModel.getInstance().reconnectIntervalId = setInterval(function() { - instance.openSocket(UIModel.getInstance().agentSettings.agentId); - }, 5000); + UIModel.getInstance().reconnectIntervalId = setInterval( + function () { + instance.openSocket( + UIModel.getInstance().agentSettings.agentId, + ); + }, + 5000, + ); } }; } @@ -10929,7 +11398,7 @@ export default (function() { }; new HttpService(baseUrl).httpPost('login/agent/hashcode', params).then( - function(response) { + function (response) { try { response = JSON.parse(response.response); UIModel.getInstance().authenticateRequest.accessToken = @@ -10940,7 +11409,7 @@ export default (function() { utils.logMessage(LOG_LEVELS.WARN, errorMsg, err); } }, - function(err) { + function (err) { var errResponse = { type: 'WebSocket Error', message: errorMsg, @@ -10970,7 +11439,7 @@ export default (function() { * @param {string} platformId Designate the platform where the agent is set up * @param {function} [callback=null] Callback function when loginAgent response received */ - AgentLibrary.prototype.authenticateAgentWithUsernamePassword = function( + AgentLibrary.prototype.authenticateAgentWithUsernamePassword = function ( username, password, platformId, @@ -10994,7 +11463,7 @@ export default (function() { * @param {string} tokenType string type received from RingCentral Single Sign-on API * @param {function} [callback=null] Callback function when loginAgent response received */ - AgentLibrary.prototype.authenticateAgentWithRcAccessToken = function( + AgentLibrary.prototype.authenticateAgentWithRcAccessToken = function ( rcAccessToken, tokenType, callback, @@ -11015,7 +11484,7 @@ export default (function() { * @param {string} engageAccessToken JSON Web Token received from RingCentral Single Sign-on API * @param {function} [callback=null] Callback function when loginAgent response received */ - AgentLibrary.prototype.authenticateAgentWithEngageAccessToken = function( + AgentLibrary.prototype.authenticateAgentWithEngageAccessToken = function ( engageAccessToken, callback, ) { @@ -11033,7 +11502,7 @@ export default (function() { * @memberof AgentLibrary.Agent * @param {function} [callback=null] Callback function when loginPhase1 response received */ - AgentLibrary.prototype.getAgentConfig = function(callback) { + AgentLibrary.prototype.getAgentConfig = function (callback) { UIModel.getInstance().loginPhase1Request = new LoginPhase1Request(); var msg = UIModel.getInstance().loginPhase1Request.formatJSON(); @@ -11047,7 +11516,7 @@ export default (function() { * * @param {function} [callback=null] Callback function when web rtc info response received */ - AgentLibrary.prototype.getWebRtcInfo = function(callback) { + AgentLibrary.prototype.getWebRtcInfo = function (callback) { var model = UIModel.getInstance(); model.webRTCRequest = new WebRTCRequest(); model.webRTCRequest.getSipRegistrationInfo(); @@ -11067,7 +11536,7 @@ export default (function() { * @param {boolean} isForce Whether the agent login is forcing an existing agentlogin out. * @param {function} [callback=null] Callback function when configureAgent response received. */ - AgentLibrary.prototype.loginAgent = function( + AgentLibrary.prototype.loginAgent = function ( dialDest, queueIds, chatIds, @@ -11088,7 +11557,6 @@ export default (function() { isForce: isForce, }; var instance = this; - // if dialDest is set to `integrated`, we are doing an integrated softphone // and need to get SIP credentials if (config.dialDest === 'integrated') { @@ -11101,7 +11569,7 @@ export default (function() { if (model.softphoneSettings.sipInfo.length === 0) { // call engage-auth api to get webRtc info - model.libraryInstance.getWebRtcInfo(function() { + model.libraryInstance.getWebRtcInfo(function () { config.dialDest = utils.getDialDestination(); _setUpAndSendLogin(config, callback, instance); }); @@ -11140,7 +11608,7 @@ export default (function() { * @memberof AgentLibrary.Agent * @param {function} [callback=null] Callback function when multisocket response received. */ - AgentLibrary.prototype.multiLoginRequest = function(callback) { + AgentLibrary.prototype.multiLoginRequest = function (callback) { var model = UIModel.getInstance(); model.multiSocketRequest = new MultiSocketRequest(); var msg = model.multiSocketRequest.formatJSON(); @@ -11161,7 +11629,7 @@ export default (function() { * @param {number} agentId Id of the agent that will be logged out. * @param {function} [callback=null] Callback function when logoutAgent response received. */ - AgentLibrary.prototype.logoutAgent = function(agentId, callback) { + AgentLibrary.prototype.logoutAgent = function (agentId, callback) { var model = UIModel.getInstance(); if (model.agentSettings.isLoggedIn) { model.agentSettings.isLoggedIn = false; @@ -11181,7 +11649,7 @@ export default (function() { * @param {number} [requestMessage=""] Message to send for the logout request. * @param {function} [callback=null] Callback function when logoutAgent response received. */ - AgentLibrary.prototype.requestLogoutAgent = function( + AgentLibrary.prototype.requestLogoutAgent = function ( agentToLogout, requestMessage, callback, @@ -11209,7 +11677,7 @@ export default (function() { * @param {string} [agentAuxState=""] The aux state display label * @param {function} [callback=null] Callback function when agentState response received */ - AgentLibrary.prototype.setAgentState = function( + AgentLibrary.prototype.setAgentState = function ( agentState, agentAuxState, callback, @@ -11229,7 +11697,7 @@ export default (function() { * @memberof AgentLibrary.Agent * @param {function} [callback=null] Callback function when offhookInit response received */ - AgentLibrary.prototype.offhookInit = function(callback) { + AgentLibrary.prototype.offhookInit = function (callback) { UIModel.getInstance().offhookInitRequest = new OffhookInitRequest(); var msg = UIModel.getInstance().offhookInitRequest.formatJSON(); @@ -11242,7 +11710,7 @@ export default (function() { * @memberof AgentLibrary.Agent * @param {function} [callback=null] Callback function when pending callbacks response received */ - AgentLibrary.prototype.offhookTerm = function(callback) { + AgentLibrary.prototype.offhookTerm = function (callback) { UIModel.getInstance().offhookTermRequest = new OffhookTermRequest(); var msg = UIModel.getInstance().offhookTermRequest.formatJSON(); @@ -11256,7 +11724,10 @@ export default (function() { * @param {number} [agentId=logged in agent id] Id of agent to get callbacks for * @param {function} [callback=null] Callback function when pending callbacks response received */ - AgentLibrary.prototype.getPendingCallbacks = function(agentId, callback) { + AgentLibrary.prototype.getPendingCallbacks = function ( + agentId, + callback, + ) { UIModel.getInstance().callbacksPendingRequest = new CallbacksPendingRequest( agentId, ); @@ -11273,7 +11744,7 @@ export default (function() { * @param {number} [agentId=logged in agent id] Id of agent to cancel specified lead callback for * @param {function} [callback=null] Callback function when callback is canceled */ - AgentLibrary.prototype.cancelCallback = function( + AgentLibrary.prototype.cancelCallback = function ( leadId, agentId, callback, @@ -11293,7 +11764,7 @@ export default (function() { * four possible callback types: agentStats, agentDailyStats, campaignStats, or queueStats * @memberof AgentLibrary.Agent */ - AgentLibrary.prototype.requestStats = function() { + AgentLibrary.prototype.requestStats = function () { // start stats interval timer, request stats every 5 seconds if (UIModel.getInstance().statsIntervalId === null) { UIModel.getInstance().statsIntervalId = setInterval( @@ -11308,7 +11779,7 @@ export default (function() { * Terminate stats messages sent to the agent every 5 seconds. * @memberof AgentLibrary.Agent */ - AgentLibrary.prototype.terminateStats = function() { + AgentLibrary.prototype.terminateStats = function () { clearInterval(UIModel.getInstance().statsIntervalId); UIModel.getInstance().statsIntervalId = null; _requestPing(); @@ -11335,7 +11806,7 @@ export default (function() { * @param {string} dialDest The dial destination used for softphone registration * @param {boolean} isSoftphoneError True - if we want to log this dial destination update as a softphone error */ - AgentLibrary.prototype.updateDialDestination = function( + AgentLibrary.prototype.updateDialDestination = function ( dialDest, isSoftphoneError, ) { @@ -11353,7 +11824,7 @@ export default (function() { * @memberof AgentLibrary.Agent * @param {string} searchString The search filter for PBX directory */ - AgentLibrary.prototype.searchDirectory = function( + AgentLibrary.prototype.searchDirectory = function ( searchString, callback, ) { @@ -11368,7 +11839,7 @@ export default (function() { * Refresh Token method * @memberof AgentLibrary.Agent */ - AgentLibrary.prototype.getRefreshedToken = function() { + AgentLibrary.prototype.getRefreshedToken = function () { utils.refreshAccessToken(); }; @@ -11377,7 +11848,7 @@ export default (function() { * @memberof AgentLibrary.Agent * @param {string} extensionIds The extension Ids of RC */ - AgentLibrary.prototype.getExtensionPresenceInfo = function( + AgentLibrary.prototype.getExtensionPresenceInfo = function ( extensionIds, callback, ) { @@ -11408,7 +11879,7 @@ export default (function() { * @param {number} monitorAgentId UII Agent Id of the agent you wish to monitor * @param {function} [callback=null] Callback function when barge in response received */ - AgentLibrary.prototype.bargeIn = function( + AgentLibrary.prototype.bargeIn = function ( agentId, uii, monitorAgentId, @@ -11434,7 +11905,7 @@ export default (function() { * @param {number} monitorAgentId UII Agent Id of the agent you wish to monitor * @param {function} [callback=null] Callback function when coaching session response received */ - AgentLibrary.prototype.coach = function( + AgentLibrary.prototype.coach = function ( agentId, uii, monitorAgentId, @@ -11460,7 +11931,7 @@ export default (function() { * @param {number} [sipHeaders=[]] Name/Value header pairs * @param {function} [callback=null] Callback function when cold transfer response received */ - AgentLibrary.prototype.coldXfer = function( + AgentLibrary.prototype.coldXfer = function ( dialDest, callerId, sipHeaders, @@ -11486,7 +11957,7 @@ export default (function() { * @param {number} [countryId=""] Country Id for the dialDest * @param {function} [callback=null] Callback function when warm transfer response received */ - AgentLibrary.prototype.internationalColdXfer = function( + AgentLibrary.prototype.internationalColdXfer = function ( dialDest, callerId, sipHeaders, @@ -11520,7 +11991,7 @@ export default (function() { * @param {string} [leadId=null] The lead id associated with this call (only for Outbound Dispositions). * @param {string} [requestId=null] The request id associated with a preview fetched lead (only for Outbound Dispositions). */ - AgentLibrary.prototype.dispositionCall = function( + AgentLibrary.prototype.dispositionCall = function ( uii, dispId, notes, @@ -11566,7 +12037,7 @@ export default (function() { * @param {string} [requestId=null] The request key for the lead * @param {string} [externId=null] The external id of the lead */ - AgentLibrary.prototype.dispositionManualPass = function( + AgentLibrary.prototype.dispositionManualPass = function ( dispId, notes, callback, @@ -11594,7 +12065,7 @@ export default (function() { * @param {string} campaignId Id for campaign to get dispositions for * @param {function} [callback=null] Callback function when campaign dispositions response received */ - AgentLibrary.prototype.getCampaignDispositions = function( + AgentLibrary.prototype.getCampaignDispositions = function ( campaignId, callback, ) { @@ -11613,7 +12084,7 @@ export default (function() { * @param {string} [sessionId=""] Session to hangup, defaults to current call session id * @param {boolean} resetPendingDisp, reset pendingDisp to false, in case of bad reconnect */ - AgentLibrary.prototype.hangup = function(sessionId, resetPendingDisp) { + AgentLibrary.prototype.hangup = function (sessionId, resetPendingDisp) { UIModel.getInstance().hangupRequest = new HangupRequest( sessionId, resetPendingDisp, @@ -11628,7 +12099,7 @@ export default (function() { * @param {boolean} holdState Whether we are putting call on hold or taking off hold - values true | false * @param {function} [callback=null] Callback function when hold response received */ - AgentLibrary.prototype.hold = function(holdState, callback) { + AgentLibrary.prototype.hold = function (holdState, callback) { UIModel.getInstance().holdRequest = new HoldRequest(holdState); var msg = UIModel.getInstance().holdRequest.formatJSON(); @@ -11643,7 +12114,7 @@ export default (function() { * @param {integer|string} sessionId session id of the call to place on hold * @param {function} [callback=null] Callback function when hold response received */ - AgentLibrary.prototype.holdSession = function( + AgentLibrary.prototype.holdSession = function ( holdState, sessionId, callback, @@ -11667,7 +12138,7 @@ export default (function() { * @param {string} [countryId='USA'] Country for the destination number * @param {number} [queueId=''] Queue id to tie manual call to */ - AgentLibrary.prototype.manualOutdial = function( + AgentLibrary.prototype.manualOutdial = function ( destination, callerId, ringTime, @@ -11690,7 +12161,7 @@ export default (function() { * @memberof AgentLibrary.Call * @param {string} uii UII of manual call request, the UII is returned in the EARLY_UII notification. */ - AgentLibrary.prototype.manualOutdialCancel = function(uii) { + AgentLibrary.prototype.manualOutdialCancel = function (uii) { UIModel.getInstance().oneToOneOutdialCancelRequest = new OneToOneOutdialCancelRequest( uii, ); @@ -11704,7 +12175,7 @@ export default (function() { * @param {boolean} record Whether we are recording or not * @param {function} [callback=null] Callback function when pause record response received */ - AgentLibrary.prototype.pauseRecord = function(record, callback) { + AgentLibrary.prototype.pauseRecord = function (record, callback) { UIModel.getInstance().pauseRecordRequest = new PauseRecordRequest( record, ); @@ -11721,7 +12192,7 @@ export default (function() { * @param {number} requestId Pending request id sent back with lead, required to dial lead. * @param {number} [leadPhone=""] Lead phone number. Only needed if there are multiple numbers loaded for given lead. */ - AgentLibrary.prototype.previewDial = function(requestId, leadPhone) { + AgentLibrary.prototype.previewDial = function (requestId, leadPhone) { UIModel.getInstance().previewDialRequest = new PreviewDialRequest( '', [], @@ -11739,7 +12210,7 @@ export default (function() { * e.g. [ {key: "name", value: "Geoff"} ] * @param {function} [callback=null] Callback function when preview fetch completed, returns matched leads */ - AgentLibrary.prototype.previewFetch = function(searchFields, callback) { + AgentLibrary.prototype.previewFetch = function (searchFields, callback) { UIModel.getInstance().previewDialRequest = new PreviewDialRequest( '', searchFields, @@ -11758,7 +12229,7 @@ export default (function() { * e.g. [ {key: "name", value: "Geoff"} ] * @param {function} [callback=null] Callback function when lead search completed, returns matched leads */ - AgentLibrary.prototype.searchLeads = function(searchFields, callback) { + AgentLibrary.prototype.searchLeads = function (searchFields, callback) { UIModel.getInstance().previewDialRequest = new PreviewDialRequest( 'search', searchFields, @@ -11776,7 +12247,7 @@ export default (function() { * @param {string} notes Agent notes to add to call * @param {function} [callback=null] Callback function when call notes response received */ - AgentLibrary.prototype.setCallNotes = function(notes, callback) { + AgentLibrary.prototype.setCallNotes = function (notes, callback) { UIModel.getInstance().callNotesRequest = new CallNotesRequest(notes); var msg = UIModel.getInstance().callNotesRequest.formatJSON(); @@ -11792,7 +12263,7 @@ export default (function() { * @param {number} monitorAgentId UII Agent Id of the agent you wish to monitor * @param {function} [callback=null] Callback function when silent monitor response received */ - AgentLibrary.prototype.monitor = function( + AgentLibrary.prototype.monitor = function ( agentId, uii, monitorAgentId, @@ -11816,7 +12287,7 @@ export default (function() { * @param {boolean} record Whether we are recording or not * @param {function} [callback=null] Callback function when record response received */ - AgentLibrary.prototype.record = function(record, callback) { + AgentLibrary.prototype.record = function (record, callback) { UIModel.getInstance().recordRequest = new RecordRequest(record); var msg = UIModel.getInstance().recordRequest.formatJSON(); @@ -11832,7 +12303,7 @@ export default (function() { * @param {boolean} maintain Whether or not to maintain the current agent * @param {function} [callback=null] Callback function when requeue response received */ - AgentLibrary.prototype.requeueCall = function( + AgentLibrary.prototype.requeueCall = function ( queueId, skillId, maintain, @@ -11856,7 +12327,7 @@ export default (function() { * @param {number} requestId Pending request id sent back with lead, required to dial lead. * @param {number} [leadPhone=""] Lead phone number. Only needed if there are multiple numbers loaded for given lead. */ - AgentLibrary.prototype.safeModeCall = function(requestId, leadPhone) { + AgentLibrary.prototype.safeModeCall = function (requestId, leadPhone) { UIModel.getInstance().tcpaSafeRequest = new TcpaSafeRequest( '', [], @@ -11874,7 +12345,7 @@ export default (function() { * e.g. [ {key: "name", value: "Geoff"} ] * @param {function} [callback=null] Callback function when safe mode fetch completed, returns matched leads */ - AgentLibrary.prototype.safeModeFetch = function(searchFields, callback) { + AgentLibrary.prototype.safeModeFetch = function (searchFields, callback) { UIModel.getInstance().tcpaSafeRequest = new TcpaSafeRequest( '', searchFields, @@ -11893,7 +12364,7 @@ export default (function() { * e.g. [ {key: "name", value: "Geoff"} ] * @param {function} [callback=null] Callback function when safe mode fetch completed, returns matched leads */ - AgentLibrary.prototype.safeSearchLeads = function( + AgentLibrary.prototype.safeSearchLeads = function ( searchFields, callback, ) { @@ -11917,7 +12388,7 @@ export default (function() { * @param {number} [countryId=""] Country Id for the dialDest * @param {function} [callback=null] Callback function when warm transfer response received */ - AgentLibrary.prototype.internationalWarmXfer = function( + AgentLibrary.prototype.internationalWarmXfer = function ( dialDest, callerId, sipHeaders, @@ -11944,7 +12415,7 @@ export default (function() { * @param {number} [sipHeaders=[]] Name/Value header pairs * @param {function} [callback=null] Callback function when warm transfer response received */ - AgentLibrary.prototype.warmXfer = function( + AgentLibrary.prototype.warmXfer = function ( dialDest, callerId, sipHeaders, @@ -11966,7 +12437,7 @@ export default (function() { * @memberof AgentLibrary.Call * @param {number} dialDest Number that was transfered to */ - AgentLibrary.prototype.warmXferCancel = function(dialDest) { + AgentLibrary.prototype.warmXferCancel = function (dialDest) { UIModel.getInstance().warmXferCancelRequest = new XferWarmCancelRequest( dialDest, ); @@ -11983,7 +12454,7 @@ export default (function() { * @param {number} [countryId=""] Country Id for the dialDest * @param {function} [callback=null] Callback function when warm transfer response received */ - AgentLibrary.prototype.corporateDirWarmXfer = function( + AgentLibrary.prototype.corporateDirWarmXfer = function ( dialDest, callerId, sipHeaders, @@ -12010,7 +12481,7 @@ export default (function() { * @param {number} [countryId=""] Country Id for the dialDest * @param {function} [callback=null] Callback function when cold transfer response received */ - AgentLibrary.prototype.corporateDirColdXfer = function( + AgentLibrary.prototype.corporateDirColdXfer = function ( dialDest, callerId, sipHeaders, @@ -12036,7 +12507,11 @@ export default (function() { * just return current script. Otherwise, fetch new version of script. * @param {function} [callback=null] Callback function when warm transfer response received */ - AgentLibrary.prototype.getScript = function(scriptId, version, callback) { + AgentLibrary.prototype.getScript = function ( + scriptId, + version, + callback, + ) { var model = UIModel.getInstance(); var script = model.scriptSettings.loadedScripts[scriptId]; utils.setCallback(this, CALLBACK_TYPES.SCRIPT_CONFIG, callback); @@ -12062,7 +12537,7 @@ export default (function() { * @param {number} scriptId Id of script * @param {object} jsonResult JSON object of script results, name/value pairs */ - AgentLibrary.prototype.saveScriptResult = function( + AgentLibrary.prototype.saveScriptResult = function ( uii, scriptId, jsonResult, @@ -12080,7 +12555,7 @@ export default (function() { * Get available list of agents available for Direct Transfer * @memberof AgentLibrary.Call */ - AgentLibrary.prototype.directAgentXferList = function(callback) { + AgentLibrary.prototype.directAgentXferList = function (callback) { UIModel.getInstance().directAgentTransferListRequest = new DirectAgentTransferList(); var msg = UIModel.getInstance().directAgentTransferListRequest.formatJSON(); @@ -12097,7 +12572,7 @@ export default (function() { * @memberof AgentLibrary.Call * @param {number} targetAgentId Agent id to transfer the call to */ - AgentLibrary.prototype.warmDirectAgentXfer = function(targetAgentId) { + AgentLibrary.prototype.warmDirectAgentXfer = function (targetAgentId) { UIModel.getInstance().directAgentTransferRequest = new DirectAgentTransfer( targetAgentId, 'WARM', @@ -12112,7 +12587,7 @@ export default (function() { * @memberof AgentLibrary.Call * @param {number} targetAgentId Agent id to transfer the call to */ - AgentLibrary.prototype.coldDirectAgentXfer = function(targetAgentId) { + AgentLibrary.prototype.coldDirectAgentXfer = function (targetAgentId) { UIModel.getInstance().directAgentTransferRequest = new DirectAgentTransfer( targetAgentId, 'COLD', @@ -12126,7 +12601,7 @@ export default (function() { * @memberof AgentLibrary.Call * @param {number} targetAgentId Agent id to transfer the call to */ - AgentLibrary.prototype.cancelDirectAgentXfer = function(targetAgentId) { + AgentLibrary.prototype.cancelDirectAgentXfer = function (targetAgentId) { UIModel.getInstance().directAgentTransferRequest = new DirectAgentTransfer( targetAgentId, 'CANCEL', @@ -12140,7 +12615,7 @@ export default (function() { * @memberof AgentLibrary.Call * @param {number} targetAgentId Agent id to receive the voicemail */ - AgentLibrary.prototype.voicemailDirectAgentXfer = function( + AgentLibrary.prototype.voicemailDirectAgentXfer = function ( targetAgentId, ) { UIModel.getInstance().directAgentTransferRequest = new DirectAgentTransfer( @@ -12157,7 +12632,7 @@ export default (function() { * @memberof AgentLibrary.Call * @param {number} targetAgentId Agent id to receive the voicemail */ - AgentLibrary.prototype.rejectDirectAgentXfer = function(uii) { + AgentLibrary.prototype.rejectDirectAgentXfer = function (uii) { UIModel.getInstance().directAgentTransferRequest = new DirectAgentTransfer( '0', 'REJECT', @@ -12184,7 +12659,7 @@ export default (function() { * @param {number} leadId The lead id to retrieve history for * @param {function} [callback=null] Callback function when lead history response received */ - AgentLibrary.prototype.leadHistory = function(leadId, callback) { + AgentLibrary.prototype.leadHistory = function (leadId, callback) { UIModel.getInstance().leadHistoryRequest = new LeadHistoryRequest( leadId, ); @@ -12200,7 +12675,7 @@ export default (function() { * @param {object} dataObj agentId, campaignId, and lead info * @param {function} [callback=null] Callback function when lead history response received */ - AgentLibrary.prototype.leadInsert = function(dataObj, callback) { + AgentLibrary.prototype.leadInsert = function (dataObj, callback) { UIModel.getInstance().leadInsertRequest = new LeadInsertRequest( dataObj, ); @@ -12218,7 +12693,7 @@ export default (function() { * @param {object} baggage Object containing lead information * @param {function} [callback=null] Callback function when lead history response received */ - AgentLibrary.prototype.leadUpdate = function( + AgentLibrary.prototype.leadUpdate = function ( leadId, leadPhone, baggage, @@ -12252,7 +12727,7 @@ export default (function() { * @param {string} alias The alias string to be used for agent chat messages * @deprecated */ - AgentLibrary.prototype.setChatAlias = function(alias) { + AgentLibrary.prototype.setChatAlias = function (alias) { UIModel.getInstance().chatAliasRequest = new ChatAliasRequest(alias); var msg = UIModel.getInstance().chatAliasRequest.formatJSON(); @@ -12266,7 +12741,7 @@ export default (function() { * @param {number} roomId Chat room id * @deprecated */ - AgentLibrary.prototype.publicChatRoom = function(action, roomId) { + AgentLibrary.prototype.publicChatRoom = function (action, roomId) { UIModel.getInstance().chatRoomRequest = new ChatRoomRequest( action, 'PUBLIC', @@ -12286,7 +12761,7 @@ export default (function() { * @param {number} agentTwo Id for the agent or supervisor the logged in agent is chatting with * @deprecated */ - AgentLibrary.prototype.privateChatRoom = function( + AgentLibrary.prototype.privateChatRoom = function ( action, roomId, agentOne, @@ -12312,7 +12787,7 @@ export default (function() { * @param {function} [callback=null] Callback function when chat message received * @deprecated */ - AgentLibrary.prototype.sendChat = function(roomId, message, callback) { + AgentLibrary.prototype.sendChat = function (roomId, message, callback) { UIModel.getInstance().chatSendRequest = new ChatSendRequest( roomId, message, @@ -12329,7 +12804,7 @@ export default (function() { * @param {function} [callback=null] Callback function when chat message received * @deprecated */ - AgentLibrary.prototype.getSupervisors = function(callback) { + AgentLibrary.prototype.getSupervisors = function (callback) { UIModel.getInstance().supervisorListRequest = new SupervisorListRequest(); var msg = UIModel.getInstance().supervisorListRequest.formatJSON(); @@ -12345,7 +12820,7 @@ export default (function() { * @param {string} response ACCEPT|REJECT response * @param {string} responseReason Agent reason for Reject */ - AgentLibrary.prototype.chatPresentedResponse = function( + AgentLibrary.prototype.chatPresentedResponse = function ( uii, messageId, response, @@ -12368,7 +12843,7 @@ export default (function() { * @param {string} agentId The agent associated with the chat * @param {string} message The message sent by the agent */ - AgentLibrary.prototype.chatMessage = function(uii, agentId, message) { + AgentLibrary.prototype.chatMessage = function (uii, agentId, message) { UIModel.getInstance().chatMessageRequest = new ChatMessageRequest( uii, agentId, @@ -12386,7 +12861,7 @@ export default (function() { * @param {string} agentId The agent associated with the chat * @param {string} message The message sent by the agent */ - AgentLibrary.prototype.chatWhisper = function(uii, agentId, message) { + AgentLibrary.prototype.chatWhisper = function (uii, agentId, message) { UIModel.getInstance().chatMessageRequest = new ChatMessageRequest( uii, agentId, @@ -12408,7 +12883,7 @@ export default (function() { * @param {object} [script=null] Script data associated with the chat session * @param {number} sessionId Id for chat session */ - AgentLibrary.prototype.chatDisposition = function( + AgentLibrary.prototype.chatDisposition = function ( uii, agentId, dispositionId, @@ -12437,9 +12912,9 @@ export default (function() { * @param {number} agentId The agent's id * @param {number} chatQueueId Id of the Chat Queue to requeue to * @param {number} skillId Skill id associated with the Chat Queue - * @param {boolean} [maintainAgent=fakse] Whether or not to keep the current agent connected to the chat on requeue + * @param {boolean} [maintainAgent=false] Whether or not to keep the current agent connected to the chat on requeue */ - AgentLibrary.prototype.chatRequeue = function( + AgentLibrary.prototype.chatRequeue = function ( uii, agentId, chatQueueId, @@ -12463,7 +12938,7 @@ export default (function() { * @param {string} uii Unique identifier for the chat session * @param {string} message Pending Agent message - sent to any monitoring Supervisors */ - AgentLibrary.prototype.chatTyping = function(uii, message) { + AgentLibrary.prototype.chatTyping = function (uii, message) { UIModel.getInstance().chatTypingRequest = new ChatTypingRequest( uii, message, @@ -12477,7 +12952,7 @@ export default (function() { * @memberof AgentLibrary.Chat * @param {string} monitorAgentId Agent id handling this chat, the agent being monitored */ - AgentLibrary.prototype.monitorAgentChats = function(monitorAgentId) { + AgentLibrary.prototype.monitorAgentChats = function (monitorAgentId) { UIModel.getInstance().monitorChatRequest = new MonitorChatRequest( monitorAgentId, ); @@ -12490,7 +12965,7 @@ export default (function() { * @memberof AgentLibrary.Chat * @param {string} monitorAgentId Agent id of agent being monitored */ - AgentLibrary.prototype.stopMonitoringChatsByAgent = function( + AgentLibrary.prototype.stopMonitoringChatsByAgent = function ( monitorAgentId, ) { UIModel.getInstance().stopMonitorChatRequest = new StopMonitorChatRequest( @@ -12504,7 +12979,7 @@ export default (function() { * Request to drop all chat monitoring sessions for the logged in agent * @memberof AgentLibrary.Chat */ - AgentLibrary.prototype.stopMonitoringAllAgentChats = function() { + AgentLibrary.prototype.stopMonitoringAllAgentChats = function () { UIModel.getInstance().stopMonitorChatRequest = new StopMonitorChatRequest(); var msg = UIModel.getInstance().stopMonitorChatRequest.formatJSON(); utils.sendMessage(this, msg); @@ -12517,7 +12992,7 @@ export default (function() { * @param {string} agentId Current logged in agent id * @param {string} sessionId Chat session id */ - AgentLibrary.prototype.leaveChat = function(uii, agentId, sessionId) { + AgentLibrary.prototype.leaveChat = function (uii, agentId, sessionId) { UIModel.getInstance().leaveChatRequest = new LeaveChatRequest( uii, agentId, @@ -12534,7 +13009,7 @@ export default (function() { * @param {string} monitorAgentId Agent id handling chats * @param {function} [callback=null] Callback function when chat-list request received */ - AgentLibrary.prototype.chatList = function( + AgentLibrary.prototype.chatList = function ( agentId, monitorAgentId, callback, @@ -12556,7 +13031,7 @@ export default (function() { * @param {string} agentId Current logged in agent id */ - AgentLibrary.prototype.chatAgentEnd = function(agentId, uii) { + AgentLibrary.prototype.chatAgentEnd = function (agentId, uii) { UIModel.getInstance().chatAgentEnd = new ChatAgentEndRequest( agentId, uii, @@ -12572,7 +13047,7 @@ export default (function() { * CHAT-AVAILABLE | CHAT-PRESENTED | CHAT-ENGAGED | CHAT-RNA * @param {function} [callback=null] Callback function when chatState response received */ - AgentLibrary.prototype.setChatState = function(chatState, callback) { + AgentLibrary.prototype.setChatState = function (chatState, callback) { UIModel.getInstance().chatStateRequest = new ChatStateRequest( chatState, ); @@ -12592,7 +13067,7 @@ export default (function() { * @param {string} message content */ - AgentLibrary.prototype.sendManualOutboundSms = function( + AgentLibrary.prototype.sendManualOutboundSms = function ( agentId, chatQueueId, ani, @@ -12616,37 +13091,37 @@ export default (function() { var AgentLibrary = context.AgentLibrary; - AgentLibrary.prototype.openLogger = function() { + AgentLibrary.prototype.openLogger = function () { var instance = this; if ('indexedDB' in context) { // Open database var dbRequest = indexedDB.open('AgentLibraryLogging', 6); // version number - dbRequest.onerror = function(event) { + dbRequest.onerror = function (event) { console.error('Error requesting DB access'); }; - dbRequest.onsuccess = function(event) { + dbRequest.onsuccess = function (event) { instance._db = event.target.result; //prune items older than 2 days instance.purgeLog(instance._db, 'logger'); - instance._db.onerror = function(event) { + instance._db.onerror = function (event) { // Generic error handler for all errors targeted at this database requests console.error( 'AgentLibrary: Database error - ' + event.target.errorCode, ); }; - instance._db.onsuccess = function(event) { + instance._db.onsuccess = function (event) { console.log('AgentLibrary: Successful logging of record'); }; }; // This event is only implemented in recent browsers - dbRequest.onupgradeneeded = function(event) { + dbRequest.onupgradeneeded = function (event) { instance._db = event.target.result; // Create an objectStore to hold log information. Key path should be unique @@ -12678,7 +13153,7 @@ export default (function() { * Purge records older than 2 days from the AgentLibrary log * @memberof AgentLibrary */ - AgentLibrary.prototype.purgeLog = function(db, store) { + AgentLibrary.prototype.purgeLog = function (db, store) { var instance = this; if (db) { @@ -12690,7 +13165,7 @@ export default (function() { endDate.setDate(endDate.getDate() - 2); // two days ago var range = IDBKeyRange.upperBound(endDate); - dateIndex.openCursor(range).onsuccess = function(event) { + dateIndex.openCursor(range).onsuccess = function (event) { var cursor = event.target.result; if (cursor) { objectStore.delete(cursor.primaryKey); @@ -12707,31 +13182,31 @@ export default (function() { * Clear the AgentLibrary log by emptying the IndexedDB object store * @memberof AgentLibrary */ - AgentLibrary.prototype.clearLog = function(db) { + AgentLibrary.prototype.clearLog = function (db) { var transaction = db.transaction(['logger'], 'readwrite'); var objectStore = transaction.objectStore('logger'); var objectStoreRequest = objectStore.clear(); - objectStoreRequest.onsuccess = function(event) { + objectStoreRequest.onsuccess = function (event) { console.log('AgentLibrary: logger database cleared'); }; }; - AgentLibrary.prototype.deleteDB = function(dbName) { + AgentLibrary.prototype.deleteDB = function (dbName) { dbName = dbName || 'AgentLibraryLogging'; var DBDeleteRequest = indexedDB.deleteDatabase(dbName); - DBDeleteRequest.onerror = function(event) { + DBDeleteRequest.onerror = function (event) { console.log('Error deleting database.', dbName); }; - DBDeleteRequest.onsuccess = function(event) { + DBDeleteRequest.onsuccess = function (event) { console.log('Database', dbName, 'deleted successfully'); }; }; - AgentLibrary.prototype.getLogRecords = function( + AgentLibrary.prototype.getLogRecords = function ( logLevel, startDate, endDate, @@ -12769,7 +13244,7 @@ export default (function() { var levelAndDateReturn = []; var idxLevelAndDate = 0; index = objStore.index('levelAndDate'); - index.openCursor(range, 'prev').onsuccess = function(event) { + index.openCursor(range, 'prev').onsuccess = function (event) { cursor = event.target.result; if (cursor && idxLevelAndDate < limit) { levelAndDateReturn.push(cursor.value); @@ -12788,7 +13263,7 @@ export default (function() { var logLevelReturn = []; var idxLogLevel = 0; index = objStore.index('logLevel'); - index.openCursor(logLevel, 'prev').onsuccess = function(event) { + index.openCursor(logLevel, 'prev').onsuccess = function (event) { cursor = event.target.result; if (cursor && idxLogLevel < limit) { logLevelReturn.push(cursor.value); @@ -12819,7 +13294,7 @@ export default (function() { var idxDTSNoStats = 0; index = objStore.index('dts'); - index.openCursor(range, 'prev').onsuccess = function(event) { + index.openCursor(range, 'prev').onsuccess = function (event) { cursor = event.target.result; if (cursor && idxDTSNoStats < limit) { if (cursor.value.logLevel !== 'stats') { @@ -12839,7 +13314,7 @@ export default (function() { // no date range specified, return all records var noStatsReturn = []; var idxNoStats = 0; - objStore.openCursor().onsuccess = function(event) { + objStore.openCursor().onsuccess = function (event) { cursor = event.target.result; if (cursor && idxNoStats < limit) { if (cursor.value.logLevel !== 'stats') { @@ -12872,7 +13347,7 @@ export default (function() { var idxDTS = 0; index = objStore.index('dts'); - index.openCursor(range, 'prev').onsuccess = function(event) { + index.openCursor(range, 'prev').onsuccess = function (event) { cursor = event.target.result; if (cursor && idxDTS < limit) { dtsReturn.push(cursor.value); @@ -12890,7 +13365,7 @@ export default (function() { // no date range specified, return all records var allValsReturn = []; var idxAll = 0; - objStore.openCursor().onsuccess = function(event) { + objStore.openCursor().onsuccess = function (event) { cursor = event.target.result; if (cursor && idxAll < limit) { allValsReturn.push(cursor.value); @@ -12924,7 +13399,7 @@ export default (function() { /** * Method to request call details from other tabs using the broadcast channel, for multisocket */ - AgentLibrary.prototype.loadCurrentCall = function(callback) { + AgentLibrary.prototype.loadCurrentCall = function (callback) { broadcastChannelHelper.requestCurrentCall(callback); }; } @@ -12933,30 +13408,30 @@ export default (function() { var AgentLibrary = context.AgentLibrary; - AgentLibrary.prototype.openConsoleLogger = function() { + AgentLibrary.prototype.openConsoleLogger = function () { var instance = this; if ('indexedDB' in context) { var dbRequest = indexedDB.open('AgentLibraryConsoleLogging', 1); - dbRequest.onerror = function(event) { + dbRequest.onerror = function (event) { console.error('Error requesting DB access'); }; - dbRequest.onsuccess = function(event) { + dbRequest.onsuccess = function (event) { instance._consoleDb = event.target.result; //prune items older than 2 days instance.purgeLog(instance._consoleDb, 'consoleLogger'); - instance._consoleDb.onerror = function(event) { + instance._consoleDb.onerror = function (event) { // Generic error handler for all errors targeted at this database requests console.error( 'AgentLibrary: Database error - ' + event.target.errorCode, ); }; - instance._consoleDb.onsuccess = function(event) { + instance._consoleDb.onsuccess = function (event) { console.log('AgentLibrary: Successful logging of record'); }; @@ -12964,7 +13439,7 @@ export default (function() { }; // This event is only implemented in recent browsers - dbRequest.onupgradeneeded = function(event) { + dbRequest.onupgradeneeded = function (event) { instance._consoleDb = event.target.result; // Create an objectStore to hold log information. Key path should be unique @@ -12996,7 +13471,7 @@ export default (function() { } }; - AgentLibrary.prototype.getConsoleLogRecords = function(type, callback) { + AgentLibrary.prototype.getConsoleLogRecords = function (type, callback) { var agentId = UIModel.getInstance().agentSettings.agentId; // only return records for this agent id var instance = this; var transaction = instance._consoleDb.transaction( @@ -13026,7 +13501,7 @@ export default (function() { } var count = 0; - index.openCursor(range, 'prev').onsuccess = function(event) { + index.openCursor(range, 'prev').onsuccess = function (event) { cursor = event.target.result; if (cursor && count < limit) { result.push(cursor.value); @@ -13045,7 +13520,7 @@ export default (function() { function _overrideConsole() { // override the window.console functions, process as normal then save to the local db var browserConsole = Object.assign({}, window.console); - (function(defaultConsole) { + (function (defaultConsole) { var instance; if ( UIModel && @@ -13096,19 +13571,19 @@ export default (function() { } } - window.console.log = function() { + window.console.log = function () { defaultConsole.log.apply(this, arguments); _saveRecord('LOG', arguments); }; - window.console.info = function() { + window.console.info = function () { defaultConsole.info.apply(this, arguments); _saveRecord('INFO', arguments); }; - window.console.warn = function() { + window.console.warn = function () { defaultConsole.warn.apply(this, arguments); _saveRecord('WARN', arguments); }; - window.console.error = function() { + window.console.error = function () { defaultConsole.error.apply(this, arguments); _saveRecord('ERROR', arguments); }; @@ -13135,7 +13610,7 @@ export default (function() { * Initializes the SIP library, sets up callback functions * @memberof AgentLibrary.Softphone */ - AgentLibrary.prototype.sipInit = function() { + AgentLibrary.prototype.sipInit = function () { var model = UIModel.getInstance(); var softphoneSettings = model.softphoneSettings; @@ -13178,7 +13653,7 @@ export default (function() { * @memberof AgentLibrary.Softphone * @returns boolean - success state */ - AgentLibrary.prototype.sipTerminate = function() { + AgentLibrary.prototype.sipTerminate = function () { var model = UIModel.getInstance(); var webRtc = model.softphoneSettings.webRtc; if (webRtc && webRtc.ua) { @@ -13202,6 +13677,7 @@ export default (function() { webRtc = null; model.softphoneSettings.webRtc = webRtc; // set back on model model.softphoneSettings.isRegistered = false; + model.softphoneSettings.attemptingSoftphoneReconnect = false; model.softphoneSettings.muteActive = false; model.softphoneSettings.registerPending = null; model.softphoneSettings.uri = ''; @@ -13217,7 +13693,7 @@ export default (function() { * Sends a session.accept response to a SIP invite event. * @memberof AgentLibrary.Softphone */ - AgentLibrary.prototype.sipAnswer = function() { + AgentLibrary.prototype.sipAnswer = function () { var webRtc = UIModel.getInstance().softphoneSettings.webRtc; if (webRtc) { webRtc.answer(); @@ -13228,7 +13704,7 @@ export default (function() { * Sends a session.reject response to a SIP invite event. * @memberof AgentLibrary.Softphone */ - AgentLibrary.prototype.sipReject = function() { + AgentLibrary.prototype.sipReject = function () { var webRtc = UIModel.getInstance().softphoneSettings.webRtc; if (webRtc && webRtc.reject) { webRtc.reject(); @@ -13239,7 +13715,7 @@ export default (function() { * Request microphone access, if already registered, call hangup * @memberof AgentLibrary.Softphone */ - AgentLibrary.prototype.sipRegister = function() { + AgentLibrary.prototype.sipRegister = function () { var model = UIModel.getInstance(); var webRtc = model.softphoneSettings.webRtc; @@ -13269,7 +13745,7 @@ export default (function() { * Sends session.cancel if connected, or session.bye if not connected to a call * @memberof AgentLibrary.Softphone */ - AgentLibrary.prototype.sipHangUp = function() { + AgentLibrary.prototype.sipHangUp = function () { var webRtc = UIModel.getInstance().softphoneSettings.webRtc; if (webRtc && webRtc.hangup) { webRtc.hangup(); @@ -13281,7 +13757,7 @@ export default (function() { * @memberof AgentLibrary.Softphone * @param {string} dtmf The dtmf tone to send */ - AgentLibrary.prototype.sipSendDTMF = function(dtmf) { + AgentLibrary.prototype.sipSendDTMF = function (dtmf) { var webRtc = UIModel.getInstance().softphoneSettings.webRtc; if (webRtc) { webRtc.sendDTMF(dtmf); @@ -13293,7 +13769,7 @@ export default (function() { * @memberof AgentLibrary.Softphone * @param {boolean} state The dtmf tone to send */ - AgentLibrary.prototype.sipToggleMute = function(state) { + AgentLibrary.prototype.sipToggleMute = function (state) { var softphoneSettings = UIModel.getInstance().softphoneSettings; var webRtc = softphoneSettings.webRtc; var muteActive = softphoneSettings.muteActive; @@ -13317,7 +13793,7 @@ export default (function() { } }; - AgentLibrary.prototype.switchSoftphoneRegistrar = function( + AgentLibrary.prototype.switchSoftphoneRegistrar = function ( maintainOffhook, autoStartOffhook, ) { @@ -13352,7 +13828,7 @@ export default (function() { } }; - AgentLibrary.prototype.resetSoftphoneSession = function(offhookParams) { + AgentLibrary.prototype.resetSoftphoneSession = function (offhookParams) { var model = UIModel.getInstance(); var softphoneSettings = model.softphoneSettings; if ( @@ -13360,18 +13836,21 @@ export default (function() { !softphoneSettings.attemptingSoftphoneReconnect ) { model.libraryInstance.sipTerminate(); // clear current SIP.js object - _rotateWebRtcServer(); - SoftphoneService.setupWebRtcServer(); - - if (offhookParams) { - if (offhookParams.maintainOH === 'null') { - offhookParams.maintainOH = softphoneSettings.maintainOH; // default to model setting + _rotateWebRtcServer(function () { + SoftphoneService.setupWebRtcServer(); + if (offhookParams) { + if (offhookParams.maintainOH === 'null') { + offhookParams.maintainOH = softphoneSettings.maintainOH; // default to model setting + } + _updateOHFlags( + offhookParams.maintainOH, + offhookParams.autoStartOH, + ); } - _updateOHFlags(offhookParams.maintainOH, offhookParams.autoStartOH); - } - model.libraryInstance.sipInit(); - model.libraryInstance.sipRegister(); + model.libraryInstance.sipInit(); + model.libraryInstance.sipRegister(); + }); } }; @@ -13381,7 +13860,7 @@ export default (function() { /* These functions are globally available to the AgentSDK app */ var SoftphoneService = { - setupWebRtcServer: function() { + setupWebRtcServer: function () { var model = UIModel.getInstance(); var softphoneSettings = model.softphoneSettings; @@ -13477,6 +13956,7 @@ export default (function() { // we want to force a registrar refresh softphoneSettings.isRegistered = true; + softphoneSettings.attemptingSoftphoneReconnect = false; model.libraryInstance.switchSoftphoneRegistrar( softphoneSettings.maintainOH, @@ -13489,12 +13969,40 @@ export default (function() { clearTimeout(UIModel.getInstance().softphoneSettings.registerPending); } - function _rotateWebRtcServer() { + function _rotateWebRtcServer(callback) { var softphoneSettings = UIModel.getInstance().softphoneSettings; + utils.setCallback( + UIModel.getInstance().libraryInstance, + CALLBACK_TYPES.ROTATE_WEBRTC_SERVER, + callback, + ); var sipInfo = softphoneSettings.sipInfo; - if (sipInfo.length > 1) { - sipInfo.push(sipInfo.shift()); + var timeSinceLastFetch = + Date.now() - softphoneSettings.lastSipFetchTime; + console.log( + '## Checking to rotate webRtc Server.SipInfo array length is ' + + sipInfo.length + + '. Time since last fetch of webRtcInfo is ' + + timeSinceLastFetch / 60000 + + ' mins.', + ); + if (sipInfo.length > 1 && timeSinceLastFetch < 5 * 60000) { + sipInfo.shift(); + console.log( + '## rotated sip server.Will now attempt to new server: ' + + sipInfo[0].domain, + ); softphoneSettings.attemptingSoftphoneReconnect = true; + utils.fireCallback( + UIModel.getInstance().libraryInstance, + CALLBACK_TYPES.ROTATE_WEBRTC_SERVER, + callback, + ); + } else { + console.log('## fetching webRtcInfo.'); + // fetch Sip Registration info again if array is consumed to rotate registrar + var model = UIModel.getInstance(); + model.libraryInstance.getWebRtcInfo(callback); } } @@ -13599,6 +14107,23 @@ export default (function() { } function _ringing(notif) { + var model = UIModel.getInstance(); + var usingRcPhone = + model.agentSettings.dialDest.indexOf('@RC_SOFTPHONE') > -1; + + var rcHeader = + notif.transaction && + notif.transaction.request && + notif.transaction.request.headers['P-Rc-Call-Source']; + + var rcHeaderValue = + rcHeader && rcHeader.length === 1 && rcHeader[0].raw; + + // do not notify the app of ringing if Rc Call Source is not 'partner-contact-center' + if (usingRcPhone && rcHeaderValue !== 'partner-contact-center') { + return; + } + utils.fireCallback( UIModel.getInstance().libraryInstance, CALLBACK_TYPES.SIP_RINGING, @@ -13615,7 +14140,7 @@ export default (function() { } } - var initAgentLibrary = function(context) { + var initAgentLibrary = function (context) { initAgentLibraryCore(context); initAgentLibrarySocket(context); initAgentLibraryAgent(context); @@ -13629,6 +14154,7 @@ export default (function() { return context.AgentLibrary; }; + return initAgentLibrary(this); }.call(this, this); }.call(window)); diff --git a/packages/engage-voice-widget/lib/EvClient/interfaces/EvSdkResponse.interface.ts b/packages/engage-voice-widget/lib/EvClient/interfaces/EvSdkResponse.interface.ts index d57b3c6aea..f9e9d38170 100644 --- a/packages/engage-voice-widget/lib/EvClient/interfaces/EvSdkResponse.interface.ts +++ b/packages/engage-voice-widget/lib/EvClient/interfaces/EvSdkResponse.interface.ts @@ -205,6 +205,7 @@ export interface EvAddSessionNotification { monitoring: boolean; agentId: string; agentName: string; + recordingUrl: string; transferSessions: { [P: string]: { sessionId: string; diff --git a/packages/engage-voice-widget/lib/contactMatchIdentify.ts b/packages/engage-voice-widget/lib/contactMatchIdentify.ts index c6ff0a468b..f2be163d8c 100644 --- a/packages/engage-voice-widget/lib/contactMatchIdentify.ts +++ b/packages/engage-voice-widget/lib/contactMatchIdentify.ts @@ -12,7 +12,7 @@ const separator = '_'; export const contactMatchIdentifyEncode = ({ phoneNumber, callType, -}: ContactMatchQuery) => +}: ContactMatchQuery) => `${phoneNumber}${separator}${callType}`.toLocaleLowerCase(); export const contactMatchIdentifyDecode = (identify: string) => { diff --git a/packages/engage-voice-widget/modules/EvActivityCallUI/EvActivityCallUI.interface.ts b/packages/engage-voice-widget/modules/EvActivityCallUI/EvActivityCallUI.interface.ts index fa5fa70b42..0249fa5dfa 100644 --- a/packages/engage-voice-widget/modules/EvActivityCallUI/EvActivityCallUI.interface.ts +++ b/packages/engage-voice-widget/modules/EvActivityCallUI/EvActivityCallUI.interface.ts @@ -2,7 +2,6 @@ import Alert from 'ringcentral-integration/modules/Alert'; import ConnectivityMonitor from 'ringcentral-integration/modules/ConnectivityMonitor'; import Locale from 'ringcentral-integration/modules/Locale'; import RateLimiter from 'ringcentral-integration/modules/RateLimiter'; -import Storage from 'ringcentral-integration/modules/Storage'; import RouterInteraction from 'ringcentral-widgets/modules/RouterInteraction'; import { EvTabManager } from '../EvTabManager'; @@ -18,6 +17,7 @@ import { EvIntegratedSoftphone } from '../EvIntegratedSoftphone'; import { EvRequeueCall } from '../EvRequeueCall'; import { EvTransferCall } from '../EvTransferCall'; import { EvWorkingState } from '../EvWorkingState'; +import { EvStorage } from '../EvStorage'; export interface State { saveStatus: string; @@ -47,7 +47,7 @@ export interface Deps { connectivityMonitor: ConnectivityMonitor; rateLimiter: RateLimiter; environment: EvEnvironment; - storage: Storage; + storage: EvStorage; evAuth: EvAuth; tabManager?: EvTabManager; evActivityCallUIOptions?: EvActivityCallUIOptions; diff --git a/packages/engage-voice-widget/modules/EvActivityCallUI/EvActivityCallUI.ts b/packages/engage-voice-widget/modules/EvActivityCallUI/EvActivityCallUI.ts index 72c5181a0e..a4decfac92 100644 --- a/packages/engage-voice-widget/modules/EvActivityCallUI/EvActivityCallUI.ts +++ b/packages/engage-voice-widget/modules/EvActivityCallUI/EvActivityCallUI.ts @@ -10,6 +10,7 @@ import { Module } from 'ringcentral-integration/lib/di'; import { CallLogPanelProps } from 'ringcentral-widgets/components/CallLogPanel'; import { + dialoutStatuses, EvTransferType, logTypes, MessageTypes, @@ -21,6 +22,9 @@ import { EvActivityCallUIFunctions, EvActivityCallUIProps, EvCurrentLog, + CallLogMethods, + SaveStatus, + saveStatus, } from '../../interfaces/EvActivityCallUI.interface'; import { EvIvrData } from '../../interfaces/EvData.interface'; import { EvBaggage } from '../../lib/EvClient'; @@ -97,7 +101,7 @@ class EvActivityCallUI @storage @state - saveStatus: EvActivityCallUIProps['saveStatus'] = 'submit'; + saveStatus: SaveStatus | CallLogMethods = saveStatus.submit; @storage @state @@ -391,7 +395,7 @@ class EvActivityCallUI } @action - changeSavingStatus(status: EvActivityCallUIProps['saveStatus']) { + changeSavingStatus(status: SaveStatus | CallLogMethods) { this.saveStatus = status; } @@ -432,7 +436,7 @@ class EvActivityCallUI notes: false, }; this.disabled = {}; - this.saveStatus = 'submit'; + this.saveStatus = saveStatus.submit; } onStateChange() { @@ -492,8 +496,11 @@ class EvActivityCallUI this._deps.routerInteraction.push(`/activityCallLog/${this.callId}${url}`); } - goDialer() { - this._deps.routerInteraction.push('/dialer'); + goBack() { + // set status to 'idle' in case of EvCallMonitor does not emit ENDED + this._deps.evCall.setDialoutStatus(dialoutStatuses.idle); + + this._deps.routerInteraction.goBack(); this.reset(); this._deps.evCall.activityCallId = null; } @@ -546,7 +553,7 @@ class EvActivityCallUI if (this._hasError()) { return; } - this.changeSavingStatus('saving'); + this.changeSavingStatus(saveStatus.saving); await this.disposeCall(); this._sendTabManager(tabManagerEvents.CALL_DISPOSITION_SUCCESS); @@ -556,19 +563,19 @@ class EvActivityCallUI message: logTypes.CALL_DISPOSITION_FAILURE, ttl: 0, }); - this.changeSavingStatus('submit'); + this.changeSavingStatus(saveStatus.submit); throw e; } } private _dispositionSuccess() { - this.changeSavingStatus('saved'); + this.changeSavingStatus(saveStatus.saved); this._deps.alert.success({ message: logTypes.CALL_DISPOSITION_SUCCESS, }); // delay for animation with loading ui. - setTimeout(() => this.goDialer(), 1000); + setTimeout(() => this.goBack(), 1000); this._deps.evWorkingState.setIsPendingDisposition(false); } @@ -602,7 +609,8 @@ class EvActivityCallUI isInComingCall: this.isInComingCall, smallCallControlSize: this._deps.environment.isWide ? 'medium' : 'small', currentCallControlPermission: this.currentCallControlPermission, - disableDispose: this.disableLinks || this.saveStatus === 'saving', + disableDispose: + this.disableLinks || this.saveStatus === saveStatus.saving, disableTransfer: this.disableLinks || this.isInComingCall || !this.allowTransfer, disableInternalTransfer: @@ -626,7 +634,7 @@ class EvActivityCallUI getUIFunctions(): EvActivityCallUIFunctions { return { - goBack: () => {}, + goBack: () => this.goBack(), onMute: () => this._deps.activeCallControl.mute(), onUnmute: () => this._deps.activeCallControl.unmute(), onHangup: () => @@ -639,8 +647,8 @@ class EvActivityCallUI onActive: () => this.goToActivityCallListPage(), onUpdateCallLog: (data, id) => this.onUpdateCallLog(data, id), disposeCall: async () => { - if (this.saveStatus === 'saved') { - return this.goDialer(); + if (this.saveStatus === saveStatus.saved) { + return this.goBack(); } await this._submitData(this.callId); }, diff --git a/packages/engage-voice-widget/modules/EvAgentSession/EvAgentSession.interface.ts b/packages/engage-voice-widget/modules/EvAgentSession/EvAgentSession.interface.ts index 14bc8e16c3..72d40d9ba3 100644 --- a/packages/engage-voice-widget/modules/EvAgentSession/EvAgentSession.interface.ts +++ b/packages/engage-voice-widget/modules/EvAgentSession/EvAgentSession.interface.ts @@ -1,7 +1,6 @@ import Alert from 'ringcentral-integration/modules/Alert'; import { Auth } from 'ringcentral-integration/modules/AuthV2'; import Locale from 'ringcentral-integration/modules/Locale'; -import Storage from 'ringcentral-integration/modules/Storage'; import { Beforeunload } from 'ringcentral-widgets/modules/Beforeunload'; import { Block } from 'ringcentral-widgets/modules/Block'; import { ModalUI } from 'ringcentral-widgets/modules/ModalUIV2'; @@ -11,6 +10,7 @@ import { LoginTypes } from '../../enums'; import { EvClient } from '../../lib/EvClient'; import { EvAuth } from '../EvAuth'; import { EvPresence } from '../EvPresence'; +import { EvStorage } from '../EvStorage'; import { EvTabManager } from '../EvTabManager'; export interface EvAgentSessionOptions {} @@ -29,7 +29,7 @@ export type FormGroup = Partial< export interface Deps { evAuth: EvAuth; evClient: EvClient; - storage: Storage; + storage: EvStorage; alert: Alert; auth: Auth; modalUI: ModalUI; diff --git a/packages/engage-voice-widget/modules/EvAgentSession/EvAgentSession.ts b/packages/engage-voice-widget/modules/EvAgentSession/EvAgentSession.ts index 0a01592893..c76435894d 100644 --- a/packages/engage-voice-widget/modules/EvAgentSession/EvAgentSession.ts +++ b/packages/engage-voice-widget/modules/EvAgentSession/EvAgentSession.ts @@ -5,6 +5,7 @@ import { state, storage, track, + watch, } from '@ringcentral-integration/core'; import { format, parse } from '@ringcentral-integration/phone-number'; import { EventEmitter } from 'events'; @@ -22,11 +23,7 @@ import { tabManagerEvents, } from '../../enums'; import { LoginType } from '../../interfaces/EvAgentSessionUI.interface'; -import { - EvAgentConfig, - EvAvailableSkillProfile, - EvConfigureAgentOptions, -} from '../../lib/EvClient'; +import { EvConfigureAgentOptions } from '../../lib/EvClient'; import { TabLife } from '../../lib/tabLife'; import { trackEvents } from '../../lib/trackEvents'; import { AgentSession, Deps, FormGroup } from './EvAgentSession.interface'; @@ -71,7 +68,6 @@ type ConfigureAgentParams = { 'EvClient', 'Auth', 'EvAuth', - 'Storage', 'Alert', 'Auth', 'Locale', @@ -80,6 +76,7 @@ type ConfigureAgentParams = { 'ModalUI', 'Block', 'Beforeunload', + 'Storage', { dep: 'TabManager', optional: true }, { dep: 'EvAgentSessionOptions', optional: true }, ], @@ -219,7 +216,7 @@ class EvAgentSession extends RcModuleV2 implements AgentSession { } @computed((that: EvAgentSession) => [ - that._deps.evAuth.agent, + that._deps.evAuth.agentConfig, that._deps.auth.isFreshLogin, ]) get inboundQueues() { @@ -600,22 +597,41 @@ class EvAgentSession extends RcModuleV2 implements AgentSession { }, 3000); } + @computed((that) => [ + that._deps.evAuth.isEvLogged, + that.ready, + ]) + get isOnLoginSuccess() { + return this.ready && this._deps.evAuth.isEvLogged; + } + private async _init() { if (this._isLogin) { - this._initTabLife(); - await this._initAgentSession(); + await this.initAgentSession(); } // ! that must call after onInitOnce, because when that is not in init once, // ! that configured will some times to be false because storage block - this._deps.evAuth.onLoginSuccess(() => { - // when that is seconds time get onLoginSuccess - console.log('----------onLoginSuccess2'); + watch( + this, + () => this.isOnLoginSuccess, + async (isOnLoginSuccess) => { + if (isOnLoginSuccess) { + // when that is seconds time get onLoginSuccess + console.log('----------onLoginSuccess2'); + await this.initAgentSession(); + } + }, + ); + } + + async initAgentSession() { + await this._deps.block.next(async () => { this._initTabLife(); - this._initAgentSession(); + await this._initAgentSession(); }); } - private _initAgentSession() { + private async _initAgentSession() { console.log('_initAgentSession~', this.isAgentUpdating); if (this.isAgentUpdating) { return; @@ -915,7 +931,11 @@ class EvAgentSession extends RcModuleV2 implements AgentSession { voiceConnectionChanged, ); - if (voiceConnectionChanged) await this.reLoginAgent(); + const extensionNumberChanged = + this.extensionNumber !== this.formGroup.extensionNumber; + + if (voiceConnectionChanged || extensionNumberChanged) + await this.reLoginAgent(); config.isForce = true; const result = await this._connectEvServer(config); @@ -1188,7 +1208,7 @@ class EvAgentSession extends RcModuleV2 implements AgentSession { } private async _connectEvServer(config: EvConfigureAgentOptions) { - console.log('configure ev agent in _connectEvServer~~'); + console.log('configure ev agent in _connectEvServer~~', config); let result = await this._deps.evClient.configureAgent(config); const { status } = result.data; diff --git a/packages/engage-voice-widget/modules/EvAgentSessionUI/EvAgentSessionUI.interface.ts b/packages/engage-voice-widget/modules/EvAgentSessionUI/EvAgentSessionUI.interface.ts index 3ab0466ada..94b0ee9dff 100644 --- a/packages/engage-voice-widget/modules/EvAgentSessionUI/EvAgentSessionUI.interface.ts +++ b/packages/engage-voice-widget/modules/EvAgentSessionUI/EvAgentSessionUI.interface.ts @@ -1,5 +1,4 @@ import Locale from 'ringcentral-integration/modules/Locale'; -import Storage from 'ringcentral-integration/modules/Storage'; import { ModalUI } from 'ringcentral-widgets/modules/ModalUIV2'; import { Block } from 'ringcentral-widgets/modules/Block'; import RouterInteraction from 'ringcentral-widgets/modules/RouterInteraction'; @@ -10,6 +9,7 @@ import { EvAuth } from '../EvAuth'; import { EvSettings } from '../EvSettings'; import { EvWorkingState } from '../EvWorkingState'; import { EvTabManager } from '../EvTabManager'; +import { EvStorage } from '../EvStorage'; export interface State { isLoading: boolean; @@ -26,7 +26,7 @@ export interface Deps { evSettings: EvSettings; evWorkingState: EvWorkingState; evAgentSession: EvAgentSession; - storage: Storage; + storage: EvStorage; evAgentSessionUIOptions?: EvAgentSessionUIOptions; modalUI: ModalUI; block: Block; diff --git a/packages/engage-voice-widget/modules/EvAgentSessionUI/EvAgentSessionUI.ts b/packages/engage-voice-widget/modules/EvAgentSessionUI/EvAgentSessionUI.ts index 7e76d4dd33..2ce88b002a 100644 --- a/packages/engage-voice-widget/modules/EvAgentSessionUI/EvAgentSessionUI.ts +++ b/packages/engage-voice-widget/modules/EvAgentSessionUI/EvAgentSessionUI.ts @@ -19,7 +19,6 @@ import { EvAgentSessionUIProps, } from '../../interfaces/EvAgentSessionUI.interface'; import { AvailableQueue } from '../../interfaces/SelectableQueue.interface'; -import { EvAgent } from '../../lib/EvClient'; import { sortByName } from '../../lib/sortByName'; import { Deps, SessionConfigUI } from './EvAgentSessionUI.interface'; import i18n from './i18n'; @@ -256,9 +255,11 @@ class EvAgentSessionUI extends RcUIModuleV2 implements SessionConfigUI { } async _onAccountReChoose(syncAllTabs = false) { + console.log('_onAccountReChoose~~', syncAllTabs); await this._deps.block.next(async () => { - if (syncAllTabs) { + if (syncAllTabs && this._deps.tabManager.hasMultipleTabs) { this._deps.tabManager.send(tabManagerEvents.RE_CHOOSE_ACCOUNT); + this._deps.storage.resetStorage(); } if (this._deps.evClient.ifSocketExist) { this._deps.evClient.closeSocket(); diff --git a/packages/engage-voice-widget/modules/EvAuth/EvAuth.interface.ts b/packages/engage-voice-widget/modules/EvAuth/EvAuth.interface.ts index 0672cfcd93..a6c332c32c 100644 --- a/packages/engage-voice-widget/modules/EvAuth/EvAuth.interface.ts +++ b/packages/engage-voice-widget/modules/EvAuth/EvAuth.interface.ts @@ -1,11 +1,10 @@ import Alert from 'ringcentral-integration/modules/Alert'; import { Auth as RcAuth } from 'ringcentral-integration/modules/AuthV2'; import Locale from 'ringcentral-integration/modules/Locale'; -import Storage from 'ringcentral-integration/modules/Storage'; import { Block } from 'ringcentral-widgets/modules/Block'; import RouterInteraction from 'ringcentral-widgets/modules/RouterInteraction'; -import { EvAgentData, EvClient } from '../../lib/EvClient'; +import { EvAgentData, EvClient, EvTokenType } from '../../lib/EvClient'; import { EvSubscription } from '../EvSubscription'; import { EvTabManager } from '../EvTabManager'; @@ -15,7 +14,6 @@ export interface EvAuthOptions { export interface Deps { locale: Locale; - storage: Storage; alert: Alert; routerInteraction: RouterInteraction; evClient: EvClient; @@ -36,3 +34,9 @@ export interface Auth extends State { disconnecting?: boolean; setConnectionData(connection: State): void; } + +export interface AuthenticateWithTokenType { + rcAccessToken?: string; + tokenType?: EvTokenType; + shouldEmitAuthSuccess?: boolean; +} diff --git a/packages/engage-voice-widget/modules/EvAuth/EvAuth.ts b/packages/engage-voice-widget/modules/EvAuth/EvAuth.ts index e5e393829d..e785cc1169 100644 --- a/packages/engage-voice-widget/modules/EvAuth/EvAuth.ts +++ b/packages/engage-voice-widget/modules/EvAuth/EvAuth.ts @@ -3,22 +3,28 @@ import { computed, RcModuleV2, state, - storage, track, globalStorage, + watch, } from '@ringcentral-integration/core'; import format from '@ringcentral-integration/phone-number/lib/format'; import { EventEmitter } from 'events'; +import { Unsubscribe } from 'redux'; import { Module } from 'ringcentral-integration/lib/di'; import sleep from 'ringcentral-integration/lib/sleep'; -import { authStatus, messageTypes, tabManagerEvents } from '../../enums'; -import { EvAgentConfig, EvAgentData, EvTokenType } from '../../lib/EvClient'; +import { loginStatus, messageTypes, tabManagerEvents } from '../../enums'; +import { EvAgentConfig, EvAgentData } from '../../lib/EvClient'; import { EvCallbackTypes } from '../../lib/EvClient/enums'; import { EvTypeError } from '../../lib/EvTypeError'; import { sortByName } from '../../lib/sortByName'; import { trackEvents } from '../../lib/trackEvents'; -import { Auth, Deps, State } from './EvAuth.interface'; +import { + Auth, + Deps, + State, + AuthenticateWithTokenType, +} from './EvAuth.interface'; import i18n from './i18n'; const DEFAULT_COUNTRIES = ['USA', 'CAN']; @@ -28,7 +34,6 @@ const DEFAULT_COUNTRIES = ['USA', 'CAN']; deps: [ 'EvClient', 'Auth', - 'Storage', 'Block', 'Alert', 'Locale', @@ -46,12 +51,19 @@ class EvAuth extends RcModuleV2 implements Auth { public canUserLogoutFn: () => Promise = async () => true; - private _logout = () => { - return this._deps.auth.logout({ dismissAllAlert: false }); + private _logout = async () => { + await this._deps.auth.logout({ dismissAllAlert: false }); + this.setNotAuth(); + if (this.tabManagerEnabled) { + this._deps.tabManager.send(tabManagerEvents.LOGGED_OUT); + } }; private _logoutByOtherTab = false; + private _authenticateResponseWatcher: Unsubscribe = null; + private _agentConfigWatcher: Unsubscribe = null; + get tabManagerEnabled() { return this._deps.tabManager?.enable; } @@ -63,17 +75,16 @@ class EvAuth extends RcModuleV2 implements Auth { constructor(deps: Deps) { super({ deps, - enableCache: true, storageKey: 'EvAuth', enableGlobalCache: true, }); } - @storage + @globalStorage @state connected = false; - @storage + @globalStorage @state agent: EvAgentData = null; @@ -82,11 +93,8 @@ class EvAuth extends RcModuleV2 implements Auth { agentId = ''; @action - setAgentId(agentId: string, syncTabs = false) { + setAgentId(agentId: string) { this.agentId = agentId; - if (syncTabs) { - this._deps.tabManager.send(tabManagerEvents.SET_AGENT_ID, agentId); - } } get isFreshLogin() { @@ -102,11 +110,11 @@ class EvAuth extends RcModuleV2 implements Auth { } get agentSettings() { - return this.agentConfig.agentSettings; + return this.agentConfig?.agentSettings; } get outboundManualDefaultRingtime() { - return this.agentSettings.outboundManualDefaultRingtime; + return this.agentSettings?.outboundManualDefaultRingtime; } get inboundSettings() { @@ -126,7 +134,7 @@ class EvAuth extends RcModuleV2 implements Auth { } get agentPermissions() { - return this.agentConfig.agentPermissions; + return this.agentConfig?.agentPermissions; } @computed((that: EvAuth) => [that.inboundSettings.availableQueues]) @@ -171,7 +179,7 @@ class EvAuth extends RcModuleV2 implements Auth { that._deps.locale.currentLocale, ]) get availableCountries() { - const { availableCountries } = this.agentConfig.applicationSettings; + const { availableCountries } = this.agentConfig?.applicationSettings; // The default Engage Voice service area is `USA` and `CAN` with `+1` international code. const countriesUsaCan = availableCountries.filter(({ countryId }) => DEFAULT_COUNTRIES.includes(countryId), @@ -208,35 +216,52 @@ class EvAuth extends RcModuleV2 implements Auth { this.connected = connected; } + @action + setAgent(agent: EvAgentData) { + this.agent = agent; + } + @action setConnected(connected: boolean) { this.connected = connected; } @action - setAgent(agent: EvAgentData) { - this.agent = agent; + clearAgentId() { + this.agentId = ''; } + @state + loginStatus: string = null; + @action - clearAgentId(syncTabs = false) { - this.agentId = ''; - if (syncTabs) { - this._deps.tabManager.send(tabManagerEvents.SET_AGENT_ID, ''); - } + setAuthSuccess() { + this.loginStatus = loginStatus.AUTH_SUCCESS; } - _shouldInit() { - return super._shouldInit() && this._deps.auth.loggedIn && this.connected; + @action + setLoginSuccess() { + this.loginStatus = loginStatus.LOGIN_SUCCESS; + } + + get isEvLogged() { + return this.loginStatus === loginStatus.LOGIN_SUCCESS; } - onBeforeRCLogout() { - console.log('_onBeforeRCLogout~'); - this.clearAgentId(); + @action + setNotAuth() { + this.loginStatus = loginStatus.NOT_AUTH; + } + + _shouldInit() { + return super._shouldInit() && this._deps.auth.loggedIn && this.connected; } onInitOnce() { - this._deps.auth.addBeforeLogoutHandler(() => this.onBeforeRCLogout()); + this._deps.auth.addAfterLoggedInHandler(() => { + console.log('addAfterLoggedInHandler~~'); + this.clearAgentId(); + }); this._deps.evSubscription.subscribe(EvCallbackTypes.LOGOUT, async () => { this._emitLogoutBefore(); @@ -260,8 +285,12 @@ class EvAuth extends RcModuleV2 implements Auth { await this._checkTabManagerEvent(); } - if (this._deps.auth.loggedIn && !this.connected && !this.connecting) { - console.log('evAuth onStateChange~~'); + if ( + this._deps.auth.loggedIn && + this.loginStatus !== loginStatus.AUTH_SUCCESS && + this.loginStatus !== loginStatus.LOGIN_SUCCESS && + !this.connecting + ) { this.connecting = true; // when login make sure the logoutByOtherTab is false this._logoutByOtherTab = false; @@ -282,6 +311,7 @@ class EvAuth extends RcModuleV2 implements Auth { if (!(await this.canUserLogoutFn())) { return; } + console.log('logout~~'); const agentId = this.agentId; @@ -311,7 +341,7 @@ class EvAuth extends RcModuleV2 implements Auth { } beforeAgentLogout(callback: () => void) { - this._eventEmitter.on(authStatus.LOGOUT_BEFORE, callback); + this._eventEmitter.on(loginStatus.LOGOUT_BEFORE, callback); } newReconnect(isBlock: boolean = true) { @@ -322,10 +352,12 @@ class EvAuth extends RcModuleV2 implements Auth { return isBlock ? this._deps.block.next(fn) : fn(); } - async authenticateWithToken( + async authenticateWithToken({ rcAccessToken = this._deps.auth.accessToken, - tokenType: EvTokenType = 'Bearer', - ) { + tokenType = 'Bearer', + shouldEmitAuthSuccess = true, + }: AuthenticateWithTokenType = {}) { + console.log('authenticateWithToken', shouldEmitAuthSuccess); try { this._deps.evClient.initSDK(); @@ -334,9 +366,21 @@ class EvAuth extends RcModuleV2 implements Auth { tokenType, ); const agent = { ...this.agent, authenticateResponse }; + if (shouldEmitAuthSuccess && !this._authenticateResponseWatcher) { + this._authenticateResponseWatcher = watch( + this, + () => this.agent?.authenticateResponse, + (authenticateResponse) => { + if (authenticateResponse) { + this._emitAuthSuccess(); + this._authenticateResponseWatcher(); + this._authenticateResponseWatcher = null; + } + }, + ); + } this.setAgent(agent); - this._emitAuthSuccess(); - + this.setAuthSuccess(); return authenticateResponse; } catch (error) { switch (error.type) { @@ -364,6 +408,11 @@ class EvAuth extends RcModuleV2 implements Auth { syncOtherTabs = false, retryOpenSocket = false, } = {}) { + console.log( + 'openSocketWithSelectedAgentId', + syncOtherTabs, + retryOpenSocket, + ); try { // TODO: here need check time when no message come back, that will block app. const getAgentConfig = new Promise((resolve) => { @@ -371,7 +420,6 @@ class EvAuth extends RcModuleV2 implements Auth { }); const selectedAgentId = this.agentId; - console.log('selectedAgentId~~~', selectedAgentId); if (!selectedAgentId) { throw new EvTypeError({ type: messageTypes.NO_AGENT, @@ -387,9 +435,10 @@ class EvAuth extends RcModuleV2 implements Auth { console.log('retryOpenSocket~~', retryOpenSocket); if (retryOpenSocket) { const { access_token } = await this._deps.auth.refreshToken(); - const authenticateRes = await this.authenticateWithToken( - access_token, - ); + const authenticateRes = await this.authenticateWithToken({ + rcAccessToken: access_token, + shouldEmitAuthSuccess: false, + }); if (!authenticateRes) return; const openSocketRes: any = await this.openSocketWithSelectedAgentId({ syncOtherTabs, @@ -410,11 +459,26 @@ class EvAuth extends RcModuleV2 implements Auth { const agent = { ...this.agent, agentConfig }; + if (!this._agentConfigWatcher) { + this._agentConfigWatcher = watch( + this, + () => this.agent?.agentConfig, + (agentConfig) => { + if (agentConfig) { + this._emitLoginSuccess(); + this._agentConfigWatcher(); + this._agentConfigWatcher = null; + } + }, + ); + } + this.setConnectionData({ agent, connected: true }); this.connecting = false; - this._emitLoginSuccess(); + this.setLoginSuccess(); + return agentConfig; } catch (error) { switch (error.type) { @@ -440,54 +504,50 @@ class EvAuth extends RcModuleV2 implements Auth { } loginAgent = async (token: string = this._deps.auth.accessToken) => { - const authenticateRes = await this.authenticateWithToken(token); + const authenticateRes = await this.authenticateWithToken({ + rcAccessToken: token, + shouldEmitAuthSuccess: false, + }); if (!authenticateRes) return; await this.openSocketWithSelectedAgentId(); }; - onLoginSuccess(callback: () => void) { - this._eventEmitter.on(authStatus.LOGIN_SUCCESS, callback); - } - onceLoginSuccess(callback: () => void) { - this._eventEmitter.once(authStatus.LOGIN_SUCCESS, callback); + this._eventEmitter.once(loginStatus.LOGIN_SUCCESS, callback); } onAuthSuccess(callback: () => void) { - this._eventEmitter.on(authStatus.AUTH_SUCCESS, callback); + this._eventEmitter.on(loginStatus.AUTH_SUCCESS, callback); } private _emitLogoutBefore() { - this._eventEmitter.emit(authStatus.LOGOUT_BEFORE); + this._eventEmitter.emit(loginStatus.LOGOUT_BEFORE); } private _emitLoginSuccess() { - this._eventEmitter.emit(authStatus.LOGIN_SUCCESS); + this._eventEmitter.emit(loginStatus.LOGIN_SUCCESS); } private _emitAuthSuccess() { - console.log('_emitAuthSuccess~~'); - this._eventEmitter.emit(authStatus.AUTH_SUCCESS); + this._eventEmitter.emit(loginStatus.AUTH_SUCCESS); } private async _checkTabManagerEvent() { const { event } = this._deps.tabManager; if (event) { - const data = event.args[0]; switch (event.name) { case tabManagerEvents.LOGOUT: this._logoutByOtherTab = true; break; case tabManagerEvents.OPEN_SOCKET: - console.log('tabManagerEvents.OPEN_SOCKET~~'); await this._deps.block.next(async () => { await this.openSocketWithSelectedAgentId({ retryOpenSocket: true, }); }); break; - case tabManagerEvents.SET_AGENT_ID: - this.setAgentId(data); + case tabManagerEvents.LOGGED_OUT: + this.setNotAuth(); break; default: break; diff --git a/packages/engage-voice-widget/modules/EvCall/EvCall.interface.ts b/packages/engage-voice-widget/modules/EvCall/EvCall.interface.ts index 0734025ffc..57b7ef1a93 100644 --- a/packages/engage-voice-widget/modules/EvCall/EvCall.interface.ts +++ b/packages/engage-voice-widget/modules/EvCall/EvCall.interface.ts @@ -1,7 +1,6 @@ import Alert from 'ringcentral-integration/modules/Alert'; -import Storage from 'ringcentral-integration/modules/Storage'; -import { EvTabManager } from '../EvTabManager'; +import { EvTabManager } from '../EvTabManager'; import { EvClient } from '../../lib/EvClient'; import { EvAgentSession } from '../EvAgentSession'; import { EvAuth } from '../EvAuth'; @@ -10,6 +9,7 @@ import { EvIntegratedSoftphone } from '../EvIntegratedSoftphone'; import { EvPresence } from '../EvPresence'; import { EvSettings } from '../EvSettings'; import { EvSubscription } from '../EvSubscription'; +import { EvStorage } from '../EvStorage'; export interface State { dialoutCallerId: string; @@ -34,7 +34,7 @@ export interface Deps { alert: Alert; evAuth: EvAuth; evSubscription: EvSubscription; - storage: Storage; + storage: EvStorage; evClient: EvClient; evAgentSession: EvAgentSession; evIntegratedSoftphone: EvIntegratedSoftphone; diff --git a/packages/engage-voice-widget/modules/EvCall/EvCall.ts b/packages/engage-voice-widget/modules/EvCall/EvCall.ts index b34dea2f90..dab6c44a14 100644 --- a/packages/engage-voice-widget/modules/EvCall/EvCall.ts +++ b/packages/engage-voice-widget/modules/EvCall/EvCall.ts @@ -5,6 +5,7 @@ import { state, storage, track, + watch, } from '@ringcentral-integration/core'; import { Module } from 'ringcentral-integration/lib/di'; import callErrors from 'ringcentral-integration/modules/Call/callErrors'; @@ -156,10 +157,21 @@ class EvCall extends RcModuleV2 implements Call { }); } + @computed((that) => [that._deps.evAuth.isEvLogged, that.ready]) + get isOnLoginSuccess() { + return this.ready && this._deps.evAuth.isEvLogged; + } + onInitOnce() { - this._deps.evAuth.onLoginSuccess(() => { - this.resetForm(); - }); + watch( + this, + () => this.isOnLoginSuccess, + (isOnLoginSuccess) => { + if (isOnLoginSuccess) { + this.resetForm(); + } + }, + ); this._deps.evCallMonitor.onCallEnded(() => { this.setDialoutStatus(dialoutStatuses.idle); @@ -215,7 +227,6 @@ class EvCall extends RcModuleV2 implements Call { queueId: this.queueId, ringTime: this.ringTime, }); - } catch (error) { this.setPhonedIdle(); } @@ -292,7 +303,7 @@ class EvCall extends RcModuleV2 implements Call { this._deps.evSettings.isOffhook || (offhookInitResult && offhookInitResult.status === 'OK') ) { - console.log('manualOutdial~~') + console.log('manualOutdial~~'); await this._deps.evClient.manualOutdial({ callerId, countryId, diff --git a/packages/engage-voice-widget/modules/EvCallDataSource/EvCallDataSource.interface.ts b/packages/engage-voice-widget/modules/EvCallDataSource/EvCallDataSource.interface.ts index 1776ea44ce..9b3ee6736e 100644 --- a/packages/engage-voice-widget/modules/EvCallDataSource/EvCallDataSource.interface.ts +++ b/packages/engage-voice-widget/modules/EvCallDataSource/EvCallDataSource.interface.ts @@ -1,9 +1,9 @@ -import Storage from 'ringcentral-integration/modules/Storage'; import { Mapping } from 'ringcentral-widgets/typings'; import { EvCallData } from '../../interfaces/EvData.interface'; import { EvClient } from '../../lib/EvClient'; import { EvAuth } from '../EvAuth'; +import { EvStorage } from '../EvStorage'; export interface State { /** current agent ongoing session calls list with callId (encodeUii({ uii, sessionId })) */ @@ -21,7 +21,7 @@ export interface State { export interface EvCallDataSourceOptions {} export interface Deps { - storage: Storage; + storage: EvStorage; evClient: EvClient; evAuth: EvAuth; } diff --git a/packages/engage-voice-widget/modules/EvCallHistory/EvCallHistory.interface.ts b/packages/engage-voice-widget/modules/EvCallHistory/EvCallHistory.interface.ts index 19ba3f5e9a..4881acefe0 100644 --- a/packages/engage-voice-widget/modules/EvCallHistory/EvCallHistory.interface.ts +++ b/packages/engage-voice-widget/modules/EvCallHistory/EvCallHistory.interface.ts @@ -1,5 +1,6 @@ import ActivityMatcher from 'ringcentral-integration/modules/ActivityMatcher'; import ContactMatcher from 'ringcentral-integration/modules/ContactMatcher'; +import { Locale } from 'ringcentral-integration/modules/LocaleV2'; import { EvCallMonitor } from '../EvCallMonitor'; import { EvSubscription } from '../EvSubscription'; @@ -13,6 +14,7 @@ export interface Deps { evSubscription: EvSubscription; contactMatcher?: ContactMatcher; activityMatcher?: ActivityMatcher; + locale: Locale; } export interface CallHistory extends State { diff --git a/packages/engage-voice-widget/modules/EvCallHistory/EvCallHistory.ts b/packages/engage-voice-widget/modules/EvCallHistory/EvCallHistory.ts index 7a1431f40c..aee70ca0d0 100644 --- a/packages/engage-voice-widget/modules/EvCallHistory/EvCallHistory.ts +++ b/packages/engage-voice-widget/modules/EvCallHistory/EvCallHistory.ts @@ -8,12 +8,14 @@ import { makeCallsUniqueIdentifies } from '../../lib/callUniqueIdentifies'; import { contactMatchIdentifyEncode } from '../../lib/contactMatchIdentify'; import { EvCallbackTypes } from '../../lib/EvClient/enums/callbackTypes'; import { CallHistory, Deps } from './EvCallHistory.interface'; +import { formatPhoneNumber } from '../../lib/FormatPhoneNumber'; @Module({ name: 'EvCallHistory', deps: [ 'EvCallMonitor', 'EvSubscription', + 'Locale', { dep: 'ContactMatcher', optional: true }, { dep: 'ActivityMatcher', optional: true }, ], @@ -59,7 +61,7 @@ class EvCallHistory extends RcModuleV2 implements CallHistory { that.contactMatches, that.activityMatches, ]) - get calls() { + get formattedCalls() { const lastWeekDayTimestamp = this._getLastWeekDayTimestamp(); // max 250 and 7 days const calls = this.rawCalls @@ -81,14 +83,16 @@ class EvCallHistory extends RcModuleV2 implements CallHistory { const activityMatches = this.activityMatches[id] || []; const agent = { name: call.agentId, - phoneNumber: call.agentId, + phoneNumber: this._formatPhoneNumber(call.agentId), }; let name = ''; if (contactMatches.length && activityMatches.length) { - const activity = activityMatches[0]; + // need to convert 18 digit ID to 15 for compatible in classic mode + // https://developer.salesforce.com/forums/?id=906F0000000BQGnIAO + const activity = activityMatches[0].slice(0, 15); const matched = contactMatches.find( - (match: { id: string }) => match.id === activity, + (match: { id: string }) => match.id.slice(0, 15) === activity, ); if (matched) { name = matched.name; @@ -96,7 +100,7 @@ class EvCallHistory extends RcModuleV2 implements CallHistory { } const contact = { name, - phoneNumber: call.ani, + phoneNumber: this._formatPhoneNumber(call.ani), }; return { @@ -120,9 +124,18 @@ class EvCallHistory extends RcModuleV2 implements CallHistory { }); } - @computed((that: EvCallHistory) => [that.calls]) + private _formatPhoneNumber(phoneNumber: string) { + // TODO: support countryCode + return formatPhoneNumber({ + phoneNumber, + countryCode: 'US', + currentLocale: this._deps.locale.currentLocale, + }); + } + + @computed((that: EvCallHistory) => [that.formattedCalls]) get lastEndedCall() { - return this.calls.length > 0 ? this.calls[0] : null; + return this.formattedCalls.length > 0 ? this.formattedCalls[0] : null; } @computed((that: EvCallHistory) => [that.rawCalls]) diff --git a/packages/engage-voice-widget/modules/EvCallMonitor/EvCallMonitor.ts b/packages/engage-voice-widget/modules/EvCallMonitor/EvCallMonitor.ts index b1ff9b6fb3..f819a63b0b 100644 --- a/packages/engage-voice-widget/modules/EvCallMonitor/EvCallMonitor.ts +++ b/packages/engage-voice-widget/modules/EvCallMonitor/EvCallMonitor.ts @@ -110,6 +110,7 @@ class EvCallMonitor extends RcModuleV2 implements CallMonitor { }); const id = call.session ? this.getCallId(call.session) : null; + const recordingUrl = call.session?.recordingUrl; const { agentFirstName, agentLastName } = call.baggage || {}; const agentName = @@ -121,6 +122,7 @@ class EvCallMonitor extends RcModuleV2 implements CallMonitor { ...mapping, [key]: { ...call, + recordingUrl, agentName, // TODO confirm about using `toMatches` & `fromMatches`? contactMatches: contactMatches[contactMatchIdentify] || [], diff --git a/packages/engage-voice-widget/modules/EvChooseAccountUI/EvChooseAccountUI.ts b/packages/engage-voice-widget/modules/EvChooseAccountUI/EvChooseAccountUI.ts index a0195d1835..bb3d2cce20 100644 --- a/packages/engage-voice-widget/modules/EvChooseAccountUI/EvChooseAccountUI.ts +++ b/packages/engage-voice-widget/modules/EvChooseAccountUI/EvChooseAccountUI.ts @@ -20,7 +20,7 @@ class EvChooseAccountUI extends RcUIModuleV2 implements ChooseAccountUI { async _onAccountItemClick(agentId: string) { await this._deps.block.next(async () => { - this._deps.evAuth.setAgentId(agentId, true); + this._deps.evAuth.setAgentId(agentId); await this._deps.evAuth.openSocketWithSelectedAgentId({ syncOtherTabs: true, retryOpenSocket: true, diff --git a/packages/engage-voice-widget/modules/EvDialerUI/EvDialerUI.interface.ts b/packages/engage-voice-widget/modules/EvDialerUI/EvDialerUI.interface.ts index 5239833900..fcf6e689df 100644 --- a/packages/engage-voice-widget/modules/EvDialerUI/EvDialerUI.interface.ts +++ b/packages/engage-voice-widget/modules/EvDialerUI/EvDialerUI.interface.ts @@ -1,5 +1,5 @@ import Locale from 'ringcentral-integration/modules/Locale'; -import Storage from 'ringcentral-integration/modules/Storage'; + import RouterInteraction from 'ringcentral-widgets/modules/RouterInteraction'; import { EvEnvironment } from '../../interfaces/Environment.interface'; @@ -10,6 +10,7 @@ import { EvCall } from '../EvCall'; import { EvCallMonitor } from '../EvCallMonitor'; import { EvIntegratedSoftphone } from '../EvIntegratedSoftphone'; import { EvSettings } from '../EvSettings'; +import { EvStorage } from '../EvStorage'; import { EvWorkingState } from '../EvWorkingState'; export interface State { @@ -25,7 +26,7 @@ export interface Deps { evCall: EvCall; locale: Locale; evAuth: EvAuth; - storage: Storage; + storage: EvStorage; routerInteraction: RouterInteraction; evSettings: EvSettings; evClient: EvClient; diff --git a/packages/engage-voice-widget/modules/EvIntegratedSoftphone/EvIntegratedSoftphone.interface.ts b/packages/engage-voice-widget/modules/EvIntegratedSoftphone/EvIntegratedSoftphone.interface.ts index 2e3e321813..ec386f6c7f 100644 --- a/packages/engage-voice-widget/modules/EvIntegratedSoftphone/EvIntegratedSoftphone.interface.ts +++ b/packages/engage-voice-widget/modules/EvIntegratedSoftphone/EvIntegratedSoftphone.interface.ts @@ -1,7 +1,6 @@ import Alert from 'ringcentral-integration/modules/Alert'; import { Auth } from 'ringcentral-integration/modules/AuthV2'; import Locale from 'ringcentral-integration/modules/Locale'; -import Storage from 'ringcentral-integration/modules/Storage'; import { Beforeunload } from 'ringcentral-widgets/modules/Beforeunload'; import { Block } from 'ringcentral-widgets/modules/Block'; import { ModalUI } from 'ringcentral-widgets/modules/ModalUIV2'; @@ -12,6 +11,7 @@ import { EvAgentSession } from '../EvAgentSession'; import { EvAuth } from '../EvAuth'; import { EvPresence } from '../EvPresence'; import { EvSettings } from '../EvSettings'; +import { EvStorage } from '../EvStorage'; import { EvSubscription } from '../EvSubscription'; import { EvTabManager } from '../EvTabManager'; @@ -33,7 +33,7 @@ export interface Deps { beforeunload: Beforeunload; evSettings: EvSettings; evClient: EvClient; - storage: Storage; + storage: EvStorage; presence: EvPresence; modalUI: ModalUI; alert: Alert; diff --git a/packages/engage-voice-widget/modules/EvIntegratedSoftphone/EvIntegratedSoftphone.ts b/packages/engage-voice-widget/modules/EvIntegratedSoftphone/EvIntegratedSoftphone.ts index e4354fe877..1a7b0085a4 100644 --- a/packages/engage-voice-widget/modules/EvIntegratedSoftphone/EvIntegratedSoftphone.ts +++ b/packages/engage-voice-widget/modules/EvIntegratedSoftphone/EvIntegratedSoftphone.ts @@ -53,6 +53,8 @@ class EvIntegratedSoftphone implements IntegratedSoftphone { autoAnswerCheckFn: () => boolean; + private _isFirefox: boolean; + private _audio: HTMLAudioElement; private _eventEmitter = new EventEmitter(); @@ -100,6 +102,8 @@ class EvIntegratedSoftphone this._deps.beforeunload.onAfterUnload(() => { this._sendTabManager(tabManagerEvents.CLOSE_WHEN_CALL_CONNECTED); }); + + this._isFirefox = navigator.userAgent.indexOf('Firefox') !== -1; } // @state @@ -175,7 +179,7 @@ class EvIntegratedSoftphone this._deps.tabManager.onSetMainTabComplete(async () => { console.log( - 'onSettedMainTab~~', + 'onSetMainTabComplete~~', this._deps.evAgentSession.isIntegratedSoftphone, ); if (this._deps.evAgentSession.isIntegratedSoftphone) { @@ -257,7 +261,7 @@ class EvIntegratedSoftphone // that event call from modal ok or cancel, that auto close modal this._deps.modalUI.close(this._answerModalId); if (data) { - this.answerCall(); + await this.answerCall(); } else { this.rejectCall(); } @@ -294,6 +298,15 @@ class EvIntegratedSoftphone case tabManagerEvents.CLOSE_WHEN_CALL_CONNECTED: this._isCloseWhenCallConnected = true; break; + case tabManagerEvents.NOTIFY_ACTIVE_TAB_CALL_ACTIVE: + if (this._deps.tabManager.active) { + this._deps.alert.warning({ + message: tabManagerEvents.NOTIFY_ACTIVE_TAB_CALL_ACTIVE, + backdrop: true, + ttl: 0, + }); + } + break; default: break; } @@ -314,6 +327,7 @@ class EvIntegratedSoftphone } async askAudioPermission(showMask: boolean = true) { + console.log('askAudioPermission~~', showMask); try { if (showMask) { if (!this.audioPermission) { @@ -573,9 +587,9 @@ class EvIntegratedSoftphone ), okText: i18n.getString('inviteModalAnswer', currentLocale), cancelText: i18n.getString('inviteModalReject', currentLocale), - onOK: () => { + onOK: async () => { this._sendTabManager(tabManagerEvents.SIP_RINGING_MODAL, true); - this.answerCall(); + await this.answerCall(); }, onCancel: () => { this._sendTabManager(tabManagerEvents.SIP_RINGING_MODAL, false); @@ -585,9 +599,14 @@ class EvIntegratedSoftphone }); } - private answerCall() { + private async answerCall() { this._resetRingingModal(); - this._sipAnswer(); + if ( + !this.tabManagerEnabled || + (this.tabManagerEnabled && this._deps.tabManager.isMainTab) + ) { + await this._sipAnswer(); + } } private rejectCall() { @@ -693,7 +712,28 @@ class EvIntegratedSoftphone this._answerModalId = null; } - private _sipAnswer() { + private async _sipAnswer() { + if (this._isFirefox) { + await raceTimeout( + navigator.mediaDevices.getUserMedia({ + audio: true, + }), + { + timeout: 2000, + onTimeout: (resolve, reject) => { + this._sendTabManager( + tabManagerEvents.NOTIFY_ACTIVE_TAB_CALL_ACTIVE, + ); + // eslint-disable-next-line no-alert + alert( + i18n.getString('activeCallTip', this._deps.locale.currentLocale), + ); + reject(null); + }, + }, + ); + } + this._deps.evClient.sipAnswer(); } diff --git a/packages/engage-voice-widget/modules/EvIntegratedSoftphone/i18n/en-US.ts b/packages/engage-voice-widget/modules/EvIntegratedSoftphone/i18n/en-US.ts index 96d81074df..fca71cf4f6 100644 --- a/packages/engage-voice-widget/modules/EvIntegratedSoftphone/i18n/en-US.ts +++ b/packages/engage-voice-widget/modules/EvIntegratedSoftphone/i18n/en-US.ts @@ -3,4 +3,5 @@ export default { inviteModalContent: 'Incoming call from {displayName}', inviteModalAnswer: 'Answer', inviteModalReject: 'Reject', + activeCallTip: 'To complete answering the call, click continue', }; diff --git a/packages/engage-voice-widget/modules/EvPresence/EvPresence.interface.ts b/packages/engage-voice-widget/modules/EvPresence/EvPresence.interface.ts index 184d0f3ea1..95b808b388 100644 --- a/packages/engage-voice-widget/modules/EvPresence/EvPresence.interface.ts +++ b/packages/engage-voice-widget/modules/EvPresence/EvPresence.interface.ts @@ -1,5 +1,4 @@ import Alert from 'ringcentral-integration/modules/Alert'; -import Storage from 'ringcentral-integration/modules/Storage'; import { Beforeunload } from 'ringcentral-widgets/modules/Beforeunload'; import { Mapping } from 'ringcentral-widgets/typings'; @@ -7,6 +6,7 @@ import { DialoutStatusesType } from '../../enums'; import { EvCallData } from '../../interfaces/EvData.interface'; import { EvClient } from '../../lib/EvClient'; import { EvCallDataSource } from '../EvCallDataSource'; +import { EvStorage } from '../EvStorage'; import { EvSubscription } from '../EvSubscription'; export interface State { @@ -35,7 +35,7 @@ export interface PresenceOptions { export interface Deps { evSubscription: EvSubscription; evClient: EvClient; - storage: Storage; + storage: EvStorage; alert: Alert; beforeunload: Beforeunload; evCallDataSource: EvCallDataSource; diff --git a/packages/engage-voice-widget/modules/EvRequeueCall/EvRequeueCall.interface.ts b/packages/engage-voice-widget/modules/EvRequeueCall/EvRequeueCall.interface.ts index 090ac28c3a..713eae069b 100644 --- a/packages/engage-voice-widget/modules/EvRequeueCall/EvRequeueCall.interface.ts +++ b/packages/engage-voice-widget/modules/EvRequeueCall/EvRequeueCall.interface.ts @@ -1,10 +1,10 @@ import Alert from 'ringcentral-integration/modules/Alert'; -import Storage from 'ringcentral-integration/modules/Storage'; import { EvClient } from '../../lib/EvClient'; import { EvActiveCallControl } from '../EvActiveCallControl'; import { EvAuth } from '../EvAuth'; import { EvCall } from '../EvCall'; +import { EvStorage } from '../EvStorage'; export interface State { requeuing: boolean; @@ -18,7 +18,7 @@ export interface EvRequeueCallOptions { } export interface Deps { - storage: Storage; + storage: EvStorage; evClient: EvClient; activeCallControl: EvActiveCallControl; evAuth: EvAuth; diff --git a/packages/engage-voice-widget/modules/EvSettings/EvSettings.interface.ts b/packages/engage-voice-widget/modules/EvSettings/EvSettings.interface.ts index 5eace19f8f..f284f7dbc6 100644 --- a/packages/engage-voice-widget/modules/EvSettings/EvSettings.interface.ts +++ b/packages/engage-voice-widget/modules/EvSettings/EvSettings.interface.ts @@ -1,9 +1,8 @@ -import Storage from 'ringcentral-integration/modules/Storage'; - import { EvClient } from '../../lib/EvClient'; import { EvAgentSession } from '../EvAgentSession'; import { EvAuth } from '../EvAuth'; import { EvPresence } from '../EvPresence'; +import { EvStorage } from '../EvStorage'; export interface State {} @@ -15,7 +14,7 @@ export interface Deps { evClient: EvClient; evAuth: EvAuth; evAgentSession: EvAgentSession; - storage: Storage; + storage: EvStorage; presence: EvPresence; evSettingsOptions?: EvSettingsOptions; } diff --git a/packages/engage-voice-widget/modules/EvStorage/EvStorage.interface.ts b/packages/engage-voice-widget/modules/EvStorage/EvStorage.interface.ts new file mode 100644 index 0000000000..2ff459d467 --- /dev/null +++ b/packages/engage-voice-widget/modules/EvStorage/EvStorage.interface.ts @@ -0,0 +1,12 @@ +import { Auth } from 'ringcentral-integration/modules/AuthV2'; +import { StorageOptions } from 'ringcentral-integration/modules/StorageV2'; +import { TabManager } from 'ringcentral-integration/modules/TabManagerV2'; +import { EvAuth } from '../EvAuth'; + +export type EvStorageOptions = StorageOptions; +export interface Deps { + auth: Auth; + evAuth: EvAuth; + tabManager?: TabManager; + storageOptions?: EvStorageOptions; +} diff --git a/packages/engage-voice-widget/modules/EvStorage/EvStorage.ts b/packages/engage-voice-widget/modules/EvStorage/EvStorage.ts new file mode 100644 index 0000000000..88c5bfc280 --- /dev/null +++ b/packages/engage-voice-widget/modules/EvStorage/EvStorage.ts @@ -0,0 +1,116 @@ +import { Module } from 'ringcentral-integration/lib/di'; + +import { Storage } from 'ringcentral-integration/modules/StorageV2'; +import loginStatus from 'ringcentral-integration/modules/Auth/loginStatus'; +import moduleStatuses from 'ringcentral-integration/enums/moduleStatuses'; +import { Deps } from './EvStorage.interface'; +import { loginStatus as evLoginStatus } from '../../enums/loginStatus'; + +@Module({ + name: 'Storage', + deps: [ + 'Auth', + 'EvAuth', + { dep: 'TabManager', optional: true }, + { dep: 'StorageOptions', optional: true }, + ], +}) +export class EvStorage extends Storage { + constructor(deps: Deps) { + super(deps); + this._disableInactiveTabsWrite = + this._deps.storageOptions?.disableInactiveTabsWrite ?? true; + } + + async initModule() { + let storedData: Record = null; + this.store.subscribe(async () => { + if ( + this._deps.auth.loginStatus === loginStatus.loggedIn && + (!this._deps.tabManager || this._deps.tabManager.ready) && + this._deps.evAuth.loginStatus === evLoginStatus.LOGIN_SUCCESS && + this.pending + ) { + this.store.dispatch({ + type: this._storageActionTypes.init, + }); + const agentId = this._deps.evAuth.agentId; + const storageKey = `${this.prefix ? `${this.prefix}-` : ''}storage-${ + this._deps.auth.ownerId + }${agentId ? `-${agentId}` : ''}`; + + this._storage = new this._StorageProvider({ + storageKey, + }); + storedData = await this._storage.getData(); + /* migration storage v1 to v2 */ + /* eslint-disable */ + for (const newKey in this.migrationMapping) { + const oldKey = this.migrationMapping[newKey]; + if (typeof oldKey === 'string') { + if (storedData[oldKey]) { + storedData[newKey] = storedData[oldKey]; + } + } else if (typeof oldKey === 'object') { + for (const index in oldKey) { + if (storedData[oldKey[index]]) { + storedData[newKey] = storedData[newKey] ?? {}; + (storedData[newKey] as Record)[index] = + storedData[oldKey[index]]; + } + } + } + this._storage.setItem(newKey, storedData[newKey]); + } + /* eslint-enable */ + /* migration storage v1 to v2 */ + for (const key in storedData) { + if (!this._storageReducers[key]) { + delete storedData[key]; + await this._storage.removeItem(key); + } + } + this.store.dispatch({ + type: this._storageActionTypes.initSuccess, + // storageKey, + // To fix same reference in redux store with storedData + data: { + ...storedData, + }, + }); + this._storageHandler = ({ key, value }) => { + if (this.ready) { + storedData[key] = value; + this.store.dispatch({ + type: this._storageActionTypes.sync, + key, + value, + }); + } + }; + this._storage.on('storage', this._storageHandler); + } else if ( + ((!!this._deps.tabManager && !this._deps.tabManager.ready) || + this._deps.auth.notLoggedIn) && + this.ready + ) { + this.resetStorage(); + } + if ( + this.status === moduleStatuses.ready && + (!this._disableInactiveTabsWrite || + !this._deps.tabManager || + this._deps.tabManager.active) + ) { + // save new data to storage when changed + const currentData = this.data; + for (const key in currentData) { + if (storedData[key] !== currentData[key]) { + this._storage.setItem(key, currentData[key]); + storedData[key] = currentData[key]; + } + } + } + }); + } +} diff --git a/packages/engage-voice-widget/modules/EvStorage/index.ts b/packages/engage-voice-widget/modules/EvStorage/index.ts new file mode 100644 index 0000000000..4ebba59d43 --- /dev/null +++ b/packages/engage-voice-widget/modules/EvStorage/index.ts @@ -0,0 +1,2 @@ +export * from './EvStorage'; +export * from './EvStorage.interface'; diff --git a/packages/engage-voice-widget/modules/EvTransferCall/EvTransferCall.interface.ts b/packages/engage-voice-widget/modules/EvTransferCall/EvTransferCall.interface.ts index 5f72d7bf43..0e5c6094f4 100644 --- a/packages/engage-voice-widget/modules/EvTransferCall/EvTransferCall.interface.ts +++ b/packages/engage-voice-widget/modules/EvTransferCall/EvTransferCall.interface.ts @@ -1,6 +1,5 @@ import Alert from 'ringcentral-integration/modules/Alert'; import Locale from 'ringcentral-integration/modules/Locale'; -import Storage from 'ringcentral-integration/modules/Storage'; import { ModalUI } from 'ringcentral-widgets/modules/ModalUIV2'; import { DirectTransferTypes } from '../../enums/directTransferTypes'; @@ -15,6 +14,7 @@ import { EvAgentSession } from '../EvAgentSession'; import { EvAuth } from '../EvAuth'; import { EvCall } from '../EvCall'; import { EvCallMonitor } from '../EvCallMonitor'; +import { EvStorage } from '../EvStorage'; import { EvSubscription } from '../EvSubscription'; import { EvWorkingState } from '../EvWorkingState'; @@ -75,7 +75,7 @@ export interface Deps { evCall: EvCall; evSubscription: EvSubscription; evWorkingState: EvWorkingState; - storage: Storage; + storage: EvStorage; modalUI: ModalUI; locale: Locale; alert: Alert; diff --git a/packages/engage-voice-widget/modules/EvWorkingState/EvWorkingState.interface.ts b/packages/engage-voice-widget/modules/EvWorkingState/EvWorkingState.interface.ts index 406281025a..208b2c803b 100644 --- a/packages/engage-voice-widget/modules/EvWorkingState/EvWorkingState.interface.ts +++ b/packages/engage-voice-widget/modules/EvWorkingState/EvWorkingState.interface.ts @@ -1,14 +1,14 @@ import Alert from 'ringcentral-integration/modules/Alert'; import { Auth } from 'ringcentral-integration/modules/AuthV2'; -import Storage from 'ringcentral-integration/modules/Storage'; -import { EvTabManager } from '../EvTabManager'; +import { EvTabManager } from '../EvTabManager'; import { EvAgentState, EvClient } from '../../lib/EvClient'; import { EvAgentSession } from '../EvAgentSession'; import { EvAuth } from '../EvAuth'; import { EvCallMonitor } from '../EvCallMonitor'; import { EvPresence } from '../EvPresence'; import { EvSubscription } from '../EvSubscription'; +import { EvStorage } from '../EvStorage'; export interface State { agentState: EvAgentState; @@ -28,7 +28,7 @@ export interface Deps { evCallMonitor: EvCallMonitor; evClient: EvClient; presence: EvPresence; - storage: Storage; + storage: EvStorage; alert: Alert; tabManager?: EvTabManager; evWorkingStateOptions?: EvWorkingStateOptions; diff --git a/packages/engage-voice-widget/package.json b/packages/engage-voice-widget/package.json index 1e166b5b1a..a4fdc16365 100644 --- a/packages/engage-voice-widget/package.json +++ b/packages/engage-voice-widget/package.json @@ -35,7 +35,7 @@ "peerDependencies": { "@ringcentral-integration/core": "^0.12.0", "@ringcentral-integration/phone-number": "^1.0.4", - "@ringcentral/juno": "^1.3.2-beta.3102-608720c9", + "@ringcentral/juno": "^1.4.1", "ringcentral-integration": "^0.12.0", "ringcentral-widgets": "^0.12.0" }, @@ -45,7 +45,7 @@ "@ringcentral-integration/locale-loader": "*", "@ringcentral-integration/locale-settings": "*", "@ringcentral-integration/phone-number": "*", - "@ringcentral/juno": "^1.3.2-beta.3102-608720c9", + "@ringcentral/juno": "^1.4.1", "crius-test": "^1.1.3", "enzyme": "^3.10.0", "enzyme-adapter-react-16": "^1.15.1", diff --git a/packages/i18n/index.js b/packages/i18n/index.js index 3a8785a15c..a63050c489 100644 --- a/packages/i18n/index.js +++ b/packages/i18n/index.js @@ -8,21 +8,42 @@ export const RUNTIME = { instances: new Set(), padRatio: 0.3, fallbackLocale: DEFAULT_LOCALE, + languageDefaults: null, }; /** * @function - * @description Set currrent runtime locale and load the locale files accordingly + * @description Set current runtime locale and load the locale files accordingly * @param {String} locale - The desired locale. * @return Promise */ async function setLocale(locale) { RUNTIME.locale = locale; + await reloadLocales(); +} + +async function reloadLocales() { for (const i of RUNTIME.instances) { await i.load(); } } +async function setDefaultLocale(locale) { + RUNTIME.defaultLocale = locale; + await reloadLocales(); +} + +async function setLanguageDefaults(defaults) { + RUNTIME.languageDefaults = defaults; + await reloadLocales(); +} + +function checkDefaults(locale) { + return ( + (RUNTIME.languageDefaults && RUNTIME.languageDefaults[locale]) || locale + ); +} + /** * @class * @description I18n is a simple localizations helper class that represents a set of locale files. @@ -56,9 +77,9 @@ export default class I18n { } } async load() { - await this._load(RUNTIME.fallbackLocale); - await this._load(RUNTIME.defaultLocale); - await this._load(RUNTIME.locale); + await this._load(checkDefaults(RUNTIME.fallbackLocale)); + await this._load(checkDefaults(RUNTIME.defaultLocale)); + await this._load(checkDefaults(RUNTIME.locale)); } _getString(key, locale) { if ( @@ -95,14 +116,21 @@ export default class I18n { padRatio: RUNTIME.padRatio, }); } - return this._getString(key, locale); + return this._getString(key, checkDefaults(locale)); + } + + static checkDefaults(locale) { + return checkDefaults(locale); + } + + checkDefaults(locale) { + return checkDefaults(locale); } - // eslint-disable-next-line class-methods-use-this get currentLocale() { return RUNTIME.locale; } - // eslint-disable-next-line class-methods-use-this + get setLocale() { return setLocale; } @@ -130,4 +158,20 @@ export default class I18n { static setDefaultLocale(locale) { RUNTIME.defaultLocale = locale; } + + static async setDefaultLocale(locale) { + return setDefaultLocale(locale); + } + + async setDefaultLocale(locale) { + return setDefaultLocale(locale); + } + + static async setLanguageDefaults(defaults) { + return setLanguageDefaults(defaults); + } + + async setLanguageDefaults(defaults) { + return setLanguageDefaults(defaults); + } } diff --git a/packages/ringcentral-integration/enums/callLoggerTriggerTypes.ts b/packages/ringcentral-integration/enums/callLoggerTriggerTypes.ts index 160e21beaf..2633bc8e81 100644 --- a/packages/ringcentral-integration/enums/callLoggerTriggerTypes.ts +++ b/packages/ringcentral-integration/enums/callLoggerTriggerTypes.ts @@ -6,4 +6,6 @@ export const callLoggerTriggerTypes = ObjectMap.fromObject({ callLogSync: 'callLogSync', } as const); +export type CallLoggerTriggerType = keyof typeof callLoggerTriggerTypes; + export default callLoggerTriggerTypes; diff --git a/packages/ringcentral-integration/constants/usageTypes.ts b/packages/ringcentral-integration/enums/usageTypes.ts similarity index 100% rename from packages/ringcentral-integration/constants/usageTypes.ts rename to packages/ringcentral-integration/enums/usageTypes.ts diff --git a/packages/ringcentral-integration/helpers/meetingHelper.ts b/packages/ringcentral-integration/helpers/meetingHelper.ts index 9c0f4a9783..e040aa01f7 100644 --- a/packages/ringcentral-integration/helpers/meetingHelper.ts +++ b/packages/ringcentral-integration/helpers/meetingHelper.ts @@ -74,6 +74,15 @@ function getMeetingSettings({ }; } +function getDefaultTopic( + extensionName: string, + currentLocale: string = 'en-US', +) { + return formatMessage(i18n.getString('meetingTitle', currentLocale), { + extensionName, + }); +} + // Basic default meeting type information function getDefaultMeetingSettings( extensionName: string, @@ -82,9 +91,7 @@ function getDefaultMeetingSettings( hostId?: string, ): RcMMeetingModel { return { - topic: formatMessage(i18n.getString('meetingTitle', currentLocale), { - extensionName, - }), + topic: getDefaultTopic(extensionName, currentLocale), meetingType: MeetingType.SCHEDULED, password: '', schedule: { @@ -183,4 +190,5 @@ export { generateRandomPassword, updateFullYear, updateFullTime, + getDefaultTopic, }; diff --git a/packages/ringcentral-integration/interfaces/CallLog.interface.ts b/packages/ringcentral-integration/interfaces/CallLog.interface.ts new file mode 100644 index 0000000000..26b868ff88 --- /dev/null +++ b/packages/ringcentral-integration/interfaces/CallLog.interface.ts @@ -0,0 +1,26 @@ +import { SvgSymbol } from '@ringcentral/juno'; + +import { Call } from './Call.interface'; + +export interface CallLog extends Call { + callTime?: string; + callDate?: string; + isDisposed?: boolean; +} + +export interface CallLogActionButton { + icon?: SvgSymbol; + label: string; + disabled?: boolean; + action?: () => Promise | void; +} + +export interface CallLogMenuButton { + icon?: SvgSymbol; + label: string; + disabled?: boolean; + subMenu?: (CallLogMenuButton & CallLogActionButton)[]; +} + +export type CallLogMenuItem = CallLogActionButton & CallLogMenuButton; +export type CallLogMenu = CallLogMenuItem[]; diff --git a/packages/ringcentral-integration/shared/clientResponse.ts b/packages/ringcentral-integration/interfaces/ClientResponse.ts similarity index 67% rename from packages/ringcentral-integration/shared/clientResponse.ts rename to packages/ringcentral-integration/interfaces/ClientResponse.ts index 0362af4141..92b66c0cf3 100644 --- a/packages/ringcentral-integration/shared/clientResponse.ts +++ b/packages/ringcentral-integration/interfaces/ClientResponse.ts @@ -8,17 +8,8 @@ interface ErrorResponse extends Response { _text?: string; } -/** - * Response from client's requests - */ export interface ClientError { response?: ErrorResponse; message?: string; retryAfter?: number; } - -export class Alert {} - -export interface Client {} - -export class Environment {} diff --git a/packages/ringcentral-integration/interfaces/MessageStore.model.ts b/packages/ringcentral-integration/interfaces/MessageStore.model.ts new file mode 100644 index 0000000000..e1cdd1bf88 --- /dev/null +++ b/packages/ringcentral-integration/interfaces/MessageStore.model.ts @@ -0,0 +1,33 @@ +import { + SyncInfoMessages, + GetMessageInfoResponse, +} from '@rc-ex/core/definitions'; + +export interface ConversationItem { + id: string; + creationTime: number; + type: GetMessageInfoResponse['type']; + messageId: GetMessageInfoResponse['id']; +} + +export type Message = Pick< + GetMessageInfoResponse, + Exclude< + keyof GetMessageInfoResponse, + 'creationTime' | 'conversationId' | 'lastModifiedTime' + > +> & { conversationId: string; creationTime: number; lastModifiedTime: number }; + +export type Messages = Message[]; + +export interface MessageSyncList { + records: Messages; + syncInfo: SyncInfoMessages; +} + +export interface MessageStoreModel { + syncInfo: SyncInfoMessages; + conversationList: ConversationItem[]; + // conversationStore: Record; + conversationStore: Record; +} diff --git a/packages/ringcentral-integration/interfaces/Rcv.model.ts b/packages/ringcentral-integration/interfaces/Rcv.model.ts index 55d5dbd2af..6fceb8f779 100644 --- a/packages/ringcentral-integration/interfaces/Rcv.model.ts +++ b/packages/ringcentral-integration/interfaces/Rcv.model.ts @@ -51,7 +51,7 @@ export interface RcVideoAPI { muteAudio: boolean; muteVideo: boolean; isMeetingSecret: boolean; - meetingPassword: string; + meetingPassword?: string; isOnlyAuthUserJoin: boolean; isOnlyCoworkersJoin: boolean; allowScreenSharing: boolean; @@ -78,7 +78,7 @@ export interface RcVideoAPIResponse extends RcVideoAPI { accountId: string; extensionId: string; phoneGroup: string; - meetingPassword?: string; + // meetingPassword?: string; meetingPasswordPSTN?: string; meetingPasswordMasked?: string; isMeetingSecret: boolean; diff --git a/packages/ringcentral-integration/interfaces/Webphone.interface.ts b/packages/ringcentral-integration/interfaces/Webphone.interface.ts index 553678880e..220710a84a 100644 --- a/packages/ringcentral-integration/interfaces/Webphone.interface.ts +++ b/packages/ringcentral-integration/interfaces/Webphone.interface.ts @@ -29,6 +29,7 @@ export interface WebphoneSession extends WebphoneSessionBase { __rc_lastActiveTime: number; __rc_extendedControls: string; __rc_extendedControlStatus: ObjectMapValue; + __rc_transferSessionId: string; } export interface NormalizedSession { @@ -60,4 +61,5 @@ export interface NormalizedSession { cached: boolean; removed: boolean; callQueueName: string; + warmTransferSessionId: string; } diff --git a/packages/ringcentral-integration/lib/Analytics/index.js b/packages/ringcentral-integration/lib/Analytics/index.js index 7e29e0f09b..28e2f9b083 100644 --- a/packages/ringcentral-integration/lib/Analytics/index.js +++ b/packages/ringcentral-integration/lib/Analytics/index.js @@ -1 +1,2 @@ export { default as Segment } from './segment'; +export { Pendo } from './pendo'; diff --git a/packages/ringcentral-integration/lib/Analytics/pendo.js b/packages/ringcentral-integration/lib/Analytics/pendo.js new file mode 100644 index 0000000000..59b47588e3 --- /dev/null +++ b/packages/ringcentral-integration/lib/Analytics/pendo.js @@ -0,0 +1,25 @@ +class Pendo { + static init(pendoApiKey, onLoadSuccess) { + if (!pendoApiKey) return; + const pendoLibSource = `https://cdn.pendo.io/agent/static/${pendoApiKey}/pendo.js`; + const isCreated = document.querySelector(`script[src="${pendoLibSource}"]`); + if (isCreated) return; + + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = pendoLibSource; + script.async = true; + script.onload = () => { + console.log('pendo SDK is loaded!'); + if (typeof onLoadSuccess === 'function') { + onLoadSuccess(window.pendo); + } + }; + script.onerror = () => { + console.log('load pendo fail.'); + }; + document.head.appendChild(script); + } +} + +export { Pendo }; diff --git a/packages/ringcentral-integration/lib/LoggerBaseV2/LoggerBase.interface.ts b/packages/ringcentral-integration/lib/LoggerBaseV2/LoggerBase.interface.ts new file mode 100644 index 0000000000..2b034055fa --- /dev/null +++ b/packages/ringcentral-integration/lib/LoggerBaseV2/LoggerBase.interface.ts @@ -0,0 +1,10 @@ +export type LogOptions = { + item?: T; +} & S; + +export interface Options { + storageKey?: string; + enableCache?: boolean; +} + +export interface Deps {} diff --git a/packages/ringcentral-integration/lib/LoggerBaseV2/LoggerBase.ts b/packages/ringcentral-integration/lib/LoggerBaseV2/LoggerBase.ts new file mode 100644 index 0000000000..f71c3b009f --- /dev/null +++ b/packages/ringcentral-integration/lib/LoggerBaseV2/LoggerBase.ts @@ -0,0 +1,108 @@ +import { + state, + action, + computed, + RcModuleV2, +} from '@ringcentral-integration/core'; +import proxify from '../proxy/proxify'; +import { defaultIdentityFunction, convertListToMap } from './loggerBaseHelper'; +import { Deps, LogOptions, Options } from './LoggerBase.interface'; + +export abstract class LoggerBase extends RcModuleV2 { + protected _identityFunction: ( + ...args: any + ) => string = defaultIdentityFunction; + + abstract _logFunction: (options: LogOptions) => Promise; + + abstract _readyCheckFunction: () => boolean; + + protected _logPromises = new Map>(); + + constructor(deps: Deps, options: Options) { + super({ + deps, + ...options, + }); + } + + @state + loggingList: string[] = []; + + @action + setLoggingList(id: string) { + if (this.loggingList.indexOf(id) === -1) { + this.loggingList.push(id); + } + } + + @action + filterLoggingListById(id: string) { + this.loggingList = this.loggingList.filter((item) => item !== id); + } + + @action + resetLoggingList() { + this.loggingList = []; + } + + _shouldInit() { + return !!(super._shouldInit() && this._readyCheckFunction()); + } + + _shouldReset() { + return !!(super._shouldReset() || !this._readyCheckFunction()); + } + + onReset() { + this.resetLoggingList(); + } + + @proxify + async _log({ item, ...options }: LogOptions) { + if (!this.ready) { + throw new Error(`${this.constructor.name}._log: module is not ready.`); + } + if (!item) { + throw new Error( + `${this.constructor.name}._log: options.item is undefined.`, + ); + } + + const id = this._identityFunction(item); + // wait for the previous log action to finish + if (this._logPromises.has(id)) { + await this._logPromises.get(id); + } + try { + this.setLoggingList(id); + const promise = this._logFunction({ item, ...options }); + this._logPromises.set(id, promise); + await promise; + this._logPromises.delete(id); + this.filterLoggingListById(id); + } catch (error) { + this._logPromises.delete(id); + this.filterLoggingListById(id); + throw error; + } + } + + @proxify + async log({ item, ...options }: LogOptions) { + if (!this.ready) { + throw new Error(`${this.constructor.name}.log: module is not ready.`); + } + if (!item) { + throw new Error( + `${this.constructor.name}.log: options.item is undefined.`, + ); + } + await this._log({ item, ...options }); + } + + @computed((that) => [that.loggingList]) + get loggingMap() { + return convertListToMap(this.loggingList); + } +} diff --git a/packages/ringcentral-integration/lib/LoggerBaseV2/index.ts b/packages/ringcentral-integration/lib/LoggerBaseV2/index.ts new file mode 100644 index 0000000000..1fd277299d --- /dev/null +++ b/packages/ringcentral-integration/lib/LoggerBaseV2/index.ts @@ -0,0 +1,3 @@ +export * from './LoggerBase'; +export * from './LoggerBase.interface'; +export * from './loggerBaseHelper'; diff --git a/packages/ringcentral-integration/lib/LoggerBaseV2/loggerBaseHelper.ts b/packages/ringcentral-integration/lib/LoggerBaseV2/loggerBaseHelper.ts new file mode 100644 index 0000000000..75e2d02503 --- /dev/null +++ b/packages/ringcentral-integration/lib/LoggerBaseV2/loggerBaseHelper.ts @@ -0,0 +1,17 @@ +/** + * Identity function returns a deterministic id value for each item. + */ +export function defaultIdentityFunction(item: T) { + return item.id; +} + +/** + * Convert array of { name, id } objects into a map. + */ +export function convertListToMap(loggingList: string[]) { + const mapping: Record = {}; + loggingList.forEach((id) => { + mapping[id] = true; + }); + return mapping; +} diff --git a/packages/ringcentral-integration/lib/contactHelper.ts b/packages/ringcentral-integration/lib/contactHelper.ts index ed244648d3..e670db5268 100644 --- a/packages/ringcentral-integration/lib/contactHelper.ts +++ b/packages/ringcentral-integration/lib/contactHelper.ts @@ -114,7 +114,11 @@ export function isSearchHitContact({ }): boolean { return ( // search names - `${contact.firstName} ${contact.lastName} ${contact.name}` + (!!contact.name && contact.name.toLowerCase().includes(lowerSearch)) || + [contact.firstName, contact.lastName] + .map((x) => x && x.trim()) + .filter((x) => !!x) + .join(' ') .toLowerCase() .includes(lowerSearch) || // search phones @@ -123,7 +127,7 @@ export function isSearchHitContact({ (x) => x.phoneNumber && x.phoneNumber.includes(lowerSearch), )) || // search emails - (contact.email && contact.email.toLowerCase() === lowerSearch) || + (!!contact.email && contact.email.toLowerCase() === lowerSearch) || (Array.isArray(contact.emails) && contact.emails.map((x) => x && x.toLowerCase()).includes(lowerSearch)) ); diff --git a/packages/ringcentral-integration/lib/dataTransport.ts b/packages/ringcentral-integration/lib/dataTransport.ts new file mode 100644 index 0000000000..ee907bf38f --- /dev/null +++ b/packages/ringcentral-integration/lib/dataTransport.ts @@ -0,0 +1,45 @@ +import { forEachObjIndexed } from 'ramda'; +import { Transport } from 'data-transport'; + +export * from 'data-transport'; + +const listen = ( + target: any, + key: string, + descriptor: TypedPropertyDescriptor<(...args: any) => Promise>, +) => { + const fn = descriptor.value; + if (process.env.NODE_ENV !== 'production') { + if (typeof fn !== 'function') { + console.warn( + `The decorator '@listen' can only decorate methods, '${key}' is NOT a methods.`, + ); + return descriptor; + } + } + target.listeners ??= {}; + target.listeners[key] = fn; + return { + ...descriptor, + async value(this: Transport) { + if (process.env.NODE_ENV !== 'production') { + throw new Error( + `The method '${key}' is a listen function that can NOT be actively called.`, + ); + } + }, + }; +}; + +const bindListeners = (instance: object, transport: Transport) => { + forEachObjIndexed( + (func, name) => { + transport.listen(name, func.bind(instance)); + }, + (instance as { + listeners?: Record any>; + }).listeners ?? {}, + ); +}; + +export { listen, bindListeners }; diff --git a/packages/ringcentral-integration/lib/getIntlDateTimeFormatter/getIntlDateTimeFormatter.ts b/packages/ringcentral-integration/lib/getIntlDateTimeFormatter/getIntlDateTimeFormatter.ts index 4bcc0083f0..021561c197 100644 --- a/packages/ringcentral-integration/lib/getIntlDateTimeFormatter/getIntlDateTimeFormatter.ts +++ b/packages/ringcentral-integration/lib/getIntlDateTimeFormatter/getIntlDateTimeFormatter.ts @@ -1,8 +1,27 @@ import isToday from '../isToday'; -export const formatterCache = {}; +export const formatterCache: Record = {}; -export function getFormatter(locale, options) { +interface DateTimeFormatOptions { + localeMatcher?: string; + weekday?: string; + era?: string; + year?: string; + month?: string; + day?: string; + hour?: string; + minute?: string; + second?: string; + timeZoneName?: string; + formatMatcher?: string; + hour12?: boolean; + timeZone?: string; +} + +export function getFormatter( + locale: string, + options: DateTimeFormatOptions, +): Intl.DateTimeFormat { const key = JSON.stringify([locale, options]); if (!formatterCache[key]) { formatterCache[key] = new Intl.DateTimeFormat(locale, { ...options }); diff --git a/packages/ringcentral-integration/lib/messageHelper/messageHelper.interface.ts b/packages/ringcentral-integration/lib/messageHelper/messageHelper.interface.ts index b6e5cb522e..d0fe1139e7 100644 --- a/packages/ringcentral-integration/lib/messageHelper/messageHelper.interface.ts +++ b/packages/ringcentral-integration/lib/messageHelper/messageHelper.interface.ts @@ -1,15 +1,3 @@ -import { GetMessageInfoResponse } from '@rc-ex/core/definitions'; - -export interface NormalizedMessageRecord - extends Omit< - GetMessageInfoResponse, - 'conversationId' | 'creationTime' | 'lastModifiedTime' | 'conversation' - > { - conversationId: string; - creationTime: number; - lastModifiedTime: number; -} - export interface Correspondent { phoneNumber?: string; extensionNumber?: string; diff --git a/packages/ringcentral-integration/lib/messageHelper/messageHelper.ts b/packages/ringcentral-integration/lib/messageHelper/messageHelper.ts index 5516efe540..d7fceb302b 100644 --- a/packages/ringcentral-integration/lib/messageHelper/messageHelper.ts +++ b/packages/ringcentral-integration/lib/messageHelper/messageHelper.ts @@ -4,10 +4,10 @@ import { } from '@rc-ex/core/definitions'; import { messageTypes } from '../../enums/messageTypes'; +import { Message } from '../../interfaces/MessageStore.model'; import removeUri from '../removeUri'; import { - NormalizedMessageRecord, Correspondent, VoicemailAttachment, FaxAttachment, @@ -26,27 +26,27 @@ export function filterNumbers( }); } -export function messageIsDeleted(message: NormalizedMessageRecord) { +export function messageIsDeleted(message: Message) { return ( message.availability === 'Deleted' || message.availability === 'Purged' ); } -export function messageIsTextMessage(message: NormalizedMessageRecord) { +export function messageIsTextMessage(message: Message) { return ( message.type !== messageTypes.fax && message.type !== messageTypes.voiceMail ); } -export function messageIsFax(message: NormalizedMessageRecord) { +export function messageIsFax(message: Message) { return message.type === messageTypes.fax; } -export function messageIsVoicemail(message: NormalizedMessageRecord) { +export function messageIsVoicemail(message: Message) { return message.type === messageTypes.voiceMail; } -export function messageIsAcceptable(message: NormalizedMessageRecord) { +export function messageIsAcceptable(message: Message) { // do not show submitted faxes or sending failed faxes now // do not show deleted messages return ( @@ -57,7 +57,7 @@ export function messageIsAcceptable(message: NormalizedMessageRecord) { ); } -export function getMessageType(message: NormalizedMessageRecord) { +export function getMessageType(message: Message) { if (messageIsTextMessage(message)) { return messageTypes.text; } @@ -74,7 +74,7 @@ export function getMyNumberFromMessage({ message, myExtensionNumber, }: { - message: NormalizedMessageRecord; + message: Message; myExtensionNumber: string; }) { if (!message) { @@ -113,7 +113,7 @@ export function getRecipientNumbersFromMessage({ message, myNumber, }: { - message: NormalizedMessageRecord; + message: Message; myNumber: Correspondent; }) { if (!message) { @@ -138,7 +138,7 @@ export function getRecipients({ message, myExtensionNumber, }: { - message: NormalizedMessageRecord; + message: Message; myExtensionNumber: string; }) { const myNumber = getMyNumberFromMessage({ @@ -156,7 +156,7 @@ export function getNumbersFromMessage({ message, }: { extensionNumber: string; - message: NormalizedMessageRecord; + message: Message; }) { if (!message) { return {}; @@ -220,7 +220,7 @@ export function sortSearchResults(a: SortEntity, b: SortEntity) { } export function getVoicemailAttachment( - message: NormalizedMessageRecord, + message: Message, accessToken: string, ): VoicemailAttachment { const attachment = message.attachments && message.attachments[0]; @@ -238,7 +238,7 @@ export function getVoicemailAttachment( } export function getFaxAttachment( - message: NormalizedMessageRecord, + message: Message, accessToken: string, ): FaxAttachment { const attachment = message.attachments && message.attachments[0]; @@ -254,7 +254,7 @@ export function getFaxAttachment( } export function getMMSAttachments( - message: NormalizedMessageRecord, + message: Message, accessToken: string, ): MessageAttachmentInfo[] { if (!message.attachments || message.attachments.length === 0) { @@ -280,17 +280,15 @@ export function getConversationId(record: GetMessageInfoResponse) { return conversationId.toString(); } -export function sortByCreationTime( - a: NormalizedMessageRecord, - b: NormalizedMessageRecord, +export function sortByCreationTime( + a: T, + b: T, ) { if (a.creationTime === b.creationTime) return 0; return a.creationTime > b.creationTime ? -1 : 1; } -export function normalizeRecord( - record: GetMessageInfoResponse, -): NormalizedMessageRecord { +export function normalizeRecord(record: GetMessageInfoResponse): Message { const newRecord = removeUri(record) as GetMessageInfoResponse; const conversationId = getConversationId(record); delete newRecord.conversation; @@ -302,7 +300,7 @@ export function normalizeRecord( }; } -export function messageIsUnread(message: NormalizedMessageRecord) { +export function messageIsUnread(message: Message) { return ( message.direction === 'Inbound' && message.readStatus !== 'Read' && @@ -316,11 +314,11 @@ export function messageIsUnread(message: NormalizedMessageRecord) { * total(SMS, Pager, Text): 250 * @param {*} records */ -export const filterMessages = (messages: NormalizedMessageRecord[]) => { - function sortByCreationTime(records: NormalizedMessageRecord[]) { +export const filterMessages = (messages: Message[]) => { + function sortByCreationTime(records: Message[]) { return records.sort((a, b) => sortByDate(a, b)); } - function groupMessages(records: NormalizedMessageRecord[]) { + function groupMessages(records: Message[]) { const faxRecords = records.filter(messageIsFax); const voiceMailRecords = records.filter(messageIsVoicemail); const textRecords = records.filter(messageIsTextMessage); diff --git a/packages/ringcentral-integration/lib/proxy/proxify.ts b/packages/ringcentral-integration/lib/proxy/proxify.ts index f6a292a8ce..c8e2c0be7a 100644 --- a/packages/ringcentral-integration/lib/proxy/proxify.ts +++ b/packages/ringcentral-integration/lib/proxy/proxify.ts @@ -1,4 +1,4 @@ -export default function proxify( +export function proxify( prototype: object, property: string, descriptor: TypedPropertyDescriptor<(...args: any) => Promise>, @@ -32,3 +32,5 @@ export default function proxify( value: proxyFn, }; } + +export default proxify; diff --git a/packages/ringcentral-integration/lib/proxy/proxyState.ts b/packages/ringcentral-integration/lib/proxy/proxyState.ts deleted file mode 100644 index 9e045dbf07..0000000000 --- a/packages/ringcentral-integration/lib/proxy/proxyState.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { RcModuleV2 } from '@ringcentral-integration/core'; - -/** - * proxy state for client's async state changes - * - * @param callback it should be a async function, and run in a reducer. - */ -export const proxyState = ( - callback: (module: T, state: any) => Promise, -) => ( - target: RcModuleV2, - key: string, - descriptor?: TypedPropertyDescriptor, -): any => { - if (target.__proxyState__) { - target.__proxyState__[key] = callback; - } else { - target.__proxyState__ = { - [key]: callback, - }; - } - return descriptor; -}; diff --git a/packages/ringcentral-integration/modules/AccountInfoV2/AccountInfo.ts b/packages/ringcentral-integration/modules/AccountInfoV2/AccountInfo.ts index f1aea267fd..02aea45f00 100644 --- a/packages/ringcentral-integration/modules/AccountInfoV2/AccountInfo.ts +++ b/packages/ringcentral-integration/modules/AccountInfoV2/AccountInfo.ts @@ -42,17 +42,6 @@ export class AccountInfo extends DataFetcherV2Consumer< return !!this._deps.rolesAndPermissions.permissions?.ReadCompanyInfo; } - onInitSuccess() { - // TODO: refactor for Analytics V2 - (this.parentModule as any).analytics?._identify?.({ - userId: this._deps.auth?.ownerId, - accountId: this.id, - servicePlanId: this.servicePlan.id, - edition: this.servicePlan.edition, - CRMEnabled: this._deps.rolesAndPermissions.tierEnabled, - }); - } - async onStateChange() { if ( this._deps.auth.loginStatus === loginStatus.loggedIn && diff --git a/packages/ringcentral-integration/modules/ActiveCallControlV2/ActiveCallControl.ts b/packages/ringcentral-integration/modules/ActiveCallControlV2/ActiveCallControl.ts index c9d6cec7e9..3dd612e570 100644 --- a/packages/ringcentral-integration/modules/ActiveCallControlV2/ActiveCallControl.ts +++ b/packages/ringcentral-integration/modules/ActiveCallControlV2/ActiveCallControl.ts @@ -97,6 +97,8 @@ export class ActiveCallControl extends RcModuleV2 { private _enableAutoSwitchFeature: boolean; private _autoMergeWebphoneSessionsMap: Map; private _onCallSwitchedFunc: (args: any) => any; + private _activeSession: Session; + constructor(deps: Deps) { super({ deps, @@ -247,9 +249,10 @@ export class ActiveCallControl extends RcModuleV2 { _addTrackToActiveSession() { const telephonySessionId = this.activeSessionId; - const activeRCCallSession = this.rcCallSessions.find( - (s) => s.telephonySessionId === telephonySessionId, - ); + const activeRCCallSession = + this.rcCallSessions.find( + (s) => s.telephonySessionId === telephonySessionId, + ) || this._activeSession; if (activeRCCallSession && activeRCCallSession.webphoneSession) { const { _remoteVideo, _localVideo } = this._deps.webphone; activeRCCallSession.webphoneSession.addTrack(_remoteVideo, _localVideo); @@ -378,7 +381,6 @@ export class ActiveCallControl extends RcModuleV2 { message = this._checkRingOutCallDirection(message); this._lastSubscriptionMessage = message; if (this._rcCall) { - console.log('notification event:', JSON.stringify(message, null, 2)); this._rcCall.onNotificationEvent(message); } } @@ -571,6 +573,7 @@ export class ActiveCallControl extends RcModuleV2 { } _getTrackEventName(name: string) { + // TODO: refactor to remove `this.parentModule`. const currentPath = this._deps.routerInteraction?.currentPath; const showCallLog = this.parentModule.callLogSection?.show; const showNotification = this.parentModule.callLogSection?.showNotification; @@ -850,6 +853,7 @@ export class ActiveCallControl extends RcModuleV2 { ); await this._holdOtherCalls(telephonySessionId); await session.unhold(); + this._activeSession = session; const { webphoneSession } = session; if (webphoneSession && webphoneSession.__rc_callStatus) { webphoneSession.__rc_callStatus = sessionStatus.connected; @@ -1045,6 +1049,8 @@ export class ActiveCallControl extends RcModuleV2 { this._autoMergeWebphoneSessionsMap.delete(webphoneSession); } else { this.setActiveSessionId(telephonySessionId); + this._holdOtherCalls(telephonySessionId); + this._addTrackToActiveSession(); } this.updateActiveSessions(); }); @@ -1119,6 +1125,7 @@ export class ActiveCallControl extends RcModuleV2 { const session = this._rcCall.sessions.find((s: Session) => { return s.id === telephonySessionId; }); + this._activeSession = session; await this._holdOtherCalls(telephonySessionId); const { webphoneSession } = session; const deviceId = this._deps.webphone?.device?.id; @@ -1234,6 +1241,15 @@ export class ActiveCallControl extends RcModuleV2 { homeCountryId: params.homeCountryId, }; const session = await this._rcCall.makeCall(sdkMakeCallParams); + this._activeSession = session; + session.webphoneSession.on('progress', () => { + if ( + session.telephonySessionId && + this.activeSessionId !== session.telephonySessionId + ) { + this.setActiveSessionId(session.telephonySessionId); + } + }); this._triggerAutoMergeEvent(); return session; } catch (error) { diff --git a/packages/ringcentral-integration/modules/ActiveCallsV2/ActiveCalls.ts b/packages/ringcentral-integration/modules/ActiveCallsV2/ActiveCalls.ts index 19180c195c..3d65f600ba 100644 --- a/packages/ringcentral-integration/modules/ActiveCallsV2/ActiveCalls.ts +++ b/packages/ringcentral-integration/modules/ActiveCallsV2/ActiveCalls.ts @@ -48,11 +48,7 @@ export class ActiveCalls extends DataFetcherV2Consumer< ttl, fetchFunction: async (): Promise => fetchList((params: any) => - this._deps.client - .account() - .extension() - .activeCalls() - .list(params), + this._deps.client.account().extension().activeCalls().list(params), ), readyCheckFunction: () => !!( diff --git a/packages/ringcentral-integration/modules/AddressBookV2/AddressBook.interface.ts b/packages/ringcentral-integration/modules/AddressBookV2/AddressBook.interface.ts index bdb1d3f9a1..3bbfdb9d5d 100644 --- a/packages/ringcentral-integration/modules/AddressBookV2/AddressBook.interface.ts +++ b/packages/ringcentral-integration/modules/AddressBookV2/AddressBook.interface.ts @@ -1,3 +1,4 @@ +import { AddressBookSync } from '@rc-ex/core/definitions'; import { DataFetcherV2ConsumerBaseDeps, DataSourceBaseProps, @@ -21,3 +22,9 @@ export interface SyncParameters { syncToken?: string; pageId?: number; } + +export type PersonalContactResource = AddressBookSync['records'][number]; +export interface AddressBookData { + records: PersonalContactResource[]; + syncToken: AddressBookSync['syncInfo']['syncToken']; +} diff --git a/packages/ringcentral-integration/modules/AddressBookV2/AddressBook.ts b/packages/ringcentral-integration/modules/AddressBookV2/AddressBook.ts index 122eaab6a4..0afe85f5d1 100644 --- a/packages/ringcentral-integration/modules/AddressBookV2/AddressBook.ts +++ b/packages/ringcentral-integration/modules/AddressBookV2/AddressBook.ts @@ -1,6 +1,6 @@ -import { AddressBookSync } from '@rc-ex/core/definitions'; import { computed } from '@ringcentral-integration/core'; import { forEach, map } from 'ramda'; +import { availabilityTypes } from '../../enums/availabilityTypes'; import { phoneSources } from '../../enums/phoneSources'; import { ContactModel, ContactSource } from '../../interfaces/Contact.model'; @@ -14,12 +14,21 @@ import { Module } from '../../lib/di'; import proxify from '../../lib/proxy/proxify'; import sleep from '../../lib/sleep'; import { DataFetcherV2Consumer, DataSource } from '../DataFetcherV2'; -import { Deps } from './AddressBook.interface'; -import { decodeAddressBookResponse, getSyncParams } from './helpers'; +import { + AddressBookData, + Deps, + PersonalContactResource, +} from './AddressBook.interface'; +import { processAddressBookResponse, getSyncParams } from './helpers'; export const DEFAULT_FETCH_INTERVAL = 1000; export const DEFAULT_CONTACTS_PER_PAGE = 250; +interface AddressBookUpdate { + deleted: Record; + upsert: Record; +} + @Module({ name: 'AddressBook', deps: [ @@ -30,10 +39,7 @@ export const DEFAULT_CONTACTS_PER_PAGE = 250; ], }) export class AddressBook - extends DataFetcherV2Consumer< - Deps, - Pick - > + extends DataFetcherV2Consumer implements ContactSource { constructor(deps: Deps) { super({ @@ -64,7 +70,7 @@ export class AddressBook } get syncToken() { - return this.data?.syncInfo?.syncToken; + return this.data?.syncToken; } protected async _fetch(perPage: number, syncToken: string, pageId?: number) { @@ -73,7 +79,7 @@ export class AddressBook syncToken, pageId, }); - return decodeAddressBookResponse( + return processAddressBookResponse( await this._deps.client .account() .extension() @@ -82,11 +88,33 @@ export class AddressBook ); } - protected async _sync() { + protected _processISyncData(records: PersonalContactResource[]) { + if (records?.length > 0) { + const updatedRecords: PersonalContactResource[] = []; + const processedIDMap: Record = {}; + forEach((record) => { + if (record.availability === availabilityTypes.alive) { + // Only keep entries that is 'alive', omit 'purged' and 'deleted' + updatedRecords.push(record); + } + processedIDMap[record.id] = true; + }, records); + forEach((record) => { + if (!processedIDMap[record.id]) { + // record has no updates + updatedRecords.push(record); + } + }, this.data.records); + return updatedRecords; + } + return this.data.records; + } + + protected async _sync(): Promise { try { const syncToken = this.syncToken; const perPage = this._perPage; - let records: AddressBookSync['records'] = []; + let records: PersonalContactResource[] = []; let response = await this._fetch(perPage, syncToken); records = records.concat(response.records ?? []); while (response.nextPageId) { @@ -94,13 +122,16 @@ export class AddressBook response = await this._fetch(perPage, syncToken, response.nextPageId); records = records.concat(response.records ?? []); } + if (response.syncInfo.syncType === 'ISync') { + records = this._processISyncData(records); + } return { - syncInfo: response.syncInfo, + syncToken: response.syncInfo.syncToken, records, }; } catch (error) { if (error?.response?.status === 403) { - return null; + return {} as AddressBookData; } throw error; } @@ -109,7 +140,7 @@ export class AddressBook // interface of ContactSource @proxify async sync() { - await this._sync(); + await this._deps.dataFetcherV2.fetchData(this._source); } // interface of ContactSource diff --git a/packages/ringcentral-integration/modules/AddressBookV2/helpers.ts b/packages/ringcentral-integration/modules/AddressBookV2/helpers.ts index ef5c2695b3..8a4b015486 100644 --- a/packages/ringcentral-integration/modules/AddressBookV2/helpers.ts +++ b/packages/ringcentral-integration/modules/AddressBookV2/helpers.ts @@ -22,7 +22,7 @@ export function decodeName(str: string) { }); } -export function decodeAddressBookResponse(data: AddressBookSync) { +export function processAddressBookResponse(data: AddressBookSync) { if (Array.isArray(data?.records)) { forEach((record) => { if (record.firstName) { @@ -31,6 +31,8 @@ export function decodeAddressBookResponse(data: AddressBookSync) { if (record.lastName) { record.lastName = decodeName(record.lastName); } + // remove uri from record to reduce size + delete record.uri; }, data.records); } return data; diff --git a/packages/ringcentral-integration/modules/Analytics/Analytics.ts b/packages/ringcentral-integration/modules/Analytics/Analytics.ts index f5d1bfa941..cd1c17bbaa 100644 --- a/packages/ringcentral-integration/modules/Analytics/Analytics.ts +++ b/packages/ringcentral-integration/modules/Analytics/Analytics.ts @@ -1,6 +1,6 @@ import RouterInteraction from '../../../ringcentral-widgets/modules/RouterInteraction'; import moduleStatuses from '../../enums/moduleStatuses'; -import { Segment } from '../../lib/Analytics'; +import { Segment, Pendo } from '../../lib/Analytics'; import { Module } from '../../lib/di'; import RcModule from '../../lib/RcModule'; import saveBlob from '../../lib/saveBlob'; @@ -36,6 +36,7 @@ export interface TrackAction { fromType?: string; callSettingMode?: string; phoneNumber?: string; + recipient?: any; } export interface TrackLog { @@ -54,6 +55,23 @@ export interface TrackItem { funcImpl: TrackImpl; } +export interface PendoAgent { + visitor: { + id: string; + appName: string; + appVersion: string; + appBrand: string; + plaBrand: string; + countryCode?: string; + companyName?: string; + [key: string]: string; + }; + account: { + id: string; + [key: string]: string; + }; +} + function warn() { console.warn('Do NOT call this directly.'); } @@ -136,6 +154,7 @@ export class Analytics extends RcModule< > { // TODO: add state interface private _analyticsKey: string; + private _pendoApiKey: string; private _appName: string; private _appVersion: string; private _brandCode: string; @@ -163,6 +182,7 @@ export class Analytics extends RcModule< private _dialerUI: any; private _segment: any; + private _pendo: any; private _trackList: TrackItem[]; private _useLog: boolean; private _logs: TrackLog[] = []; @@ -173,10 +193,13 @@ export class Analytics extends RcModule< private _callLogSection: any; private _activeCallControl: any; private _enablePendo: boolean; + private _waitPendoCount: number; + private _pendoTimeout: ReturnType; constructor({ // config analyticsKey, + pendoApiKey, appName, appVersion, brandCode, @@ -218,6 +241,7 @@ export class Analytics extends RcModule< // config this._analyticsKey = analyticsKey; + this._pendoApiKey = pendoApiKey; this._appName = appName; this._appVersion = appVersion; this._brandCode = brandCode; @@ -254,9 +278,17 @@ export class Analytics extends RcModule< this._useLog = useLog; this._lingerThreshold = lingerThreshold; this._enablePendo = enablePendo; + this._pendo = null; + this._waitPendoCount = 0; + this._pendoTimeout = null; + if (this._enablePendo && this._pendoApiKey) { + Pendo.init(this._pendoApiKey, (pendoInstance: any) => { + this._pendo = pendoInstance; + }); + } } - private _identify({ + protected _identify({ userId, ...props }: { userId: string } & Record) { @@ -271,11 +303,57 @@ export class Analytics extends RcModule< integrations: { All: true, Mixpanel: true, - Pendo: this._enablePendo, }, }, ); } + if (this._enablePendo && this._pendoApiKey) { + this._pendoInitialize({ userId, ...props }); + } + } + + protected _pendoInitialize({ + userId, + ...props + }: { userId: string } & Record) { + if (!this._accountInfo || !this._accountInfo.id || !userId) { + return; + } + if (this._pendoTimeout) { + clearTimeout(this._pendoTimeout); + } + if (this._waitPendoCount > 3) { + return; + } + if (!this._pendo) { + this._pendoTimeout = setTimeout(() => { + this._waitPendoCount += 1; + this._pendoInitialize({ userId, ...props }); + }, 5 * 1000); + return; + } + const initializeFunc = !this._pendo.isReady() + ? this._pendo.initialize + : this._pendo.updateOptions; + const pendoAgent: PendoAgent = { + visitor: { + id: userId, + ...props, + companyName: this._extensionInfo?.info?.contact?.company, + appName: this._appName, + appVersion: this._appVersion, + appBrand: this._brandCode, + plaBrand: this._accountInfo?.serviceInfo?.brand?.name, + countryCode: this._accountInfo?.countryCode, + }, + account: { + id: this._accountInfo.id, + }, + }; + typeof initializeFunc === 'function' && + initializeFunc({ + ...pendoAgent, + }); } track(event: string, properties: any = {}) { @@ -290,7 +368,6 @@ export class Analytics extends RcModule< integrations: { All: true, Mixpanel: true, - Pendo: this._enablePendo, }, }); if (this._useLog) { @@ -354,7 +431,6 @@ export class Analytics extends RcModule< integrations: { All: true, Mixpanel: true, - Pendo: this._enablePendo, }, }); } @@ -391,9 +467,7 @@ export class Analytics extends RcModule< @tracking private _authentication(action: TrackAction) { if (this._auth?.actionTypes.loginSuccess === action.type) { - this._identify({ - userId: this._auth.ownerId, - }); + this.setUserId(); this.track('Authentication'); } } @@ -544,7 +618,7 @@ export class Analytics extends RcModule< private _navigate(action: TrackAction) { if (this._routerInteraction?.actionTypes.locationChange === action.type) { const path = action.payload && action.payload.pathname; - const target = this._getTrackTarget(path); + const target = this.getTrackTarget(path); if (target) { this.trackNavigation(target); } @@ -846,9 +920,13 @@ export class Analytics extends RcModule< } } - private _getTrackTarget( - path = this._routerInteraction?.currentPath, - ): TrackTarget { + setUserId() { + this._identify({ + userId: this._auth.ownerId, + }); + } + + getTrackTarget(path = this._routerInteraction?.currentPath): TrackTarget { if (!path) { return null; } @@ -935,7 +1013,7 @@ export class Analytics extends RcModule< this._meeting?.actionTypes.initScheduling === action.type || this._rcVideo?.actionTypes.initCreating === action.type ) { - const target = this._getTrackTarget(this._routerInteraction?.currentPath); + const target = this.getTrackTarget(this._routerInteraction?.currentPath); if (target) { this.trackSchedule(target); } @@ -1057,9 +1135,11 @@ export class Analytics extends RcModule< private _smsHistoryPlaceRingOutCall(action: TrackAction) { if ( this._messageStore?.actionTypes.clickToCall === action.type && - this._callingSettings.callingMode === callingModes.ringout + this._callingSettings.callingMode !== callingModes.webphone ) { - this.track('Call: Place RingOut call/SMS history'); + this.track('Call: Place RingOut call/SMS history', { + 'RingOut type': this._callingSettings?.callWith, + }); } } @@ -1067,9 +1147,11 @@ export class Analytics extends RcModule< private _callHistoryPlaceRingOutCall(action: TrackAction) { if ( this._callHistory?.actionTypes.clickToCall === action.type && - this._callingSettings.callingMode === callingModes.ringout + this._callingSettings.callingMode !== callingModes.webphone ) { - this.track('Call: Place RingOut call/Call history'); + this.track('Call: Place RingOut call/Call history', { + 'RingOut type': this._callingSettings?.callWith, + }); } } @@ -1077,10 +1159,12 @@ export class Analytics extends RcModule< private _dialerPlaceRingOutCall(action: TrackAction) { if ( this._dialerUI?.actionTypes.call === action.type && - action.phoneNumber?.length > 0 && - this._callingSettings.callingMode === callingModes.ringout + (action.phoneNumber?.length > 0 || action.recipient) && + this._callingSettings.callingMode !== callingModes.webphone ) { - this.track('Call: Place RingOut call/Dialer'); + this.track('Call: Place RingOut call/Dialer', { + 'RingOut type': this._callingSettings?.callWith, + }); } } diff --git a/packages/ringcentral-integration/modules/Analytics/trackEvents.ts b/packages/ringcentral-integration/modules/Analytics/trackEvents.ts index 4bb344bee1..09728c465e 100644 --- a/packages/ringcentral-integration/modules/Analytics/trackEvents.ts +++ b/packages/ringcentral-integration/modules/Analytics/trackEvents.ts @@ -47,12 +47,14 @@ export const trackEvents = ObjectMap.fromObject({ clickToDialVoicemailList: 'Click To Dial (Voicemail List)', clickToSMSVoicemailList: 'Click to SMS (Voicemail List)', deleteVoicemail: 'Delete Voicemail', + deleteFax: 'Delete Fax', flagVoicemail: 'Flag Voicemail', clickToDialContactDetails: 'Click To Dial (Contact Details)', clickToSMSContactDetails: 'Click To SMS (Contact Details)', clickToDialCallHistory: 'Click To dial (Call History)', clickToDialCallHistoryWithRingOut: 'Call: Place RingOut call/Call history', clickToSMSCallHistory: 'Click To SMS (Call History)', + callPlaceRingOutCallSMSHistory: 'Call: Place RingOut call/SMS history', inviteWithTextConference: 'Invite With Text (Conference)', selectAdditionalDialInNumber: 'Select Additional Dial-in Number (Conference)', joinAsHostConference: 'Join As Host (Conference)', diff --git a/packages/ringcentral-integration/modules/AnalyticsV2/Analytics.interface.ts b/packages/ringcentral-integration/modules/AnalyticsV2/Analytics.interface.ts new file mode 100644 index 0000000000..e8c8c61137 --- /dev/null +++ b/packages/ringcentral-integration/modules/AnalyticsV2/Analytics.interface.ts @@ -0,0 +1,72 @@ +import { BrandConfig } from '../BrandV2'; +import { AccountInfo } from '../AccountInfoV2'; +import { RolesAndPermissions } from '../RolesAndPermissionsV2'; +import { ExtensionInfo } from '../ExtensionInfoV2'; +import { Locale } from '../LocaleV2'; + +interface RouterInteraction { + currentPath: string; +} + +interface Auth { + loggedIn: boolean; + ownerId: string; +} + +export interface Deps { + auth: Auth; + brandConfig: BrandConfig; + analyticsOptions: AnalyticsOptions; + accountInfo?: AccountInfo; + rolesAndPermissions?: RolesAndPermissions; + extensionInfo?: ExtensionInfo; + locale?: Locale; + routerInteraction?: RouterInteraction; +} + +export interface AnalyticsOptions { + /** + * Segment key. + */ + analyticsKey: string; + /** + * App version. + */ + appVersion: string; + /** + * Pendo toggle, the default value is `false`. + */ + enablePendo?: boolean; + /** + * Enable memory log, the default value is `false`. + */ + useLog?: boolean; + /** + * Linger the router timeout, the default value is 1s. + */ + lingerThreshold?: number; + /** + * Track router list + */ + trackRouters?: TrackRouter[]; +} + +export interface TrackProps { + appName: string; + appVersion: string; + brand: string; + 'App Language': string; + 'Browser Language': string; + 'Extension Type': string; +} + +export interface TrackRouter { + eventPostfix: string; + router: string; +} + +export interface TrackLog { + timeStamp: string; + event: string; + trackProps: TrackProps; +} diff --git a/packages/ringcentral-integration/modules/AnalyticsV2/Analytics.ts b/packages/ringcentral-integration/modules/AnalyticsV2/Analytics.ts new file mode 100644 index 0000000000..6cc5e0673e --- /dev/null +++ b/packages/ringcentral-integration/modules/AnalyticsV2/Analytics.ts @@ -0,0 +1,224 @@ +import { RcModuleV2, watch } from '@ringcentral-integration/core'; +import { Segment } from '../../lib/Analytics'; +import { Module } from '../../lib/di'; +import saveBlob from '../../lib/saveBlob'; +import { Deps, TrackLog, TrackProps, TrackRouter } from './Analytics.interface'; +import { trackRouters } from './analyticsRouters'; + +// TODO: refactoring the module against `https://docs.google.com/spreadsheets/d/1xufV6-C-RJR6OJgwFYHYzNQwhIdN4BXXCo8ABs7RT-8/edit#gid=1480480736` +// TODO: if use `dialerUI`/`callLogSection`/`adapter`, make sure they should all be RcModuleV2 +@Module({ + name: 'Analytics', + deps: [ + 'Auth', + 'BrandConfig', + 'AnalyticsOptions', + { dep: 'AccountInfo', optional: true }, + { dep: 'ExtensionInfo', optional: true }, + { dep: 'RolesAndPermissions', optional: true }, + { dep: 'RouterInteraction', optional: true }, + { dep: 'Locale', optional: true }, + ], +}) +export class Analytics extends RcModuleV2 { + protected _useLog = this._deps.analyticsOptions.useLog ?? false; + + protected _lingerThreshold = + this._deps.analyticsOptions.lingerThreshold ?? 1000; + + protected _enablePendo = this._deps.analyticsOptions.enablePendo ?? false; + + protected _trackRouters = + this._deps.analyticsOptions.trackRouters ?? trackRouters; + + private _segment: any; + + protected _logs: TrackLog[] = []; + + protected _lingerTimeout?: NodeJS.Timeout = null; + + constructor(deps: Deps) { + super({ + deps, + }); + this._segment = Segment(); + } + + onInitOnce() { + if (this._deps.routerInteraction) { + // make sure that track if refresh app + this.trackRouter(); + watch( + this, + () => this._deps.routerInteraction.currentPath, + (currentPath) => { + this.trackRouter(currentPath); + }, + ); + } + + if (this._deps.accountInfo) { + watch( + this, + () => this._deps.accountInfo.ready, + (accountInfoReady) => { + if (accountInfoReady) { + this._identify({ + userId: this._deps.auth.ownerId, + accountId: this._deps.accountInfo.id, + servicePlanId: this._deps.accountInfo.servicePlan.id, + edition: this._deps.accountInfo.servicePlan.edition, + CRMEnabled: this._deps.rolesAndPermissions?.tierEnabled, + }); + } + }, + ); + } + } + + trackRouter(currentPath = this._deps.routerInteraction.currentPath) { + const target = this.getTrackTarget(currentPath); + if (target) { + this.trackNavigation(target); + } + + if (this._lingerTimeout) { + clearTimeout(this._lingerTimeout); + } + this._lingerTimeout = setTimeout(() => { + this._lingerTimeout = null; + if (target && this._deps.routerInteraction.currentPath === currentPath) { + this.trackLinger(target); + } + }, this._lingerThreshold); + } + + onInit() { + if (this._deps.analyticsOptions.analyticsKey && this._segment) { + this._segment.load(this._deps.analyticsOptions.analyticsKey, { + integrations: { + All: true, + Mixpanel: true, + Pendo: this._enablePendo, + }, + }); + } + } + + setUserId() { + this._identify({ + userId: this._deps.auth.ownerId, + }); + } + + protected _identify({ + userId, + ...props + }: { userId: string } & Record) { + if (this.analytics) { + this.analytics.identify( + userId, + { + ...props, + companyName: this._deps.extensionInfo?.info?.contact?.company, + }, + { + integrations: { + All: true, + Mixpanel: true, + Pendo: this._enablePendo, + }, + }, + ); + } + } + + track(event: string, properties: any = {}) { + if (!this.analytics) { + return; + } + const trackProps: TrackProps = { + ...this.trackProps, + ...properties, + }; + this.analytics.track(event, trackProps, { + integrations: { + All: true, + Mixpanel: true, + Pendo: this._enablePendo, + }, + }); + if (this._useLog) { + this._logs.push({ + timeStamp: new Date().toISOString(), + event, + trackProps, + }); + } + } + + downloadLogs() { + if (!this._useLog) { + return; + } + const blob = new Blob([JSON.stringify(this._logs, null, 2)], { + type: 'application/json', + }); + saveBlob('logs.json', blob); + } + + trackNavigation({ router, eventPostfix }: TrackRouter) { + const trackProps = { + router, + appName: this._deps.brandConfig.appName, + appVersion: this._deps.analyticsOptions.appVersion, + brand: this._deps.brandConfig.brandCode, + }; + this.track(`Navigation: Click/${eventPostfix}`, trackProps); + } + + trackLinger({ router, eventPostfix }: TrackRouter) { + const trackProps = { + router, + appName: this._deps.brandConfig.appName, + appVersion: this._deps.analyticsOptions.appVersion, + brand: this._deps.brandConfig.brandCode, + }; + this.track(`Navigation: View/${eventPostfix}`, trackProps); + } + + getTrackTarget( + currentPath = this._deps.routerInteraction?.currentPath, + ): TrackRouter { + if (!currentPath) { + return null; + } + const routes = currentPath.split('/'); + let formatRoute: string = null; + const needMatchSecondRoutes = ['calls']; + if (routes.length >= 3 && needMatchSecondRoutes.indexOf(routes[1]) !== -1) { + formatRoute = `/${routes[1]}/${routes[2]}`; + } else if (routes.length > 1) { + formatRoute = `/${routes[1]}`; + } + const target = this._trackRouters.find( + (target) => formatRoute === target.router, + ); + return target; + } + + get analytics() { + return (global as any).analytics; + } + + get trackProps(): TrackProps { + return { + appName: this._deps.brandConfig.appName, + appVersion: this._deps.analyticsOptions.appVersion, + brand: this._deps.brandConfig.brandCode, + 'App Language': this._deps.locale?.currentLocale || '', + 'Browser Language': this._deps.locale?.browserLocale || '', + 'Extension Type': this._deps.extensionInfo?.info.type || '', + }; + } +} diff --git a/packages/ringcentral-integration/modules/AnalyticsV2/analyticsRouters.ts b/packages/ringcentral-integration/modules/AnalyticsV2/analyticsRouters.ts new file mode 100644 index 0000000000..31b5ed69ca --- /dev/null +++ b/packages/ringcentral-integration/modules/AnalyticsV2/analyticsRouters.ts @@ -0,0 +1,64 @@ +import { TrackRouter } from './Analytics.interface'; + +export const trackRouters: TrackRouter[] = [ + { + eventPostfix: 'Dialer', + router: '/dialer', + }, + { + eventPostfix: 'Compose SMS', + router: '/composeText', + }, + { + eventPostfix: 'Messages', + router: '/messages', + }, + { + eventPostfix: 'Conversation', + router: '/conversations', + }, + { + eventPostfix: 'Call History', + router: '/history', + }, + { + eventPostfix: 'All calls page', + router: '/calls', + }, + { + eventPostfix: 'Settings', + router: '/settings', + }, + { + eventPostfix: 'Conference', + router: '/conference', + }, + { + eventPostfix: 'Meeting', + router: '/meeting', + }, + { + eventPostfix: 'Contacts', + router: '/contacts', + }, + { + eventPostfix: 'Call Control', + router: '/calls/active', + }, + { + eventPostfix: 'Transfer', + router: '/transfer', + }, + { + eventPostfix: 'Small call control', + router: '/simplifycallctrl', + }, + { + eventPostfix: 'Flip', + router: '/flip', + }, + { + eventPostfix: 'Add', + router: '/conferenceCall', + }, +]; diff --git a/packages/ringcentral-integration/modules/AnalyticsV2/index.ts b/packages/ringcentral-integration/modules/AnalyticsV2/index.ts new file mode 100644 index 0000000000..789a3c20cf --- /dev/null +++ b/packages/ringcentral-integration/modules/AnalyticsV2/index.ts @@ -0,0 +1,4 @@ +export * from './Analytics'; +export * from './Analytics.interface'; +export * from './trackEvents'; +export * from './analyticsRouters'; diff --git a/packages/ringcentral-integration/modules/AnalyticsV2/trackEvents.ts b/packages/ringcentral-integration/modules/AnalyticsV2/trackEvents.ts new file mode 100644 index 0000000000..09728c465e --- /dev/null +++ b/packages/ringcentral-integration/modules/AnalyticsV2/trackEvents.ts @@ -0,0 +1,83 @@ +import { ObjectMap } from '@ringcentral-integration/core/lib/ObjectMap'; + +export const trackEvents = ObjectMap.fromObject({ + outbound: 'Outbound Call', + mute: 'Call Control: Mute', + unmute: 'Call Control: Unmute', + hold: 'Call Control: Hold', + unhold: 'Call Control: Unhold', + record: 'Call Control: Record', + stopRecord: 'Call Control: Stop Record', + hangup: 'Call Control: Hang up', + answer: 'Call Control: Answer', + voicemail: 'Call Control: To voicemail', + ignore: 'Call Control: Ignore', + holdAndAnswer: 'Call Control: Hold and answer', + endAndAnswer: 'Call Control: End and answer', + switch: 'Call Control: Switch', + confirmSwitch: 'Call Control: Confirm switch', + forward: 'Call Control: Forward', + confirmForward: 'Call Control: Confirm forward', + dialpadOpen: 'Call Control: Open dialpad/Call log page', + dialpadClose: 'Call Control: Close dialpad/Call log page', + clickTransfer: 'Call Control: Transfer', + transfer: 'Call Control: Cold transfer/Transfer page', + authentication: 'Authentication', + logout: 'Logout', + callAttemptWebRTC: 'Call Attempt WebRTC', + callAttempt: 'Call Attempt', + outboundWebRTCCallConnected: 'Outbound WebRTC Call Connected', + outboundCallConnected: 'Outbound Call Connected', + webRTCRegistration: 'WebRTC registration', + smsAttempt: 'SMS Attempt', + smsSentSuccessfully: 'SMS: SMS sent successfully', + smsSentFailed: 'SMS: SMS sent failed', + logCall: 'Log Call', + logSMS: 'Log SMS', + clickToDial: 'Click To Dial', + callPlaceRingOutCallClickToDial: 'Call: Place RingOut call/Click to Dial ', + clickToSMS: 'Click To SMS', + viewEntityDetails: 'View Entity Details', + addEntity: 'Add Entity', + editCallLog: 'Edit Call Log', + editSMSLog: 'Edit SMS Log', + inboundWebRTCCallConnected: 'Inbound WebRTC Call Connected', + coldTransferCall: 'Cold Transfer Call', + clickToDialTextList: 'Click To Dial (Text List)', + clickToDialVoicemailList: 'Click To Dial (Voicemail List)', + clickToSMSVoicemailList: 'Click to SMS (Voicemail List)', + deleteVoicemail: 'Delete Voicemail', + deleteFax: 'Delete Fax', + flagVoicemail: 'Flag Voicemail', + clickToDialContactDetails: 'Click To Dial (Contact Details)', + clickToSMSContactDetails: 'Click To SMS (Contact Details)', + clickToDialCallHistory: 'Click To dial (Call History)', + clickToDialCallHistoryWithRingOut: 'Call: Place RingOut call/Call history', + clickToSMSCallHistory: 'Click To SMS (Call History)', + callPlaceRingOutCallSMSHistory: 'Call: Place RingOut call/SMS history', + inviteWithTextConference: 'Invite With Text (Conference)', + selectAdditionalDialInNumber: 'Select Additional Dial-in Number (Conference)', + joinAsHostConference: 'Join As Host (Conference)', + whatsNew: "What's New", + clickHoldAllCalls: 'Click Hold (All Calls)', + clickHangupAllCalls: 'Click Hangup (All Calls)', + clickRejectAllCalls: 'Click Reject (All Calls)', + clickCallItem: 'Click Call Item (All Calls)', + clickAddCallControl: 'Click Add (Call Control)', + clickMergeCallControl: 'Click Merge (Call Control)', + clickMergeMergeCallControl: 'Click Merge (Merge Call Control)', + clickHangupMergeCallControl: 'Click Hangup (Merge Call Control)', + callInboundCallConnected: 'Call: Inbound call connected', + callOutboundRingOutCallConnected: 'Call: Outbound RingOut Call connected', + clickAddCallsOnHold: 'Click Add (Calls OnHold)', + clickMergeCallsOnHold: 'Click Merge (Calls OnHold)', + clickCloseConfirmMergeModal: 'Click Close (ConfirmMerge Modal)', + clickMergeConfirmMergeModal: 'Click Merge (ConfirmMerge Modal)', + clickRemoveRemoveParticipantsModal: 'Click Remove (RemoveParticipants Modal)', + cancelRemoveRemoveParticipantsModal: + 'Cancel Remove (RemoveParticipants Modal)', + clickHangupParticipantList: 'Click Hangup (Participant List)', + clickParticipantAreaCallControl: 'Click Participant Area (Call Control)', + clickHangupCallsOnHold: 'Click Hangup (Calls OnHold)', + clickMeetingSchedulePage: 'Meeting: Click Schedule/Meeting schedule page', +} as const); diff --git a/packages/ringcentral-integration/modules/AudioSettingsV2/AudioSettings.ts b/packages/ringcentral-integration/modules/AudioSettingsV2/AudioSettings.ts index 15351dabff..9d34c250b8 100644 --- a/packages/ringcentral-integration/modules/AudioSettingsV2/AudioSettings.ts +++ b/packages/ringcentral-integration/modules/AudioSettingsV2/AudioSettings.ts @@ -1,14 +1,14 @@ -import { find, filter } from 'ramda'; import { action, + computed, RcModuleV2, state, storage, - computed, } from '@ringcentral-integration/core'; -import proxify from '../../lib/proxy/proxify'; +import { filter, find } from 'ramda'; import { Module } from '../../lib/di'; -import { Deps, AudioSettingsData } from './AudioSettings.interface'; +import { proxify } from '../../lib/proxy/proxify'; +import { AudioSettingsData, Deps } from './AudioSettings.interface'; import { audioSettingsErrors } from './audioSettingsErrors'; function polyfillGetUserMedia() { diff --git a/packages/ringcentral-integration/modules/Auth/index.ts b/packages/ringcentral-integration/modules/Auth/index.ts index 6c4baddf31..c64383d81b 100644 --- a/packages/ringcentral-integration/modules/Auth/index.ts +++ b/packages/ringcentral-integration/modules/Auth/index.ts @@ -1,9 +1,8 @@ import url from 'url'; - import moduleStatuses from '../../enums/moduleStatuses'; import { Module } from '../../lib/di'; import ensureExist from '../../lib/ensureExist'; -import proxify from '../../lib/proxy/proxify'; +import { proxify } from '../../lib/proxy/proxify'; import RcModule from '../../lib/RcModule'; import validateIsOffline from '../../lib/validateIsOffline'; import actionTypes from './actionTypes'; @@ -89,7 +88,7 @@ export default class Auth extends RcModule { const platform = this._client.service.platform(); const client = this._client.service._client; - const onRequestError = (error) => { + const onRequestError = (error: any) => { if (error instanceof Error && error.message === 'Token revoked') { this.logout(); } @@ -105,7 +104,7 @@ export default class Auth extends RcModule { handler(); } }; - const onLoginError = (error) => { + const onLoginError = (error: any) => { this.store.dispatch({ type: this.actionTypes.loginError, error, @@ -116,18 +115,12 @@ export default class Auth extends RcModule { type: this.actionTypes.logoutSuccess, }); }; - const onLogoutError = (error) => { + const onLogoutError = (error: any) => { platform._cache.clean(); this.store.dispatch({ type: this.actionTypes.logoutError, error, }); - if (error) { - this._alert.danger({ - message: authMessages.logoutError, - payload: error, - }); - } }; const onRefreshSuccess = async () => { this.store.dispatch({ @@ -135,7 +128,7 @@ export default class Auth extends RcModule { token: await platform.auth().data(), }); }; - const onRefreshError = async (error) => { + const onRefreshError = async (error: any) => { // user is still considered logged in if the refreshToken is still valid const isOffline = validateIsOffline(error.message); @@ -181,7 +174,7 @@ export default class Auth extends RcModule { } initialize() { - let loggedIn; + let loggedIn: boolean; this.store.subscribe(async () => { if ( this.status === moduleStatuses.pending && @@ -219,10 +212,7 @@ export default class Auth extends RcModule { type: this.actionTypes.tabSync, loggedIn, token: loggedIn - ? (await this._client.service - .platform() - .auth() - .data()) + ? await this._client.service.platform().auth().data() : null, }); } @@ -302,26 +292,20 @@ export default class Auth extends RcModule { endpointId, tokenType, scope, - }) { + }: any) { this.store.dispatch({ type: this.actionTypes.login, }); let ownerId; if (accessToken) { - await this._client.service - .platform() - .auth() - .setData({ - token_type: tokenType, - access_token: accessToken, - expires_in: expiresIn, - refresh_token_expires_in: expiresIn, - scope, - }); - const extensionData = await this._client - .account() - .extension() - .get(); + await this._client.service.platform().auth().setData({ + token_type: tokenType, + access_token: accessToken, + expires_in: expiresIn, + refresh_token_expires_in: expiresIn, + scope, + }); + const extensionData = await this._client.account().extension().get(); ownerId = extensionData.id; } // TODO: support to set redirectUri in js sdk v4 login function @@ -363,7 +347,7 @@ export default class Auth extends RcModule { localeId, force, implicit = false, - }) { + }: any) { // TODO: support to set redirectUri in js sdk v4 login function if (!this._client.service.platform()._redirectUri) { this._client.service.platform()._redirectUri = redirectUri; @@ -414,10 +398,6 @@ export default class Auth extends RcModule { } } catch (error) { console.error(error); - this._alert.danger({ - message: authMessages.beforeLogoutError, - payload: error, - }); } this.store.dispatch({ type: this.actionTypes.logout, @@ -437,7 +417,7 @@ export default class Auth extends RcModule { * @param {Function} handler * @returns {Function} return that delete handler event, call that will delete that event */ - addBeforeLogoutHandler(handler: Function): Function { + addBeforeLogoutHandler(handler: (...args: any[]) => any) { this._beforeLogoutHandlers.add(handler); return () => { this.removeBeforeLogoutHandler(handler); @@ -448,11 +428,11 @@ export default class Auth extends RcModule { * @function * @param {Function} handler */ - removeBeforeLogoutHandler(handler: Function) { + removeBeforeLogoutHandler(handler: (...args: any[]) => any) { this._beforeLogoutHandlers.delete(handler); } - addAfterLoggedInHandler(handler) { + addAfterLoggedInHandler(handler: (...args: any[]) => any) { this._afterLoggedInHandlers.add(handler); return () => { this._afterLoggedInHandlers.delete(handler); @@ -465,12 +445,9 @@ export default class Auth extends RcModule { accessToken, expiresIn, endpointId, - }) { + }: any) { try { - const extensionData = await this._client - .account() - .extension() - .get(); + const extensionData = await this._client.account().extension().get(); const ownerId = String(extensionData.id); if (ownerId !== String(this.ownerId)) { return; diff --git a/packages/ringcentral-integration/modules/AuthV2/Auth.ts b/packages/ringcentral-integration/modules/AuthV2/Auth.ts index ee3409f524..41c68f7c68 100644 --- a/packages/ringcentral-integration/modules/AuthV2/Auth.ts +++ b/packages/ringcentral-integration/modules/AuthV2/Auth.ts @@ -6,18 +6,17 @@ import { track, } from '@ringcentral-integration/core'; import url from 'url'; - import moduleStatuses from '../../enums/moduleStatuses'; import { Module } from '../../lib/di'; -import proxify from '../../lib/proxy/proxify'; +import { proxify } from '../../lib/proxy/proxify'; import validateIsOffline from '../../lib/validateIsOffline'; import { trackEvents } from '../Analytics'; import { Deps, LoginOptions, + LoginUrlOptions, Token, TokenInfo, - LoginUrlOptions, } from './Auth.interface'; import { authMessages } from './authMessages'; import { loginStatus } from './loginStatus'; @@ -59,7 +58,10 @@ class Auth extends RcModuleV2 { @state token: Token = {}; - @track(trackEvents.authentication) + @track(() => (analytics) => { + analytics.setUserId(); + return [trackEvents.authentication]; + }) @action setLoginSuccess(token: TokenInfo) { this.loginStatus = loginStatus.loggedIn; @@ -71,10 +73,6 @@ class Auth extends RcModuleV2 { expiresIn: token.expires_in, scope: token.scope, }; - // TODO: refactor for Analytics V2 - (this.parentModule as any).analytics?._identify?.({ - userId: token.owner_id, - }); } @action @@ -185,15 +183,9 @@ class Auth extends RcModuleV2 { const onLogoutSuccess = () => { this.setLogoutSuccess(); }; - const onLogoutError = (error: Error) => { + const onLogoutError = () => { platform._cache.clean(); this.setLogoutError(); - if (error) { - this._deps.alert.danger({ - message: authMessages.logoutError, - payload: error, - }); - } }; const onRefreshSuccess = async () => { const token: TokenInfo = await platform.auth().data(); @@ -426,10 +418,6 @@ class Auth extends RcModuleV2 { } } catch (error) { console.error(error); - this._deps.alert.danger({ - message: authMessages.beforeLogoutError, - payload: error, - }); } this.setLogout(); if (this.isImplicit) { diff --git a/packages/ringcentral-integration/modules/AvailabilityMonitor/index.d.ts b/packages/ringcentral-integration/modules/AvailabilityMonitor/index.d.ts index 45eed34eea..c2d9a91e36 100644 --- a/packages/ringcentral-integration/modules/AvailabilityMonitor/index.d.ts +++ b/packages/ringcentral-integration/modules/AvailabilityMonitor/index.d.ts @@ -1,7 +1,7 @@ import Client from 'ringcentral-client'; import RcModule from '../../lib/RcModule'; -import { ClientError } from '../../shared/clientResponse'; +import { ClientError } from '../../interfaces/ClientResponse'; import Alert from '../Alert'; import Environment from '../Environment'; diff --git a/packages/ringcentral-integration/modules/BlockedNumberV2/BlockedNumber.ts b/packages/ringcentral-integration/modules/BlockedNumberV2/BlockedNumber.ts index a4b9762e9e..05b3a172cd 100644 --- a/packages/ringcentral-integration/modules/BlockedNumberV2/BlockedNumber.ts +++ b/packages/ringcentral-integration/modules/BlockedNumberV2/BlockedNumber.ts @@ -29,11 +29,7 @@ export class BlockedNumber extends DataFetcherV2Consumer< cleanOnReset: true, fetchFunction: async (): Promise => fetchList((params: any) => - this._deps.client - .account() - .extension() - .blockedNumber() - .list(params), + this._deps.client.account().extension().blockedNumber().list(params), ), readyCheckFunction: () => !!( diff --git a/packages/ringcentral-integration/modules/CallHistory/index.js b/packages/ringcentral-integration/modules/CallHistory/index.js index c8b5f42d9c..f41caf019c 100644 --- a/packages/ringcentral-integration/modules/CallHistory/index.js +++ b/packages/ringcentral-integration/modules/CallHistory/index.js @@ -527,4 +527,8 @@ export default class CallHistory extends RcModule { } return this.state.endedCalls; } + + get endedCalls() { + return this.recentlyEndedCalls; + } } diff --git a/packages/ringcentral-integration/modules/CallHistoryV2/CallHistory.ts b/packages/ringcentral-integration/modules/CallHistoryV2/CallHistory.ts index f9833eeed9..27352fe2be 100644 --- a/packages/ringcentral-integration/modules/CallHistoryV2/CallHistory.ts +++ b/packages/ringcentral-integration/modules/CallHistoryV2/CallHistory.ts @@ -412,6 +412,7 @@ export class CallHistory extends RcModuleV2 { // TODO: remove recentlyEndedCalls getter, instead of `endedCalls`. /** + * !!Please use `endedCalls` instead of it. * @deprecated */ get recentlyEndedCalls() { diff --git a/packages/ringcentral-integration/modules/CallLoggerV2/CallLogger.interface.ts b/packages/ringcentral-integration/modules/CallLoggerV2/CallLogger.interface.ts new file mode 100644 index 0000000000..4fdcf6f6d0 --- /dev/null +++ b/packages/ringcentral-integration/modules/CallLoggerV2/CallLogger.interface.ts @@ -0,0 +1,69 @@ +import { Storage } from '../StorageV2'; +import { CallHistory, HistoryCall } from '../CallHistoryV2'; +import { CallMonitor } from '../CallMonitorV2'; +import { ActivityMatcher } from '../ActivityMatcherV2'; +import { ContactMatcher } from '../ContactMatcherV2'; +import { TabManager } from '../TabManagerV2'; +import { LogOptions as BaseLogOptions } from '../../lib/LoggerBaseV2'; +import { Entity } from '../../interfaces/Entity.interface'; +import { CallLoggerTriggerType } from '../../enums/callLoggerTriggerTypes'; +import { ActiveCall } from '../../interfaces/Presence.model'; + +export interface CallLoggerOptions { + autoLog?: boolean; + logFunction: (options: BaseLogOptions) => Promise; + readyCheckFunction: () => boolean; +} + +export interface Deps { + storage: Storage; + callHistory: CallHistory; + callMonitor: CallMonitor; + callLoggerOptions: CallLoggerOptions; + activityMatcher?: ActivityMatcher; + contactMatcher?: ContactMatcher; + tabManager?: TabManager; +} + +export type Hook = (sessionId: string) => boolean; + +export type UpdatedCallMap = { + presenceUpdate: ActiveCall & { + isTransferredCall: boolean; + transferredMiddleNumber: string; + phoneNumberUpdated?: boolean; + }; + callLogSync: HistoryCall & { + isTransferredCall: boolean; + transferredMiddleNumber: string; + }; +}; + +export type UpdatedCall = UpdatedCallMap[T]; + +export type LogCallOptions = { + call: HistoryCall | ActiveCall; + contact: Entity; +} & T; + +export interface AutoLogCallOptions { + call: HistoryCall | ActiveCall; + fromEntity?: Entity; + toEntity?: Entity; + triggerType: CallLoggerTriggerType; +} + +export interface TransferredCall { + transferredMiddleNumber: string; +} + +export type LogOptions = { + // TODO: fix type for sessionId + call: Record & { + // sessionId: string; + duration: number; + result: ActiveCall['result'] | HistoryCall['telephonyStatus']; + }; + fromEntity?: Entity; + toEntity?: Entity; +} & T; diff --git a/packages/ringcentral-integration/modules/CallLoggerV2/CallLogger.ts b/packages/ringcentral-integration/modules/CallLoggerV2/CallLogger.ts new file mode 100644 index 0000000000..e4b7267e0e --- /dev/null +++ b/packages/ringcentral-integration/modules/CallLoggerV2/CallLogger.ts @@ -0,0 +1,389 @@ +import { reduce } from 'ramda'; +import { + state, + storage, + action, + watch, + computed, +} from '@ringcentral-integration/core'; +import { Module } from '../../lib/di'; +import { LoggerBase } from '../../lib/LoggerBaseV2'; +import { + isRinging, + isInbound, + removeDuplicateSelfCalls, +} from '../../lib/callLogHelpers'; +import { + CallLoggerTriggerType, + callLoggerTriggerTypes, +} from '../../enums/callLoggerTriggerTypes'; +import proxify from '../../lib/proxy/proxify'; +import { callIdentityFunction } from './callLoggerHelper'; +import { Call } from '../../interfaces/Call.interface'; +import { + AutoLogCallOptions, + Deps, + Hook, + LogCallOptions, + LogOptions, + TransferredCall, + UpdatedCall, + UpdatedCallMap, +} from './CallLogger.interface'; +import { CallLogRecord } from '../CallLogV2'; +import { HistoryCall } from '../CallHistoryV2'; +import { ActiveCall } from '../../interfaces/Presence.model'; + +const DEFAULT_OPACITY = 20; + +@Module({ + name: 'CallLogger', + deps: [ + 'Storage', + 'CallHistory', + 'CallMonitor', + 'CallLoggerOptions', + { dep: 'ActivityMatcher', optional: true }, + { dep: 'ContactMatcher', optional: true }, + { dep: 'TabManager', optional: true }, + ], +}) +export class CallLogger extends LoggerBase { + protected _customMatcherHooks: Hook[] = []; + + protected _identityFunction = callIdentityFunction; + + _logFunction = this._deps.callLoggerOptions.logFunction; + + _readyCheckFunction = this._deps.callLoggerOptions.readyCheckFunction; + + constructor(deps: Deps) { + super(deps, { + enableCache: true, + storageKey: 'CallLogger', + }); + if (typeof this._deps.callLoggerOptions.autoLog !== 'undefined') { + this.autoLog = this._deps.callLoggerOptions.autoLog; + } + } + + @storage + @state + autoLog = true; + + @storage + @state + logOnRinging = true; + + @storage + @state + transferredCallsList: Record[] = []; + + @action + _setLogOnRinging(logOnRinging: boolean) { + this.logOnRinging = !!logOnRinging; + } + + @action + _setAutoLog(autoLog: boolean) { + this.autoLog = !!autoLog; + } + + @action + _addTransferredCall(sessionId: string, transferredMiddleNumber: string) { + this.transferredCallsList = [ + ...this.transferredCallsList.slice( + this.transferredCallsList.length >= DEFAULT_OPACITY ? 1 : 0, + DEFAULT_OPACITY, + ), + { [sessionId]: { transferredMiddleNumber } }, + ]; + } + + @proxify + async log({ call, ...options }: LogOptions) { + return super.log({ item: call, ...options }); + } + + async _ensureActive() { + const isActive = + !this._deps.tabManager || (await this._deps.tabManager.checkIsMain()); + return isActive; + } + + async _shouldLogNewCall(call: Call) { + const isActive = await this._ensureActive(); + return isActive && this.autoLog && (this.logOnRinging || !isRinging(call)); + } + + @proxify + async logCall({ call, contact, ...options }: LogCallOptions) { + const inbound = isInbound(call); + const fromEntity = (inbound && contact) || null; + const toEntity = (!inbound && contact) || null; + await this.log({ + ...options, + call: { + ...call, + duration: Object.prototype.hasOwnProperty.call(call, 'duration') + ? (call as CallLogRecord).duration + : Math.round((Date.now() - call.startTime) / 1000), + result: + (call as CallLogRecord).result || (call as Call).telephonyStatus, + }, + fromEntity, + toEntity, + }); + } + + async _autoLogCall({ + call, + fromEntity, + toEntity, + triggerType, + }: AutoLogCallOptions) { + if (!this.ready) { + return; + } + await this.log({ + call: { + ...call, + duration: Object.prototype.hasOwnProperty.call(call, 'duration') + ? (call as CallLogRecord).duration + : Math.round((Date.now() - call.startTime) / 1000), + result: + (call as CallLogRecord).result || (call as Call).telephonyStatus, + }, + fromEntity, + toEntity, + triggerType, + }); + } + + _activityMatcherCheck(sessionId: string) { + return ( + !this._deps.activityMatcher.dataMapping[sessionId] || + !this._deps.activityMatcher.dataMapping[sessionId].length + ); + } + + _customMatcherCheck(sessionId: string) { + if (!this._customMatcherHooks.length) { + return true; + } + return this._customMatcherHooks.some((hook) => hook(sessionId)); + } + + addCustomMatcherHook(hook: Hook) { + this._customMatcherHooks.push(hook); + } + + async _onNewCall(call: Call, triggerType: CallLoggerTriggerType) { + if (await this._shouldLogNewCall(call)) { + // RCINT-3857 check activity in case instance was reloaded when call is still active + await this._deps.activityMatcher.triggerMatch(); + if ( + this._activityMatcherCheck(call.sessionId) && + this._customMatcherCheck(call.sessionId) + ) { + // is completely new, need entity information + await this._deps.contactMatcher.triggerMatch(); + + const toNumberEntity = call.toNumberEntity || ''; + + const fromMatches = + (call.from && + call.from.phoneNumber && + this._deps.contactMatcher.dataMapping[call.from.phoneNumber]) || + []; + + const toMatches = + (call.to && + call.to.phoneNumber && + this._deps.contactMatcher.dataMapping[call.to.phoneNumber]) || + []; + + const fromEntity = + (fromMatches && fromMatches.length === 1 && fromMatches[0]) || null; + + let toEntity = null; + if (toMatches && toMatches.length === 1) { + /* eslint { "prefer-destructuring": 0 } */ + toEntity = toMatches[0]; + } else if (toMatches && toMatches.length > 1 && toNumberEntity !== '') { + toEntity = toMatches.find((match) => toNumberEntity === match.id); + } + + await this._autoLogCall({ + call, + fromEntity, + toEntity, + triggerType, + }); + } else { + // only update call information if call has been logged + await this._autoLogCall({ + call, + triggerType, + }); + } + } + } + + async _shouldLogUpdatedCall(call: HistoryCall | ActiveCall) { + const isActive = await this._ensureActive(); + if (isActive && (this.logOnRinging || !isRinging(call))) { + if (this.autoLog) return true; + await this._deps.activityMatcher.triggerMatch(); + const activityMatches = + this._deps.activityMatcher.dataMapping[call.sessionId] || []; + return activityMatches.length > 0; + } + return false; + } + + async _onCallUpdated( + call: UpdatedCall, + triggerType: T, + ) { + if (await this._shouldLogUpdatedCall(call)) { + await this._autoLogCall({ call, triggerType }); + } + } + + onInitOnce() { + watch( + this, + () => this._deps.callMonitor.calls, + (newCalls, oldCalls) => { + if (this.ready) { + oldCalls = oldCalls?.slice() || []; + removeDuplicateSelfCalls(newCalls).forEach((call) => { + const oldCallIndex = oldCalls.findIndex( + (item) => item.sessionId === call.sessionId, + ); + if (oldCallIndex === -1) { + this._onNewCall(call, callLoggerTriggerTypes.presenceUpdate); + } else { + const oldCall = oldCalls[oldCallIndex]; + oldCalls.splice(oldCallIndex, 1); + if (call.telephonyStatus !== oldCall.telephonyStatus) { + this._onCallUpdated( + { + ...call, + isTransferredCall: !!this.transferredCallsMap[ + call.sessionId + ], + transferredMiddleNumber: this.transferredCallsMap[ + call.sessionId + ] + ? this.transferredCallsMap[call.sessionId] + .transferredMiddleNumber + : null, + }, + callLoggerTriggerTypes.presenceUpdate, + ); + } + if ( + (call.from && call.from.phoneNumber) !== + (oldCall.from && oldCall.from.phoneNumber) + ) { + this._addTransferredCall( + call.sessionId, + oldCall.from?.phoneNumber, + ); + this._onCallUpdated( + { + ...call, + isTransferredCall: true, + transferredMiddleNumber: + oldCall.from && oldCall.from.phoneNumber, + phoneNumberUpdated: true, + }, + callLoggerTriggerTypes.presenceUpdate, + ); + } + } + }); + oldCalls.forEach((call) => { + this._onCallUpdated( + { + ...call, + isTransferredCall: !!this.transferredCallsMap[call.sessionId], + transferredMiddleNumber: this.transferredCallsMap[ + call.sessionId + ] + ? this.transferredCallsMap[call.sessionId] + .transferredMiddleNumber + : null, + }, + callLoggerTriggerTypes.presenceUpdate, + ); + }); + } + }, + ); + + watch( + this, + () => this._deps.callHistory.endedCalls, + (newCall, oldCalls) => { + if (this.ready) { + oldCalls = oldCalls?.slice() || []; + const currentSessions: Record = {}; + newCall.forEach((call) => { + currentSessions[call.sessionId] = true; + }); + oldCalls.forEach((call) => { + if (!currentSessions[call.sessionId]) { + // call log updated + const callInfo = this._deps.callHistory.calls.find( + (item) => item.sessionId === call.sessionId, + ); + if (callInfo) { + this._onCallUpdated( + { + ...callInfo, + isTransferredCall: !!this.transferredCallsMap[ + callInfo.sessionId + ], + transferredMiddleNumber: this.transferredCallsMap[ + call.sessionId + ] + ? this.transferredCallsMap[call.sessionId] + .transferredMiddleNumber + : null, + }, + callLoggerTriggerTypes.callLogSync, + ); + } + } + }); + } + }, + ); + } + + @proxify + async setAutoLog(autoLog: boolean) { + if (this.ready && autoLog !== this.autoLog) { + this._setAutoLog(autoLog); + } + } + + @proxify + async setLogOnRinging(logOnRinging: boolean) { + if (this.ready && logOnRinging !== this.logOnRinging) { + this._setLogOnRinging(logOnRinging); + } + } + + @computed((that) => [that.transferredCallsList]) + get transferredCallsMap() { + return reduce( + (mapping, matcher) => ({ ...mapping, ...matcher }), + {} as Record, + this.transferredCallsList, + ); + } +} diff --git a/packages/ringcentral-integration/modules/CallLoggerV2/callLoggerHelper.ts b/packages/ringcentral-integration/modules/CallLoggerV2/callLoggerHelper.ts new file mode 100644 index 0000000000..957933d6a2 --- /dev/null +++ b/packages/ringcentral-integration/modules/CallLoggerV2/callLoggerHelper.ts @@ -0,0 +1,6 @@ +/** + * Identity function for calls. + */ +export function callIdentityFunction(call: T) { + return call.sessionId; +} diff --git a/packages/ringcentral-integration/modules/CallLoggerV2/index.ts b/packages/ringcentral-integration/modules/CallLoggerV2/index.ts new file mode 100644 index 0000000000..edc27c8818 --- /dev/null +++ b/packages/ringcentral-integration/modules/CallLoggerV2/index.ts @@ -0,0 +1,3 @@ +export * from './CallLogger'; +export * from './CallLogger.interface'; +export * from './callLoggerHelper'; diff --git a/packages/ringcentral-integration/modules/CallV2/Call.ts b/packages/ringcentral-integration/modules/CallV2/Call.ts index 7696960aa6..012bb918a7 100644 --- a/packages/ringcentral-integration/modules/CallV2/Call.ts +++ b/packages/ringcentral-integration/modules/CallV2/Call.ts @@ -283,6 +283,15 @@ export class Call extends RcModuleV2 { message: callErrors.networkError, payload: error, }); + } else if ( + typeof error.message === 'string' && + error.message.includes('[InternationalCalls] is not available') + ) { + // ringout call may not have international permission, then first leg can't be create + // directly, customer will not be able to hear the voice prompt, so show a warning + this._deps.alert.danger({ + message: callErrors.noInternational, + }); } else if (error.message !== 'Refresh token has expired') { if ( !this._deps.availabilityMonitor || diff --git a/packages/ringcentral-integration/modules/ConferenceCallV2/ConferenceCall.interfaces.ts b/packages/ringcentral-integration/modules/ConferenceCallV2/ConferenceCall.interfaces.ts new file mode 100644 index 0000000000..26bed28071 --- /dev/null +++ b/packages/ringcentral-integration/modules/ConferenceCallV2/ConferenceCall.interfaces.ts @@ -0,0 +1,205 @@ +import { CallParty } from '@rc-ex/core/definitions'; +import { NormalizedCall } from '../../interfaces/Call.interface'; +import { Auth } from '../AuthV2'; +import { Alert } from '../AlertV2'; +import { Call } from '../CallV2'; +import { CallingSettings } from '../CallingSettingsV2'; +import { ConnectivityMonitor } from '../ConnectivityMonitorV2'; +import { Webphone } from '../WebphoneV2'; +import { RolesAndPermissions } from '../RolesAndPermissionsV2'; +import { ContactMatcher } from '../ContactMatcherV2'; +import AvailabilityMonitor from '../AvailabilityMonitor'; +import calleeTypes from '../../enums/calleeTypes'; + +interface ConferenceCallOptions { + pulling?: boolean; + capacity?: number; + timeout?: number; +} + +export interface Deps extends ConferenceCallOptions { + auth: Auth; + alert: Alert; + call: Call; + callingSettings: CallingSettings; + connectivityMonitor: ConnectivityMonitor; + client: any; + rolesAndPermissions: RolesAndPermissions; + webphone?: Webphone; + contactMatcher?: ContactMatcher; + availabilityMonitor?: AvailabilityMonitor; + conferenceCallOptions?: ConferenceCallOptions; +} + +export type SessionType = + | 'Call' + | 'RingOut' + | 'RingMe' + | 'Conference' + | 'GreetingsRecording' + | 'VerificationCall' + | 'Zoom' + | 'CallOut'; + +// https://developers.ringcentral.com/api-reference/Call-Control/createConferenceCallSession +export interface Conference { + creationTime: string; + id: string; + origin: { + type: SessionType; + }; + parties: Party[]; + voiceCallToken: string; +} + +export interface PartyState { + rcId: string; + avatarUrl: string; + partyName: string; + partyNumber: string; + calleeType: keyof typeof calleeTypes; + id: string; +} + +export interface ConferenceState { + conference: Conference; + sessionId: string; + profiles: PartyState[]; +} + +export interface ConferencesState { + [key: string]: ConferenceState; +} + +export interface MergingPair { + fromSessionId?: string; + toSessionId?: string; +} + +export type Party = AbstractParty; + +export type AbstractParty< + Role extends ConferenceRole, + Status extends CallStatus, + PartySessionType extends SessionType +> = CallParty & + (Role extends 'Host' + ? { + // Deprecated: Information on call owner + owner: { accountId: string; extensionId: string }; + to: { name: 'Conference'; phoneNumber: 'conference' }; + conferenceRole: Role; + } + : Role extends 'Participant' + ? { + conferenceRole: Role; + } + : never) & + (PartySessionType extends 'RingOut' + ? { + ringOutRole: RingRole; + } + : PartySessionType extends 'RingMe' + ? { + ringMeRole: RingRole; + } + : {}) & { + status: (Status extends 'Disconnected' + ? { + // Reason for call termination. For 'Disconnected' code only + reason?: PartyDisconnectReason; + } + : Status extends 'Gone' + ? { + // Peer session/party details. Valid in 'Gone' state of a call + peerId: Pick< + NormalizedCall, + 'sessionId' | 'telephonySessionId' | 'partyId' + >; + } + : {}) & { + code: Status; + description?: string; + }; + }; + +// internal types +type ConferenceRole = 'Host' | 'Participant'; + +export type CallDirection = 'Outbound' | 'Inbound'; + +export type CallStatus = + | 'Setup' + | 'Proceeding' + | 'Answered' + | 'Disconnected' + | 'Gone' + | 'Parked' + | 'Hold' + | 'VoiceMail' + | 'FaxReceive' + | 'VoiceMailScreening'; + +interface CallPartyInfo { + deviceId?: string; + extensionId?: string; + phoneNumber: string; + name?: string; +} + +export type PartyDisconnectReason = + | 'Pickup' + | 'Supervising' + | 'TakeOver' + | 'Timeout' + | 'BlindTransfer' + | 'RccTransfer' + | 'AttendedTransfer' + | 'CallerInputRedirect' + | 'CallFlip' + | 'ParkLocation' + | 'DtmfTransfer' + | 'AgentAnswered' + | 'AgentDropped' + | 'Rejected' + | 'Cancelled' + | 'InternalError' + | 'NoAnswer' + | 'TargetBusy' + | 'InvalidNumber' + | 'InternationalDisabled' + | 'DestinationBlocked' + | 'NotEnoughFunds' + | 'NoSuchUser' + | 'CallPark' + | 'CallRedirected' + | 'CallReplied' + | 'CallSwitch' + | 'CallFinished' + | 'CallDropped'; + +type RingRole = 'Initiator' | 'Target'; + +interface Recording { + id: string; + active: boolean; +} + +interface IBaseParty { + // Internal identifier of a party + id: string; + muted: boolean; + // If 'True' then the party is not connected to a session voice conference, 'False' means the party is connected to other parties in a session + standAlone: boolean; + // Call park information + park?: { + id: string; + }; + accountId: string; + extensionId: string; + recordings?: Recording[]; + from: CallPartyInfo; + to: CallPartyInfo; + direction: CallDirection; + attributes?: object; +} diff --git a/packages/ringcentral-integration/modules/ConferenceCallV2/ConferenceCall.ts b/packages/ringcentral-integration/modules/ConferenceCallV2/ConferenceCall.ts new file mode 100644 index 0000000000..8f2a8d893d --- /dev/null +++ b/packages/ringcentral-integration/modules/ConferenceCallV2/ConferenceCall.ts @@ -0,0 +1,1053 @@ +import { find, values, is, filter, map } from 'ramda'; +import { EventEmitter } from 'events'; +import { + action, + RcModuleV2, + state, + computed, + track, +} from '@ringcentral-integration/core'; +import { Module } from '../../lib/di'; +import { proxify } from '../../lib/proxy/proxify'; +import calleeTypes from '../../enums/calleeTypes'; +import callDirections from '../../enums/callDirections'; +import callingModes from '../CallingSettings/callingModes'; +import { permissionsMessages } from '../RolesAndPermissions/permissionsMessages'; +import { isConferenceSession, isRecording } from '../Webphone/webphoneHelper'; +import sessionStatusEnum from '../Webphone/sessionStatus'; +import { + DEFAULT_TIMEOUT, + DEFAULT_TTL, + MAXIMUM_CAPACITY, + ascendSortParties, + conferenceCallStatus, + partyStatusCode, + mergeParty, + mergeEvents, +} from './lib'; +import { + Deps, + ConferenceState, + ConferencesState, + Conference, + MergingPair, + PartyState, +} from './ConferenceCall.interfaces'; +import { conferenceCallErrors } from './conferenceCallErrors'; +import { + WebphoneSession, + NormalizedSession, +} from '../../interfaces/Webphone.interface'; +import { trackEvents } from '../Analytics'; + +@Module({ + name: 'ConferenceCall', + deps: [ + 'Auth', + 'Alert', + 'Call', + 'CallingSettings', + 'ConnectivityMonitor', + 'Client', + 'RolesAndPermissions', + { + dep: 'ContactMatcher', + optional: true, + }, + { + dep: 'Webphone', + optional: true, + }, + { + dep: 'AvailabilityMonitor', + optional: true, + }, + { + dep: 'ConferenceCallOptions', + optional: true, + }, + ], +}) +export class ConferenceCall extends RcModuleV2 { + private _eventEmitter = new EventEmitter(); + private _timers: { + [key: string]: number; + } = {}; + + private _fromSessionId: string; + private _ttl: number = DEFAULT_TTL; + private _timout: number = + this._deps.conferenceCallOptions?.timeout ?? DEFAULT_TIMEOUT; + private _capacity: number = + this._deps.conferenceCallOptions?.capacity ?? MAXIMUM_CAPACITY; + protected _pulling: boolean = + this._deps.conferenceCallOptions?.pulling ?? true; + private _lastCallInfo: { + calleeType: string; + extraNum: number; + phoneNumber?: string; + status: string; + lastCallContact?: any; + avatarUrl?: string; + name?: string; + }; + + @state + conferences: ConferencesState = {}; + + @state + conferenceCallStatus = conferenceCallStatus.idle; + + @state + mergingPair: MergingPair = {}; + + @state + currentConferenceId: string = null; + + @state + isMerging = false; + + constructor(deps: Deps) { + super({ deps }); + } + + @action + setIsMerging(state: boolean) { + this.isMerging = state; + } + + @action + setCurrentConferenceId(id: string) { + this.currentConferenceId = id; + } + + @action + setMergingPair(val: MergingPair) { + this.mergingPair = val; + } + + @action + setConferencesState(val: ConferencesState) { + this.conferences = val; + } + + @action + toggleConferenceCallStatus() { + if (this.conferenceCallStatus === conferenceCallStatus.idle) { + this.conferenceCallStatus = conferenceCallStatus.requesting as any; + return; + } + this.conferenceCallStatus = conferenceCallStatus.idle as any; + } + + @action + setConferenceCallStatus(val: conferenceCallStatus) { + this.conferenceCallStatus = val; + } + + @proxify + async updateAConference(conference: Conference, sessionId: string) { + this.setConferencesState({ + ...this.conferences, + [conference.id]: { + conference, + sessionId, + profiles: + (this.conferences[conference.id] && + this.conferences[conference.id].profiles) || + [], + } as ConferenceState, + }); + } + + bringInParty( + conference: Conference, + sessionId: string, + partyProfile: PartyState, + ) { + this.setConferencesState({ + ...this.conferences, + [conference.id]: { + conference, + sessionId, + profiles: [...this.conferences[conference.id].profiles, partyProfile], + }, + }); + } + + @action + removeConference(id: string) { + delete this.conferences[id]; + } + + isConferenceSession(sessionId: string) { + const { webphone } = this._deps; + // only can be used after webphone._onCallStartFunc + let res = !!this.findConferenceWithSession(sessionId); + + if (this.isMerging && !res) { + const session = find( + (session) => session.id === sessionId, + webphone.sessions, + ); + res = isConferenceSession(session); + } + + return res; + } + + findConferenceWithSession(sessionId: string) { + return find((c) => c.sessionId === sessionId, values(this.conferences)); + } + + @proxify + async updateConferenceStatus(id: string) { + this.setConferenceCallStatus(conferenceCallStatus.requesting); + const { client } = this._deps; + try { + const rawResponse = await client.service + .platform() + .get(`/restapi/v1.0/account/~/telephony/sessions/${id}`); + const response = await rawResponse.json(); + const storedConference = this.conferences[response.id]; + const conference = { ...storedConference.conference }; + conference.parties = + // if BE session hasn't been updated + conference.parties.length > response.parties.length + ? mergeParty(response.parties, conference.parties) + : response.parties; + const { sessionId } = storedConference; + this.updateAConference(conference, sessionId); + } finally { + this.setConferenceCallStatus(conferenceCallStatus.idle); + // eslint-disable-next-line no-unsafe-finally + return this.conferences[id]; + } + } + + @proxify + async terminateConference(id: string) { + if (this.conferenceCallStatus === conferenceCallStatus.requesting) { + return; + } + this.setConferenceCallStatus(conferenceCallStatus.requesting); + const { webphone, client, availabilityMonitor, alert } = this._deps; + const conferenceData = this.conferences[id]; + try { + if (!conferenceData) { + return; + } + if (webphone) { + webphone.hangup(conferenceData.sessionId); + } + await client.service + .platform() + .delete(`/restapi/v1.0/account/~/telephony/sessions/${id}`); + this.removeConference(id); + } catch (e) { + if ( + !availabilityMonitor || + !(await availabilityMonitor.checkIfHAError(e)) + ) { + alert.warning({ + message: conferenceCallErrors.terminateConferenceFailed, + }); + } + } finally { + this.setConferenceCallStatus(conferenceCallStatus.idle); + // eslint-disable-next-line no-unsafe-finally + return conferenceData; + } + } + + @proxify + async bringInToConference( + id: string, + webphoneSession: NormalizedSession, + propagate: boolean = false, + ) { + const { connectivityMonitor, alert, client } = this._deps; + const conferenceState = this.conferences[id]; + if ( + !conferenceState || + !this.ready || + !webphoneSession || + this.isOverload(id) || + !connectivityMonitor.connectivity + ) { + alert.danger({ + message: conferenceCallErrors.modeError, + ttl: 0, + }); + return null; + } + const { sessionId } = conferenceState; + let { conference } = conferenceState; + this.setConferenceCallStatus(conferenceCallStatus.requesting); + + try { + const partyProfile = this._getProfile(webphoneSession.id); + await client.service + .platform() + .post( + `/restapi/v1.0/account/~/telephony/sessions/${id}/parties/bring-in`, + webphoneSession.partyData, + ); + const newConference = await this.updateConferenceStatus(id); + conference = newConference.conference; + + if (partyProfile) { + const conferenceState = this.conferences[id]; + const newParties = ascendSortParties( + conferenceState.conference.parties, + ); + (partyProfile as PartyState).id = newParties[newParties.length - 1].id; + this.bringInParty(conference, sessionId, partyProfile as PartyState); + } + // else using BE push notification to get the new party data + return id; + } catch (e) { + if (!propagate) { + return; + } + throw e; + } finally { + this.setConferenceCallStatus(conferenceCallStatus.idle); + } + } + + @proxify + async removeFromConference(id: string, partyId: string) { + const { client, availabilityMonitor, alert } = this._deps; + this.setConferenceCallStatus(conferenceCallStatus.requesting); + try { + await client.service + .platform() + .delete( + `/restapi/v1.0/account/~/telephony/sessions/${id}/parties/${partyId}`, + ); + await this.updateConferenceStatus(id); + } catch (e) { + if ( + !availabilityMonitor || + !(await availabilityMonitor.checkIfHAError(e)) + ) { + alert.warning({ + message: conferenceCallErrors.removeFromConferenceFailed, + }); + } + } finally { + this.setConferenceCallStatus(conferenceCallStatus.idle); + // eslint-disable-next-line no-unsafe-finally + return this.conferences[id]; + } + } + + @proxify + async makeConference(propagate = false) { + const { connectivityMonitor, alert, callingSettings } = this._deps; + if (!this.ready || !connectivityMonitor.connectivity) { + alert.danger({ + message: conferenceCallErrors.modeError, + ttl: 0, + }); + + return null; + } + if (!this._checkPermission()) { + if (!propagate) { + alert.danger({ + message: permissionsMessages.insufficientPrivilege, + ttl: 0, + }); + } + + return null; + } + if (!(callingSettings.callingMode === callingModes.webphone)) { + if (!propagate) { + alert.danger({ + message: conferenceCallErrors.modeError, + ttl: 0, + }); + } + + return null; + } + const conference = await this._makeConference(propagate); + return conference; + } + + /** + * Merge calls to (or create) a conference. + * @param {webphone.sessions} webphoneSessions + * FIXME: dynamically construct this function during the construction + * to avoid `webphone` criterias to improve performance ahead of time + */ + @proxify + async mergeToConference(webphoneSessions: NormalizedSession[] = []) { + const { alert, webphone, availabilityMonitor } = this._deps; + + webphoneSessions = filter( + (session) => !this.isConferenceSession(session.id), + filter((session) => !!session, webphoneSessions), + ); + + if (!webphoneSessions.length) { + alert.warning({ + message: conferenceCallErrors.bringInFailed, + }); + return; + } + this.setIsMerging(true); + let sipInstances; + let conferenceId = null; + + if (webphone) { + /** + * Because the concurrency behaviour of the server, + * we cannot sure the merging process is over when + * the function's procedure has finshed. + */ + sipInstances = map( + (webphoneSession) => webphone._sessions.get(webphoneSession.id), + webphoneSessions, + ); + /** + * HACK: we need to preserve the merging session in prevent the glitch of + * the call control page. + */ + const sessionIds = map((x) => x.id, webphoneSessions); + webphone.setSessionCaching(sessionIds); + + const pSips = map((instance) => { + const p = new Promise((resolve) => { + instance.on('terminated', () => { + resolve(null); + }); + }); + return p; + }, sipInstances); + + await Promise.all([ + this._mergeToConference(webphoneSessions), + ...pSips, + ]).then( + () => { + this.setIsMerging(false); + this.setMergingPair({}); + const conferenceState = Object.values(this.conferences)[0]; + + this._eventEmitter.emit(mergeEvents.mergeSucceeded, conferenceState); + }, + (e) => { + console.error(e); + const conferenceState = Object.values(this.conferences)[0]; + + /** + * if create conference successfully but failed to bring-in, + * then terminate the conference. + */ + if (conferenceState && conferenceState.profiles.length < 1) { + this.terminateConference(conferenceState.conference.id); + } + alert.warning({ + message: conferenceCallErrors.bringInFailed, + }); + this.setIsMerging(false); + }, + ); + webphone.clearSessionCaching(); + } else { + try { + conferenceId = await this._mergeToConference(webphoneSessions); + + this.setIsMerging(false); + this.setMergingPair({}); + this._eventEmitter.emit(mergeEvents.mergeSucceeded); + } catch (e) { + const conferenceState = Object.values(this.conferences)[0]; + /** + * if create conference successfully but failed to bring-in, + * then terminate the conference. + */ + if ( + conferenceState && + conferenceState?.conference?.parties?.length < 1 + ) { + this.terminateConference(conferenceState.conference.id); + } + + if ( + !availabilityMonitor || + !(await availabilityMonitor.checkIfHAError(e)) + ) { + alert.warning({ + message: conferenceCallErrors.bringInFailed, + }); + } + } + + if (!sipInstances || conferenceId === null) { + this.setIsMerging(false); + } + } + } + + @proxify + async setMergeParty({ + fromSessionId, + toSessionId, + }: { + fromSessionId?: string; + toSessionId?: string; + }) { + if (fromSessionId) { + this.setMergingPair({ fromSessionId }); + return; + } + this.setMergingPair({ + ...this.mergingPair, + ...(toSessionId && { toSessionId }), + }); + } + + @proxify + async closeMergingPair() { + if (!this.mergingPair.fromSessionId) { + return; + } + this.setMergingPair({}); + } + + getOnlinePartyProfiles(id: string) { + const conferenceData = this.conferences[id]; + + if (!conferenceData) { + return null; + } + + return ascendSortParties(conferenceData.conference.parties) + .reduce((accum, party, idx) => { + if (party.status.code.toLowerCase() !== partyStatusCode.disconnected) { + // 0 position is the host + accum.push({ idx, party }); + } + return accum; + }, []) + .map(({ idx, party }) => ({ + ...party, + ...conferenceData.profiles[idx], + })) + .filter((i) => !!i); + } + + getOnlineParties(id: string) { + const conferenceData = this.conferences[id]; + if (!conferenceData) { + return null; + } + return filter( + (p) => p.status.code.toLowerCase() !== partyStatusCode.disconnected, + conferenceData.conference.parties, + ); + } + + countOnlineParties(id: string) { + const res = this.getOnlineParties(id); + return is(Array, res) ? res.length : null; + } + + isOverload(id: string) { + return this.countOnlineParties(id) >= this._capacity; + } + + @proxify + async startPollingConferenceStatus(id: string) { + if (this._timers[id] || !this._pulling) { + return; + } + + await this.updateConferenceStatus(id); + this._timers[id] = window.setTimeout(async () => { + await this.updateConferenceStatus(id); + this.stopPollingConferenceStatus(id); + if (this.conferences[id]) { + this.startPollingConferenceStatus(id); + } + }, this._ttl); + } + + stopPollingConferenceStatus(id: string) { + clearTimeout(this._timers[id]); + delete this._timers[id]; + } + + openPulling() { + this._pulling = true; + } + + closePulling() { + this._pulling = false; + } + + togglePulling() { + this._pulling = !this._pulling; + } + + setCapacity(capacity = MAXIMUM_CAPACITY) { + if (typeof capacity !== 'number') { + throw new Error('The capcity must be a number'); + } + this._capacity = capacity; + return capacity; + } + + setTimeout(timeout: number = DEFAULT_TIMEOUT) { + if (typeof timeout !== 'number') { + throw new Error('The timeout must be a number'); + } + this._timout = timeout; + return timeout; + } + + onMergeSuccess(func: (...args: any[]) => void, isOnce: boolean) { + if (isOnce) { + this._eventEmitter.once(mergeEvents.mergeSucceeded, func); + return; + } + this._eventEmitter.on(mergeEvents.mergeSucceeded, func); + } + + removeMergeSuccess(func: (...args: any[]) => void) { + this._eventEmitter.off(mergeEvents.mergeSucceeded, func); + } + + @proxify + async loadConference(conferenceId: string) { + this.setCurrentConferenceId(conferenceId); + } + + onReset() { + this.resetSuccess(); + } + + private _checkPermission() { + const { rolesAndPermissions, alert } = this._deps; + if (!rolesAndPermissions.hasConferenceCallPermission) { + alert.danger({ + message: permissionsMessages.insufficientPrivilege, + ttl: 0, + }); + return false; + } + return true; + } + + @proxify + private async _hookConference( + conference: Conference, + session: WebphoneSession, + ) { + ['accepted'].forEach((evt) => + session.on(evt as any, () => + this.startPollingConferenceStatus(conference.id), + ), + ); + ['terminated', 'failed', 'rejected'].forEach((evt) => + session.on(evt as any, () => { + this.setConferenceCallStatus(conferenceCallStatus.idle); + this.removeConference(conference.id); + this.stopPollingConferenceStatus(conference.id); + }), + ); + } + + @proxify + private async _mergeToConference(webphoneSessions: NormalizedSession[] = []) { + const { webphone } = this._deps; + const conferenceState = Object.values(this.conferences)[0]; + if (conferenceState) { + const conferenceId = conferenceState.conference.id; + this.stopPollingConferenceStatus(conferenceId); + // for the sake of participants ordering, we can't concurrently bring in the participants + for (const webphoneSession of webphoneSessions) { + await this.bringInToConference(conferenceId, webphoneSession, true); + } + if (!this.conferences[conferenceId].profiles.length) { + throw new Error( + 'bring-in operations failed, not all intended parties were brought in', + ); + } + this.startPollingConferenceStatus(conferenceId); + return conferenceId; + } + const { id } = await this.makeConference(true); + let conferenceAccepted = false; + await Promise.race([ + new Promise((resolve, reject) => { + const sipSession = webphone._sessions.get( + this.conferences[id].sessionId, + ); + sipSession.on('accepted', () => { + conferenceAccepted = true; + resolve(null); + }); + sipSession.on('cancel', () => reject(new Error('conferencing cancel'))); + sipSession.on('failed', () => reject(new Error('conferencing failed'))); + sipSession.on('rejected', () => + reject(new Error('conferencing rejected')), + ); + sipSession.on('terminated', () => + reject(new Error('conferencing terminated')), + ); + }), + new Promise((resolve, reject) => { + setTimeout( + () => + conferenceAccepted + ? resolve(null) + : reject(new Error('conferencing timeout')), + this._timout, + ); + }), + ]); + await this._mergeToConference(webphoneSessions); + return id; + } + + @proxify + private async _makeConference(propagate = false) { + const { client, call, availabilityMonitor, alert } = this._deps; + this.setConferenceCallStatus(conferenceCallStatus.requesting); + try { + // TODO: replace with SDK function chaining calls + const rawResponse = await client.service + .platform() + .post('/restapi/v1.0/account/~/telephony/conference', {}); + const response = await rawResponse.json(); + const conference = response.session as Conference; + const phoneNumber = conference.voiceCallToken; + // whether to mutate the session to mark the conference? + const session = await call.call({ + phoneNumber, + isConference: true, + } as any); + if ( + typeof session === 'object' && + Object.prototype.toString.call(session.on).toLowerCase() === + '[object function]' + ) { + this._hookConference(conference, session); + this.updateAConference(conference, session.id); + } + return conference; + } catch (e) { + console.error(e); + if ( + !propagate || + !availabilityMonitor || + !(await availabilityMonitor.checkIfHAError(e)) + ) { + alert.warning({ + message: conferenceCallErrors.makeConferenceFailed, + }); + + return null; + } + // need to propagate to out side try...catch block + throw e; + } finally { + this.setConferenceCallStatus(conferenceCallStatus.idle); + } + } + + // get profile the a webphone session + private _getProfile(sessionId: string) { + const { webphone, contactMatcher } = this._deps; + const session = find( + (session) => session.id === sessionId, + webphone.sessions, + ); + + let rcId; + let avatarUrl; + let calleeType = calleeTypes.unknown; + let partyName = + session.direction === callDirections.outbound + ? session.toUserName + : session.fromUserName; + const partyNumber = + session.direction === callDirections.outbound ? session.to : session.from; + + let matchedContact = session.contactMatch; + if (!matchedContact && contactMatcher) { + const nameMatches = contactMatcher.dataMapping[partyNumber]; + if (nameMatches && nameMatches.length) { + matchedContact = nameMatches[0]; + } + } + + if (matchedContact) { + rcId = matchedContact.id; + avatarUrl = (matchedContact as any).profileImageUrl; + partyName = (matchedContact as any).name; + calleeType = calleeTypes.contacts; + } + + return { + rcId, + avatarUrl, + partyName, + partyNumber, + calleeType, + } as Omit; + } + + @proxify + async parseMergingSessions({ + sessionId, + sessionIdToMergeWith, + }: { + sessionId: string; + sessionIdToMergeWith: string; + }) { + const { webphone } = this._deps; + const session = find((x) => x.id === sessionId, webphone.sessions); + + const sessionToMergeWith = find( + (x) => x.id === (sessionIdToMergeWith || this.mergingPair.fromSessionId), + webphone.sessions, + ); + + const webphoneSessions = sessionToMergeWith + ? [sessionToMergeWith, session] + : [session]; + + for (const session of webphoneSessions) { + if (!this.validateCallRecording(session)) { + return null; + } + } + + const conferenceState = Object.values(this.conferences)[0]; + if (conferenceState) { + const conferenceSession = find( + (x) => x.id === conferenceState.sessionId, + webphone.sessions, + ); + if (!this.validateCallRecording(conferenceSession)) { + return null; + } + } + + return { + session, + sessionToMergeWith, + }; + } + + @proxify + async mergeSessions({ + session, + sessionToMergeWith, + }: { + session: NormalizedSession; + sessionToMergeWith: NormalizedSession; + }) { + this.setMergeParty({ + toSessionId: session.id, + }); + const { webphone } = this._deps; + const webphoneSessions = sessionToMergeWith + ? [sessionToMergeWith, session] + : [session]; + await this.mergeToConference(webphoneSessions); + + const conferenceData = Object.values(this.conferences)[0]; + if (!conferenceData) { + await webphone.resume(session.id); + return null; + } + const currentConferenceSession = find( + (x) => x.id === conferenceData.sessionId, + webphone.sessions, + ); + const isCurrentConferenceOnHold = currentConferenceSession.isOnHold; + + if (isCurrentConferenceOnHold) { + webphone.resume(conferenceData.sessionId); + } + + return conferenceData; + } + + validateCallRecording(session: NormalizedSession) { + if (isRecording(session)) { + this._deps.alert.warning({ + message: conferenceCallErrors.callIsRecording, + }); + return false; + } + return true; + } + + @action + resetSuccess() { + this.setIsMerging(false); + this.setMergingPair({}); + this.setCurrentConferenceId(null); + this.conferenceCallStatus = conferenceCallStatus.idle as any; + this.conferences = {}; + } + + /* + * User action track dispatchs + * */ + @track(trackEvents.clickHangupParticipantList) + participantListClickHangupTrack() {} + + @track(trackEvents.cancelRemoveRemoveParticipantsModal) + removeParticipantClickCancelTrack() {} + + @track(trackEvents.clickRemoveRemoveParticipantsModal) + removeParticipantClickRemoveTrack() {} + + _shouldInit() { + const { auth } = this._deps; + return auth.loggedIn && super._shouldInit(); + } + + _shouldReset() { + const { auth } = this._deps; + + return super._shouldReset() || (this.ready && !auth.loggedIn); + } + + @computed((that: ConferenceCall) => [ + that._deps.webphone.sessions, + that.mergingPair.fromSessionId, + that.partyProfiles, + ]) + get lastCallInfo() { + const { sessions } = this._deps.webphone; + const { + partyProfiles, + mergingPair: { fromSessionId }, + } = this; + if (!fromSessionId) { + this._lastCallInfo = null; + return this._lastCallInfo; + } + + let sessionName; + let sessionNumber; + let sessionStatus; + let matchedContact; + const fromSession = sessions.find( + (session) => session.id === fromSessionId, + ); + if (fromSession) { + sessionName = + fromSession.direction === callDirections.outbound + ? fromSession.toUserName + : fromSession.fromUserName; + sessionNumber = + fromSession.direction === callDirections.outbound + ? fromSession.to + : fromSession.from; + sessionStatus = fromSession.callStatus; + matchedContact = fromSession.contactMatch; + if (!matchedContact && this._deps.contactMatcher) { + const nameMatches = this._deps.contactMatcher.dataMapping[ + sessionNumber + ]; + if (nameMatches && nameMatches.length) { + matchedContact = nameMatches[0]; + } + } + } + + let lastCalleeType; + if (fromSession) { + if (matchedContact) { + lastCalleeType = calleeTypes.contacts; + } else if (this.isConferenceSession(fromSession.id)) { + lastCalleeType = calleeTypes.conference; + } else { + lastCalleeType = calleeTypes.unknown; + } + } else if ( + this._fromSessionId === fromSessionId && + this._lastCallInfo && + this._lastCallInfo.calleeType + ) { + this._lastCallInfo = { + ...this._lastCallInfo, + status: sessionStatusEnum.finished, + }; + return this._lastCallInfo; + } else { + return { + calleeType: calleeTypes.unknown, + }; + } + + let partiesAvatarUrls = null; + if (lastCalleeType === calleeTypes.conference) { + partiesAvatarUrls = (partyProfiles || []).map( + (profile) => profile.avatarUrl, + ); + } + switch (lastCalleeType) { + case calleeTypes.conference: + this._lastCallInfo = { + calleeType: calleeTypes.conference, + avatarUrl: partiesAvatarUrls[0], + extraNum: partiesAvatarUrls.length - 1, + name: null, + phoneNumber: null, + status: sessionStatus, + lastCallContact: null, + }; + break; + case calleeTypes.contacts: + this._lastCallInfo = { + calleeType: calleeTypes.contacts, + avatarUrl: (matchedContact as any).profileImageUrl, + name: (matchedContact as any).name, + status: sessionStatus, + phoneNumber: sessionNumber, + extraNum: 0, + lastCallContact: matchedContact, + }; + break; + default: + this._lastCallInfo = { + calleeType: calleeTypes.unknown, + avatarUrl: null, + name: sessionName, + status: sessionStatus, + phoneNumber: sessionNumber, + extraNum: 0, + lastCallContact: null, + }; + } + + this._fromSessionId = fromSessionId; + return this._lastCallInfo; + } + + @computed((that: ConferenceCall) => [ + that.currentConferenceId, + that.conferences, + ]) + get partyProfiles() { + const { currentConferenceId, conferences } = this; + const conferenceData = conferences && conferences[currentConferenceId]; + if (!conferenceData) { + return []; + } + return this.getOnlinePartyProfiles(currentConferenceId); + } +} diff --git a/packages/ringcentral-integration/modules/ConferenceCallV2/conferenceCallErrors.js b/packages/ringcentral-integration/modules/ConferenceCallV2/conferenceCallErrors.js new file mode 100644 index 0000000000..e2d4cd9ba8 --- /dev/null +++ b/packages/ringcentral-integration/modules/ConferenceCallV2/conferenceCallErrors.js @@ -0,0 +1,20 @@ +import { ObjectMap } from '@ringcentral-integration/core/lib/ObjectMap'; + +export const conferenceCallErrors = ObjectMap.prefixKeys( + [ + 'internalServerError', + 'conferenceForbidden', + 'conferenceBadRequest', + 'conferenceNotFound', + 'conferenceConflict', + 'modeError', + 'makeConferenceFailed', + 'bringInFailed', + 'removeFromConferenceFailed', + 'terminateConferenceFailed', + 'callIsRecording', + ], + 'conferenceCall', +); + +export default conferenceCallErrors; diff --git a/packages/ringcentral-integration/modules/ConferenceCallV2/index.ts b/packages/ringcentral-integration/modules/ConferenceCallV2/index.ts new file mode 100644 index 0000000000..667bb79c96 --- /dev/null +++ b/packages/ringcentral-integration/modules/ConferenceCallV2/index.ts @@ -0,0 +1,2 @@ +export * from './ConferenceCall'; +export * from './ConferenceCall.interfaces'; diff --git a/packages/ringcentral-integration/modules/ConferenceCallV2/lib/constants.ts b/packages/ringcentral-integration/modules/ConferenceCallV2/lib/constants.ts new file mode 100644 index 0000000000..0dcca9a12b --- /dev/null +++ b/packages/ringcentral-integration/modules/ConferenceCallV2/lib/constants.ts @@ -0,0 +1,25 @@ +import { ObjectMap } from '@ringcentral-integration/core/lib/ObjectMap'; + +export const DEFAULT_TIMEOUT = 30000; // time out for conferencing session being accepted. +export const DEFAULT_TTL = 5000; // timer to update the conference information +export const MAXIMUM_CAPACITY = 10; +export const conferenceRole = ObjectMap.fromKeys(['host', 'participant']); +export const enum conferenceCallStatus { + idle = 'idle', + requesting = 'requesting', +} +export const partyStatusCode = ObjectMap.fromKeys( + [ + 'Setup', + 'Proceeding', + 'Answered', + 'Disconnected', + 'Gone', + 'Parked', + 'Hold', + 'VoiceMail', + 'FaxReceive', + 'VoiceMailScreening', + ].map((i) => i.toLowerCase()), +); +export const mergeEvents = ObjectMap.fromKeys(['mergeSucceeded']); diff --git a/packages/ringcentral-integration/modules/ConferenceCallV2/lib/helpers.ts b/packages/ringcentral-integration/modules/ConferenceCallV2/lib/helpers.ts new file mode 100644 index 0000000000..324ee0eed6 --- /dev/null +++ b/packages/ringcentral-integration/modules/ConferenceCallV2/lib/helpers.ts @@ -0,0 +1,29 @@ +import { sort, filter, map, find } from 'ramda'; +import { conferenceRole } from './constants'; +import { Party } from '../ConferenceCall.interfaces'; + +export function ascendSortParties(parties: Party[]): Party[] { + return sort( + (last: Party, next: Party) => + +last.id.split('-')[1] - +next.id.split('-')[1], + filter( + (party) => party.conferenceRole.toLowerCase() !== conferenceRole.host, + parties, + ), + ); +} + +export function mergeParty(newParties: Party[], oldParties: Party[]) { + return map((oldParty) => { + const newParty = find((newParty) => { + if (newParty.id === oldParty.id) { + return true; + } + return false; + }, newParties); + if (newParty) { + return newParty; + } + return oldParty; + }, oldParties); +} diff --git a/packages/ringcentral-integration/modules/ConferenceCallV2/lib/index.ts b/packages/ringcentral-integration/modules/ConferenceCallV2/lib/index.ts new file mode 100644 index 0000000000..5a9087d1d2 --- /dev/null +++ b/packages/ringcentral-integration/modules/ConferenceCallV2/lib/index.ts @@ -0,0 +1,2 @@ +export * from './constants'; +export * from './helpers'; diff --git a/packages/ringcentral-integration/modules/ConversationLogger/index.js b/packages/ringcentral-integration/modules/ConversationLogger/index.js index e90d86e466..5a1fd14c00 100644 --- a/packages/ringcentral-integration/modules/ConversationLogger/index.js +++ b/packages/ringcentral-integration/modules/ConversationLogger/index.js @@ -123,7 +123,6 @@ export default class ConversationLogger extends LoggerBase { readyCheckFn: () => this._messageStore.ready && this._extensionInfo.ready, }); - this._lastProcessedConversationLogMap = null; this._autoLogQueue = []; this._autoLogPromise = null; } @@ -262,8 +261,7 @@ export default class ConversationLogger extends LoggerBase { conversation.self && (conversation.self.phoneNumber || conversation.self.extensionNumber); const selfMatches = - (selfNumber && this._contactMatcher.dataMapping[conversation.self]) || - []; + (selfNumber && this._contactMatcher.dataMapping[selfNumber]) || []; const correspondentMatches = this._getCorrespondentMatches(conversation); const selfEntity = @@ -367,7 +365,7 @@ export default class ConversationLogger extends LoggerBase { .map((date) => this.conversationLogMap[conversationId][date]) .sort(sortByDate) .map((conversation, idx) => { - const queueIndex = this._autoLogQueue.find( + const queueIndex = this._autoLogQueue.findIndex( (item) => item.conversationLogId === conversation.conversationLogId, ); diff --git a/packages/ringcentral-integration/modules/ConversationLoggerV2/ConversationLogger.interface.ts b/packages/ringcentral-integration/modules/ConversationLoggerV2/ConversationLogger.interface.ts new file mode 100644 index 0000000000..db4b6b2bfb --- /dev/null +++ b/packages/ringcentral-integration/modules/ConversationLoggerV2/ConversationLogger.interface.ts @@ -0,0 +1,59 @@ +import { Auth } from '../AuthV2'; +import { Storage } from '../StorageV2'; +import { ContactMatcher } from '../ContactMatcherV2'; +import { ConversationMatcher } from '../ConversationMatcherV2'; +import { DateTimeFormat, FormatDateTimeOptions } from '../DateTimeFormatV2'; +import { LogOptions as BaseLogOptions } from '../../lib/LoggerBaseV2'; +import { ExtensionInfo } from '../ExtensionInfoV2'; +import { MessageStore } from '../MessageStoreV2'; +import { RolesAndPermissions } from '../RolesAndPermissionsV2'; +import { TabManager } from '../TabManagerV2'; +import { Message } from '../../interfaces/MessageStore.model'; +import { Entity } from '../../interfaces/Entity.interface'; +import { Correspondent } from '../../lib/messageHelper'; + +export interface ConversationLoggerOptions { + isLoggedContact?: ( + conversation: ConversationLogItem, + lastActivity: Entity, + item: Entity, + ) => boolean; + isAutoUpdate?: boolean; + formatDateTime?: (options: Partial) => string; + accordWithLogRequirement: (conversation: ConversationLogItem) => boolean; + logFunction: (options: BaseLogOptions) => Promise; + readyCheckFunction: () => boolean; +} + +export interface Deps { + auth: Auth; + storage: Storage; + contactMatcher: ContactMatcher; + conversationMatcher: ConversationMatcher; + dateTimeFormat: DateTimeFormat; + extensionInfo: ExtensionInfo; + messageStore: MessageStore; + rolesAndPermissions: RolesAndPermissions; + tabManager?: TabManager; + conversationLoggerOptions: ConversationLoggerOptions; +} + +export interface ConversationLogItem { + conversationLogId: string; + conversationId: string; + creationTime: number; + date: string; + type: Message['type']; + messages: Message[]; + conversationLogMatches: Entity[]; + self?: Correspondent; + // self?: { + // extensionNumber?: string; + // }; + correspondents?: Correspondent[]; +} + +export type ConversationLogMap = Record< + string, + Record +>; diff --git a/packages/ringcentral-integration/modules/ConversationLoggerV2/ConversationLogger.ts b/packages/ringcentral-integration/modules/ConversationLoggerV2/ConversationLogger.ts new file mode 100644 index 0000000000..346bdbedc7 --- /dev/null +++ b/packages/ringcentral-integration/modules/ConversationLoggerV2/ConversationLogger.ts @@ -0,0 +1,479 @@ +import { + state, + storage, + action, + computed, +} from '@ringcentral-integration/core'; +import { Module } from '../../lib/di'; +import { LoggerBase } from '../../lib/LoggerBaseV2'; +import { messageTypes } from '../../enums/messageTypes'; +import { + Correspondent, + getNumbersFromMessage, + sortByDate, +} from '../../lib/messageHelper'; +import sleep from '../../lib/sleep'; +import { proxify } from '../../lib/proxy/proxify'; +import { + getLogId, + conversationLogIdentityFunction, +} from './conversationLoggerHelper'; +import { + ConversationLogItem, + ConversationLogMap, + Deps, +} from './ConversationLogger.interface'; +import { Message } from '../../interfaces/MessageStore.model'; +import { Entity } from '../../interfaces/Entity.interface'; + +@Module({ + name: 'ConversationLogger', + deps: [ + 'Auth', + 'Storage', + 'ContactMatcher', + 'ConversationMatcher', + 'DateTimeFormat', + 'ExtensionInfo', + 'MessageStore', + 'RolesAndPermissions', + 'ConversationLoggerOptions', + { dep: 'TabManager', optional: true }, + ], +}) +export class ConversationLogger extends LoggerBase { + _logFunction = this._deps.conversationLoggerOptions.logFunction; + + _readyCheckFunction = this._deps.conversationLoggerOptions.readyCheckFunction; + + protected _isLoggedContact = + this._deps.conversationLoggerOptions.isLoggedContact ?? (() => false); + + protected _formatDateTime = + this._deps.conversationLoggerOptions.formatDateTime ?? + ((...args) => this._deps.dateTimeFormat.formatDateTime(...args)); + + protected _isAutoUpdate = + this._deps.conversationLoggerOptions.isAutoUpdate ?? true; + + protected _accordWithLogRequirement = this._deps.conversationLoggerOptions + .accordWithLogRequirement; + + protected _identityFunction = conversationLogIdentityFunction; + + protected _autoLogQueue: ConversationLogItem[] = []; + + protected _autoLogPromise: Promise = null; + + protected _lastProcessedConversations: ConversationLogMap = null; + + protected _lastAutoLog: boolean = null; + + constructor(deps: Deps) { + super(deps, { + enableCache: true, + storageKey: 'ConversationLogger', + }); + this._deps.messageStore.onMessageUpdated(() => { + this._processConversationLogMap(); + }); + this._deps.contactMatcher.addQuerySource({ + getQueriesFn: () => this.uniqueNumbers, + readyCheckFn: () => + this._deps.messageStore.ready && this._deps.extensionInfo.ready, + }); + this._deps.conversationMatcher.addQuerySource({ + getQueriesFn: () => this.conversationLogIds, + readyCheckFn: () => + this._deps.messageStore.ready && this._deps.extensionInfo.ready, + }); + } + + _shouldInit() { + return !!(super._shouldInit() && this._readyCheckFunction()); + } + + _shouldReset() { + return !!( + super._shouldReset() || + (this.ready && !this._readyCheckFunction()) + ); + } + + @storage + @state + autoLog = false; + + @action + _setAutoLog(autoLog: boolean) { + this.autoLog = autoLog; + } + + _onReset() { + this._lastProcessedConversations = null; + this._lastAutoLog = null; + this._autoLogPromise = null; + this._autoLogQueue = []; + } + + async _processQueue() { + const { ownerId } = this._deps.auth; + await sleep(300); + if (ownerId !== this._deps.auth.ownerId) return; + await Promise.all( + this._autoLogQueue + .splice(0, 10) + .map((conversation) => this._processConversationLog({ conversation })), + ); + if (ownerId === this._deps.auth.ownerId && this._autoLogQueue.length > 0) { + this._autoLogPromise = this._processQueue(); + } else { + this._autoLogPromise = null; + } + } + + _queueAutoLogConversation({ + conversation, + }: { + conversation: ConversationLogItem; + }) { + this._autoLogQueue.push(conversation); + if (!this._autoLogPromise) { + this._autoLogPromise = this._processQueue(); + } + } + + _getCorrespondentMatches(conversation: ConversationLogItem) { + return ( + (conversation.correspondents && + conversation.correspondents.reduce((result, contact) => { + const number = contact.phoneNumber || contact.extensionNumber; + return number && this._deps.contactMatcher.dataMapping[number] + ? result.concat(this._deps.contactMatcher.dataMapping[number]) + : result; + }, [] as Entity[])) || + [] + ); + } + + getLastMatchedCorrespondentEntity(conversation: ConversationLogItem) { + const conversationLog = this.conversationLogMap[ + conversation.conversationId + ]; + if (!conversationLog) { + return null; + } + const lastRecord = Object.keys(conversationLog) + .map((date) => this.conversationLogMap[conversation.conversationId][date]) + .sort(sortByDate) + .find((item) => item.conversationLogMatches.length > 0); + if ( + lastRecord && + this._deps.conversationMatcher.dataMapping[ + lastRecord.conversationLogId + ] && + this._deps.conversationMatcher.dataMapping[lastRecord.conversationLogId] + .length + ) { + const lastActivity = this._deps.conversationMatcher.dataMapping[ + lastRecord.conversationLogId + ][0]; + const correspondentMatches = this._getCorrespondentMatches(lastRecord); + return correspondentMatches.find((item) => + this._isLoggedContact(conversation, lastActivity, item), + ); + } + return null; + } + + async _processConversationLog({ + conversation, + }: { + conversation: ConversationLogItem; + }) { + // await this._deps.conversationMatcher.triggerMatch(); + await this._deps.conversationMatcher.match({ + queries: [conversation.conversationLogId], + }); + if ( + this._isAutoUpdate && + this._deps.conversationMatcher.dataMapping[ + conversation.conversationLogId + ] && + this._deps.conversationMatcher.dataMapping[conversation.conversationLogId] + .length + ) { + // update conversation + await this._autoLogConversation({ + conversation, + }); + } else if (this.autoLog && conversation.type === messageTypes.sms) { + // new entry + const numbers: string[] = []; + const numberMap: Record = {}; + /* eslint { "no-inner-declarations": 0 } */ + function addIfNotExist(contact: Correspondent) { + const number = contact.phoneNumber || contact.extensionNumber; + if (number && !numberMap[number]) { + numbers.push(number); + numberMap[number] = true; + } + } + addIfNotExist(conversation.self); + conversation.correspondents.forEach(addIfNotExist); + await this._deps.contactMatcher.match({ queries: numbers }); + const selfNumber = + conversation.self && + (conversation.self.phoneNumber || conversation.self.extensionNumber); + const selfMatches = + (selfNumber && this._deps.contactMatcher.dataMapping[selfNumber]) || []; + const correspondentMatches = this._getCorrespondentMatches(conversation); + + const selfEntity = + (selfMatches && selfMatches.length === 1 && selfMatches[0]) || null; + + let correspondentEntity = this.getLastMatchedCorrespondentEntity( + conversation, + ); + + correspondentEntity = + correspondentEntity || + (correspondentMatches && + correspondentMatches.length === 1 && + correspondentMatches[0]) || + null; + await this._autoLogConversation({ + conversation, + selfEntity, + correspondentEntity, + }); + } + } + + accordWithProcessLogRequirement(conversationLogItem: ConversationLogItem) { + return !!this._accordWithLogRequirement?.(conversationLogItem); + } + + _processConversationLogMap() { + if (this.ready && this._lastAutoLog !== this.autoLog) { + this._lastAutoLog = this.autoLog; + if (this.autoLog) { + // force conversation log checking when switch auto log to on + this._lastProcessedConversations = null; + } + } + if ( + this.ready && + this._lastProcessedConversations !== this.conversationLogMap + ) { + this._deps.conversationMatcher.triggerMatch(); + this._deps.contactMatcher.triggerMatch(); + const oldMap = this._lastProcessedConversations || {}; + this._lastProcessedConversations = this.conversationLogMap; + if (!this._deps.tabManager || this._deps.tabManager.active) { + Object.keys(this._lastProcessedConversations).forEach( + (conversationId) => { + Object.keys( + this._lastProcessedConversations[conversationId], + ).forEach((date) => { + const conversation = this._lastProcessedConversations[ + conversationId + ][date]; + if ( + !oldMap[conversationId] || + !oldMap[conversationId][date] || + conversation.messages[0].id !== + oldMap[conversationId][date].messages[0].id + ) { + if (this.accordWithProcessLogRequirement(conversation)) { + this._queueAutoLogConversation({ + conversation, + }); + } + } + }); + }, + ); + } + } + } + + async _autoLogConversation({ + conversation, + selfEntity, + correspondentEntity, + }: { + conversation: ConversationLogItem; + selfEntity?: Entity; + correspondentEntity?: Entity; + }) { + await this.log({ + conversation, + selfEntity, + correspondentEntity, + }); + } + + @proxify + async log({ + conversation, + ...options + }: { + conversation: ConversationLogItem; + } & T) { + super.log({ item: conversation, ...options }); + } + + @proxify + async logConversation({ + conversationId, + correspondentEntity, + redirect, + ...options + }: { + conversationId: string; + correspondentEntity: Entity; + redirect: boolean; + } & T) { + if (this.conversationLogMap[conversationId]) { + await Promise.all( + Object.keys(this.conversationLogMap[conversationId]) + .map((date) => this.conversationLogMap[conversationId][date]) + .sort(sortByDate) + .map((conversation, idx) => { + const queueIndex = this._autoLogQueue.findIndex( + (item) => + item.conversationLogId === conversation.conversationLogId, + ); + if (queueIndex > -1) { + this._autoLogQueue.splice(queueIndex, 1); + } + return this.log({ + ...options, + conversation, + correspondentEntity, + redirect: redirect && idx === 0, // only direct on the first item + }); + }), + ); + } + } + + get available() { + const { + SMSReceiving, + PagerReceiving, + } = this._deps.rolesAndPermissions.serviceFeatures; + return !!( + (SMSReceiving && SMSReceiving.enabled) || + (PagerReceiving && PagerReceiving.enabled) + ); + } + + @proxify + async setAutoLog(autoLog: boolean) { + if (this.ready && autoLog !== this.autoLog) { + this._setAutoLog(autoLog); + } + } + + @computed((that) => [ + that._deps.messageStore.conversationStore, + that._deps.extensionInfo.extensionNumber, + that._deps.conversationMatcher.dataMapping, + ]) + get conversationLogMap() { + const { conversationStore } = this._deps.messageStore; + const { extensionNumber } = this._deps.extensionInfo; + const conversationLogMapping = + this._deps.conversationMatcher.dataMapping ?? {}; + const messages = Object.values(conversationStore).reduce( + (allMessages, messages) => [...allMessages, ...messages], + [], + ); + const mapping: ConversationLogMap = {}; + messages + .slice() + .sort(sortByDate) + .forEach((message) => { + const { conversationId } = message; + const date = this._formatDateTime({ + type: 'date', + utcTimestamp: message.creationTime, + }); + if (!mapping[conversationId]) { + mapping[conversationId] = {}; + } + if (!mapping[conversationId][date]) { + const conversationLogId = getLogId({ conversationId, date }); + mapping[conversationId][date] = { + conversationLogId, + conversationId, + creationTime: message.creationTime, // for sorting + date, + type: message.type, + messages: [], + conversationLogMatches: + conversationLogMapping[conversationLogId] || [], + // The reason for passing extensionNumber here is to filter the correspondence in the group conversation(type paper, and Only it has extensionNumber) that contains its own information. + ...getNumbersFromMessage({ extensionNumber, message }), + }; + } + mapping[conversationId][date].messages.push(message); + }); + return mapping; + } + + @computed((that) => [that.conversationLogMap]) + get conversationLogIds() { + const logIds: string[] = []; + Object.keys(this.conversationLogMap).forEach((conversationId) => { + Object.keys(this.conversationLogMap[conversationId]).forEach((date) => { + logIds.push( + this.conversationLogMap[conversationId][date].conversationLogId, + ); + }); + }); + return logIds; + } + + @computed((that) => [that.conversationLogMap]) + get uniqueNumbers() { + const output: string[] = []; + const numberMap: Record = {}; + function addIfNotExist(contact: Correspondent = {}) { + const number = contact.phoneNumber || contact.extensionNumber; + if (number && !numberMap[number]) { + output.push(number); + numberMap[number] = true; + } + } + Object.keys(this.conversationLogMap).forEach((conversationId) => { + Object.keys(this.conversationLogMap[conversationId]).forEach((date) => { + const conversation = this.conversationLogMap[conversationId][date]; + addIfNotExist(conversation.self); + conversation.correspondents.forEach(addIfNotExist); + }); + }); + return output; + } + + getConversationLogId(message: Message) { + if (!message) { + return; + } + const { conversationId } = message; + const date = this._formatDateTime({ + type: 'date', + utcTimestamp: message.creationTime, + }); + return getLogId({ + conversationId, + date, + }); + } + + get dataMapping() { + return this._deps.conversationMatcher.dataMapping; + } +} diff --git a/packages/ringcentral-integration/modules/ConversationLoggerV2/conversationLoggerHelper.ts b/packages/ringcentral-integration/modules/ConversationLoggerV2/conversationLoggerHelper.ts new file mode 100644 index 0000000000..ca8465d505 --- /dev/null +++ b/packages/ringcentral-integration/modules/ConversationLoggerV2/conversationLoggerHelper.ts @@ -0,0 +1,17 @@ +import { ConversationLogItem } from './ConversationLogger.interface'; + +export function getLogId({ + conversationId, + date, +}: { + conversationId: string; + date: string; +}) { + return `${conversationId}/${date}`; +} + +export function conversationLogIdentityFunction( + conversation: ConversationLogItem, +) { + return conversation.conversationLogId; +} diff --git a/packages/ringcentral-integration/modules/ConversationLoggerV2/index.ts b/packages/ringcentral-integration/modules/ConversationLoggerV2/index.ts new file mode 100644 index 0000000000..1d76789cbf --- /dev/null +++ b/packages/ringcentral-integration/modules/ConversationLoggerV2/index.ts @@ -0,0 +1,3 @@ +export * from './ConversationLogger'; +export * from './ConversationLogger.interface'; +export * from './conversationLoggerHelper'; diff --git a/packages/ringcentral-integration/modules/ConversationsV2/Conversations.interface.ts b/packages/ringcentral-integration/modules/ConversationsV2/Conversations.interface.ts index 821b7bd41e..b0715cdba1 100644 --- a/packages/ringcentral-integration/modules/ConversationsV2/Conversations.interface.ts +++ b/packages/ringcentral-integration/modules/ConversationsV2/Conversations.interface.ts @@ -1,7 +1,6 @@ import { MessageAttachmentInfo } from '@rc-ex/core/definitions'; import { - NormalizedMessageRecord, Correspondent, VoicemailAttachment, FaxAttachment, @@ -12,11 +11,12 @@ import { Auth } from '../AuthV2'; import { RingCentralClient } from '../../lib/RingCentralClient'; import { ExtensionInfo } from '../ExtensionInfoV2'; import { MessageSender, Attachment } from '../MessageSenderV2'; -import MessageStore from '../MessageStore'; +import { MessageStore } from '../MessageStoreV2'; import { RolesAndPermissions } from '../RolesAndPermissionsV2'; import RegionSettings from '../RegionSettings'; import ContactMatcher from '../ContactMatcher'; import ConversationLogger from '../ConversationLogger'; +import { Message } from '../../interfaces/MessageStore.model'; export interface Deps { alert: Alert; @@ -45,7 +45,7 @@ export interface InputContent { attachments?: Attachment[]; } -export interface FormattedConversation extends NormalizedMessageRecord { +export interface FormattedConversation extends Message { unreadCounts: number; self: Correspondent; voicemailAttachment?: VoicemailAttachment; @@ -60,7 +60,7 @@ export interface FormattedConversation extends NormalizedMessageRecord { export interface FilteredConversation extends FormattedConversation { matchOrder?: number; - matchedMessage?: NormalizedMessageRecord; + matchedMessage?: Message; } export interface CorrespondentMatch { @@ -79,7 +79,7 @@ export interface CorrespondentResponse { } export interface CurrentConversation extends FormattedConversation { - messages: NormalizedMessageRecord[]; + messages: Message[]; senderNumber: Correspondent; recipients: Correspondent[]; } diff --git a/packages/ringcentral-integration/modules/ConversationsV2/Conversations.ts b/packages/ringcentral-integration/modules/ConversationsV2/Conversations.ts index a0f66adbd3..2b9d081490 100644 --- a/packages/ringcentral-integration/modules/ConversationsV2/Conversations.ts +++ b/packages/ringcentral-integration/modules/ConversationsV2/Conversations.ts @@ -38,7 +38,6 @@ import { getRecipientNumbersFromMessage, messageIsUnread, normalizeRecord, - NormalizedMessageRecord, Correspondent, } from '../../lib/messageHelper'; @@ -53,13 +52,11 @@ import { CorrespondentMatch, CorrespondentResponse, } from './Conversations.interface'; +import { Message } from '../../interfaces/MessageStore.model'; -function mergeMessages( - messages: NormalizedMessageRecord[], - oldMessages: NormalizedMessageRecord[], -): NormalizedMessageRecord[] { +function mergeMessages(messages: Message[], oldMessages: Message[]): Message[] { const tmp: { [key: string]: number } = {}; - const currentMessages: NormalizedMessageRecord[] = []; + const currentMessages: Message[] = []; messages.forEach((element) => { currentMessages.push(element); tmp[element.id] = 1; @@ -73,7 +70,7 @@ function mergeMessages( return currentMessages; } -function getEarliestTime(messages: NormalizedMessageRecord[]) { +function getEarliestTime(messages: Message[]) { let newTime = Date.now(); messages.forEach((message) => { const creationTime = new Date(message.creationTime).getTime(); @@ -84,7 +81,7 @@ function getEarliestTime(messages: NormalizedMessageRecord[]) { return newTime; } -function getUniqueNumbers(conversations: NormalizedMessageRecord[]): string[] { +function getUniqueNumbers(conversations: Message[]): string[] { const output: string[] = []; const numberMap: { [key: string]: boolean } = {}; function addIfNotExist(number: string) { @@ -180,28 +177,31 @@ export class Conversations extends RcModuleV2 { typeFilter: ObjectMapValue = messageTypes.all; @state - oldConversations: NormalizedMessageRecord[] = []; + oldConversations: Message[] = []; @state currentPage: number = 1; @state - fetchConversationsStatus: ObjectMapValue = conversationsStatus.idle; + fetchConversationsStatus: ObjectMapValue = + conversationsStatus.idle; @state currentConversationId?: string = null; @state - oldMessages: NormalizedMessageRecord[] = []; + oldMessages: Message[] = []; @state - fetchMessagesStatus: ObjectMapValue = conversationsStatus.idle; + fetchMessagesStatus: ObjectMapValue = + conversationsStatus.idle; @state inputContents: InputContent[] = []; @state - conversationStatus: ObjectMapValue = conversationsStatus.idle; + conversationStatus: ObjectMapValue = + conversationsStatus.idle; @state correspondentMatch: CorrespondentMatch[] = []; @@ -355,7 +355,7 @@ export class Conversations extends RcModuleV2 { @action _addCorrespondentResponses( - responses: NormalizedMessageRecord[] = [], + responses: Message[] = [], phoneNumber: string = '', ) { this.correspondentResponse = responses.reduce( @@ -517,7 +517,9 @@ export class Conversations extends RcModuleV2 { params.messageType = [typeFilter]; } try { - const { records } : GetMessageList = await this._deps.client + const { + records, + }: GetMessageList = await this._deps.client .account() .extension() .messageStore() @@ -606,7 +608,9 @@ export class Conversations extends RcModuleV2 { dateTo: dateTo.toISOString(), }; try { - const { records }: GetMessageList = await this._deps.client + const { + records, + }: GetMessageList = await this._deps.client .account() .extension() .messageStore() @@ -752,15 +756,15 @@ export class Conversations extends RcModuleV2 { that._deps.messageStore.allConversations, that.oldConversations, ]) - get allConversations(): NormalizedMessageRecord[] { + get allConversations(): Message[] { const conversations = this._deps.messageStore.allConversations; const oldConversations = this.oldConversations; if (oldConversations.length === 0) { return conversations; } - const newConversations: NormalizedMessageRecord[] = []; + const newConversations: Message[] = []; const conversationMap: { [key: string]: number } = {}; - const pushConversation = (c: NormalizedMessageRecord) => { + const pushConversation = (c: Message) => { // use conversationId when available, use id for VoiceMail/Fax/etc.. const cid = c.conversationId || c.id; if (conversationMap[cid]) { @@ -788,7 +792,7 @@ export class Conversations extends RcModuleV2 { get effectiveSearchString() { if (this.searchInput.length >= 3) { return this.searchInput; - }; + } return ''; } @@ -853,7 +857,9 @@ export class Conversations extends RcModuleV2 { (matches: CorrespondentMatch[], contact: Correspondent) => { const number = contact && (contact.phoneNumber || contact.extensionNumber); - return number && contactMapping[number] && contactMapping[number].length + return number && + contactMapping[number] && + contactMapping[number].length ? matches.concat(contactMapping[number]) : matches; }, @@ -973,9 +979,9 @@ export class Conversations extends RcModuleV2 { }); return; } - const messageList: NormalizedMessageRecord[] = + const messageList: Message[] = this._deps.messageStore.conversationStore[message.conversationId] || []; - const matchedMessage: NormalizedMessageRecord = messageList.find( + const matchedMessage: Message = messageList.find( (item) => (item.subject || '').toLowerCase().indexOf(searchString) > -1, ); if (matchedMessage) { @@ -1038,7 +1044,7 @@ export class Conversations extends RcModuleV2 { const conversation = conversations.find( (c) => c.conversationId === conversationId, ); - const messages: NormalizedMessageRecord[] = [].concat( + const messages: Message[] = [].concat( conversationStore[conversationId] || [], ); const currentConversation = { @@ -1138,7 +1144,7 @@ export class Conversations extends RcModuleV2 { this._removeCorrespondentMatchEntity(entity); } - addResponses(responses: NormalizedMessageRecord[]) { + addResponses(responses: Message[]) { this._addCorrespondentResponses(responses); } @@ -1146,7 +1152,7 @@ export class Conversations extends RcModuleV2 { this._removeCorrespondentResponses(phoneNumber); } - relateCorrespondentEntity(responses: NormalizedMessageRecord[]) { + relateCorrespondentEntity(responses: Message[]) { if ( !this._deps.contactMatcher || !this._deps.conversationLogger || diff --git a/packages/ringcentral-integration/modules/DateTimeFormatV2/DateTimeFormat.interface.ts b/packages/ringcentral-integration/modules/DateTimeFormatV2/DateTimeFormat.interface.ts index e3f8f223b8..b226e75ffc 100644 --- a/packages/ringcentral-integration/modules/DateTimeFormatV2/DateTimeFormat.interface.ts +++ b/packages/ringcentral-integration/modules/DateTimeFormatV2/DateTimeFormat.interface.ts @@ -5,7 +5,7 @@ import { import { Locale } from '../LocaleV2'; interface NameOptions { - name: string; + name?: string; } export interface AddFormatterOptions extends NameOptions { diff --git a/packages/ringcentral-integration/modules/DateTimeFormatV2/DateTimeFormat.ts b/packages/ringcentral-integration/modules/DateTimeFormatV2/DateTimeFormat.ts index e4434092c4..6020c7453d 100644 --- a/packages/ringcentral-integration/modules/DateTimeFormatV2/DateTimeFormat.ts +++ b/packages/ringcentral-integration/modules/DateTimeFormatV2/DateTimeFormat.ts @@ -54,7 +54,7 @@ export class DateTimeFormat extends RcModuleV2 { utcTimestamp, locale = this._deps.locale.currentLocale, type, - }: FormatDateTimeOptions) { + }: Partial) { if (name && typeof this._formatters[name] === 'function') { return this._formatters[name]({ utcTimestamp, diff --git a/packages/ringcentral-integration/modules/ErrorLogger/ErrorLogger.ts b/packages/ringcentral-integration/modules/ErrorLogger/ErrorLogger.ts index 15c7032052..00862a0262 100644 --- a/packages/ringcentral-integration/modules/ErrorLogger/ErrorLogger.ts +++ b/packages/ringcentral-integration/modules/ErrorLogger/ErrorLogger.ts @@ -32,7 +32,7 @@ export enum Severity { }) export class ErrorLogger extends RcModule { private _auth: any; - private _loggedIn?: boolean; + private _loggedIn?: boolean = false; private _sentryInitialized: boolean = false; constructor({ diff --git a/packages/ringcentral-integration/modules/ExtensionDeviceV2/ExtensionDevice.ts b/packages/ringcentral-integration/modules/ExtensionDeviceV2/ExtensionDevice.ts index c8a55be374..e3cb94a098 100644 --- a/packages/ringcentral-integration/modules/ExtensionDeviceV2/ExtensionDevice.ts +++ b/packages/ringcentral-integration/modules/ExtensionDeviceV2/ExtensionDevice.ts @@ -32,11 +32,7 @@ export class ExtensionDevice extends DataFetcherV2Consumer< cleanOnReset: true, fetchFunction: async (): Promise => fetchList((params: any) => - this._deps.client - .account() - .extension() - .device() - .list(params), + this._deps.client.account().extension().device().list(params), ), }); this._deps.dataFetcherV2.register(this._source); diff --git a/packages/ringcentral-integration/modules/ExtensionPhoneNumberV2/ExtensionPhoneNumber.ts b/packages/ringcentral-integration/modules/ExtensionPhoneNumberV2/ExtensionPhoneNumber.ts index 76c3baca70..50d820fe96 100644 --- a/packages/ringcentral-integration/modules/ExtensionPhoneNumberV2/ExtensionPhoneNumber.ts +++ b/packages/ringcentral-integration/modules/ExtensionPhoneNumberV2/ExtensionPhoneNumber.ts @@ -6,7 +6,7 @@ import { computed, watch } from '@ringcentral-integration/core'; import { filter, find } from 'ramda'; import { Unsubscribe } from 'redux'; -import { usageTypes } from '../../constants/usageTypes'; +import { usageTypes } from '../../enums/usageTypes'; import { subscriptionFilters } from '../../enums/subscriptionFilters'; import { subscriptionHints } from '../../enums/subscriptionHints'; import { Module } from '../../lib/di'; diff --git a/packages/ringcentral-integration/modules/ForwardingNumberV2/ForwardingNumber.ts b/packages/ringcentral-integration/modules/ForwardingNumberV2/ForwardingNumber.ts index a91b182b6d..781b7fa808 100644 --- a/packages/ringcentral-integration/modules/ForwardingNumberV2/ForwardingNumber.ts +++ b/packages/ringcentral-integration/modules/ForwardingNumberV2/ForwardingNumber.ts @@ -28,14 +28,23 @@ export class ForwardingNumber extends DataFetcherV2Consumer< ...deps.forwardingNumberOptions, key: 'forwardingNumber', cleanOnReset: true, - fetchFunction: async (): Promise => - fetchList((params: any) => - this._deps.client - .account() - .extension() - .forwardingNumber() - .list(params), - ), + fetchFunction: async (): Promise => { + try { + const forwardingNumbers = await fetchList((params: any) => + this._deps.client + .account() + .extension() + .forwardingNumber() + .list(params), + ); + return forwardingNumbers; + } catch (error) { + if (error.response?.status === 403) { + return []; + } + throw error; + } + }, readyCheckFunction: () => this._deps.rolesAndPermissions.ready, permissionCheckFunction: () => !!this._deps.rolesAndPermissions.permissions diff --git a/packages/ringcentral-integration/modules/GenericMeetingV2/GenericMeeting.ts b/packages/ringcentral-integration/modules/GenericMeetingV2/GenericMeeting.ts index aa0fb2863c..99bdaaadf4 100644 --- a/packages/ringcentral-integration/modules/GenericMeetingV2/GenericMeeting.ts +++ b/packages/ringcentral-integration/modules/GenericMeetingV2/GenericMeeting.ts @@ -269,6 +269,10 @@ export class GenericMeeting extends RcModuleV2 { return this._meetingModule.meeting; } + get defaultTopic() { + return this._meetingModule.defaultTopic; + } + get delegators() { return this._meetingModule.delegators; } diff --git a/packages/ringcentral-integration/modules/GlipCompanyV2/GlipCompany.ts b/packages/ringcentral-integration/modules/GlipCompanyV2/GlipCompany.ts index f4cda395a7..0be8a78fa7 100644 --- a/packages/ringcentral-integration/modules/GlipCompanyV2/GlipCompany.ts +++ b/packages/ringcentral-integration/modules/GlipCompanyV2/GlipCompany.ts @@ -23,10 +23,7 @@ export class GlipCompany extends DataFetcherV2Consumer { ...deps.glipCompanyOptions, key: 'glipCompany', fetchFunction: async (): Promise => { - const response = await this._deps.client - .glip() - .companies('~') - .get(); + const response = await this._deps.client.glip().companies('~').get(); return response; }, readyCheckFunction: () => this._deps.rolesAndPermissions.ready, diff --git a/packages/ringcentral-integration/modules/LocaleV2/Locale.ts b/packages/ringcentral-integration/modules/LocaleV2/Locale.ts index eb9d525381..601448fbcb 100644 --- a/packages/ringcentral-integration/modules/LocaleV2/Locale.ts +++ b/packages/ringcentral-integration/modules/LocaleV2/Locale.ts @@ -6,8 +6,7 @@ import I18n, { import formatMessage from 'format-message'; import detectBrowserLocale from '../../lib/detectBrowserLocale'; import { Module } from '../../lib/di'; -import proxify from '../../lib/proxy/proxify'; -import { proxyState } from '../../lib/proxy/proxyState'; +import { proxify } from '../../lib/proxy/proxify'; import { Deps } from './Locale.interface'; @Module({ @@ -36,12 +35,22 @@ export class Locale extends RcModuleV2 { return this._deps.localeOptions?.pollingInterval ?? 2000; } - @proxyState(async (that: Locale, locale: string) => { - await that._setLocale(locale); - }) @state locale: string = null; + @state + proxyLocale: string = null; + + @action + _setProxyLocaleSuccess(locale: string) { + this.proxyLocale = locale; + } + + @proxify + async setProxyLocaleSuccess(locale: string) { + this._setProxyLocaleSuccess(locale); + } + @state debugMode = false; @@ -64,6 +73,17 @@ export class Locale extends RcModuleV2 { } } + async initializeProxy() { + await this._setLocale(this.currentLocale); + this.setProxyLocaleSuccess(this.currentLocale); + this.store.subscribe(async () => { + if (this.locale !== this.proxyLocale) { + await this._setLocale(this.locale); + this.setProxyLocaleSuccess(this.locale); + } + }); + } + async onInit() { await this.setLocale( this._detectBrowser ? this.browserLocale : this._defaultLocale, @@ -88,7 +108,9 @@ export class Locale extends RcModuleV2 { } get currentLocale() { - return this.locale || this._defaultLocale; + return ( + (this._transport ? this.proxyLocale : this.locale) ?? this._defaultLocale + ); } get browserLocale() { diff --git a/packages/ringcentral-integration/modules/MeetingV2/Meeting.ts b/packages/ringcentral-integration/modules/MeetingV2/Meeting.ts index a7e58878bd..b7e9db65c6 100644 --- a/packages/ringcentral-integration/modules/MeetingV2/Meeting.ts +++ b/packages/ringcentral-integration/modules/MeetingV2/Meeting.ts @@ -10,11 +10,12 @@ import { import moment from 'moment'; import { filter, find, isEmpty, pick } from 'ramda'; import { DEFAULT_LOCALE } from '@ringcentral-integration/i18n'; - +import { Analytics } from '../AnalyticsV2'; import { comparePreferences, generateRandomPassword, getDefaultMeetingSettings, + getDefaultTopic, getInitializedStartTime, getMobileDialingNumberTpl, getPhoneDialingNumberTpl, @@ -122,6 +123,18 @@ export class Meeting extends RcModuleV2 implements IMeeting { @state isPreferencesChanged: boolean = false; + get extensionName(): string { + return this._deps.extensionInfo.info?.name; + } + + @computed(({ extensionName, currentLocale }) => [ + extensionName, + currentLocale, + ]) + get defaultTopic(): string { + return getDefaultTopic(this.extensionName, this.currentLocale); + } + @computed((that) => [that.userSettings?.scheduleMeeting]) get scheduleUserSettings(): Partial { return this.userSettings?.scheduleMeeting || {}; @@ -382,11 +395,15 @@ export class Meeting extends RcModuleV2 implements IMeeting { @track((that: Meeting, isScheduling: boolean) => { if (!isScheduling) return; - // TODO: fix analytics V2 - const target = (that.parentModule as any).analytics?._getTrackTarget(); - if (target) { - return [trackEvents.clickMeetingSchedulePage, { router: target.router }]; - } + return (analytics: Analytics) => { + const target = analytics.getTrackTarget(); + if (target) { + return [ + trackEvents.clickMeetingSchedulePage, + { router: target.router }, + ]; + } + }; }) @action protected _updateIsScheduling(isScheduling: boolean) { diff --git a/packages/ringcentral-integration/modules/MeetingV2/i18n/de-DE.js b/packages/ringcentral-integration/modules/MeetingV2/i18n/de-DE.js new file mode 100644 index 0000000000..a57c939938 --- /dev/null +++ b/packages/ringcentral-integration/modules/MeetingV2/i18n/de-DE.js @@ -0,0 +1,5 @@ +export default { + meetingTitle: "Besprechung von {extensionName}" +}; + +// @key: @#@"meetingTitle"@#@ @source: @#@"{extensionName}'s Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/MeetingV2/i18n/en-GB.js b/packages/ringcentral-integration/modules/MeetingV2/i18n/en-GB.js new file mode 100644 index 0000000000..012f3dd3e2 --- /dev/null +++ b/packages/ringcentral-integration/modules/MeetingV2/i18n/en-GB.js @@ -0,0 +1,5 @@ +export default { + meetingTitle: "{extensionName}'s Meeting" +}; + +// @key: @#@"meetingTitle"@#@ @source: @#@"{extensionName}'s Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/MeetingV2/i18n/es-419.js b/packages/ringcentral-integration/modules/MeetingV2/i18n/es-419.js new file mode 100644 index 0000000000..5a1e6c67d3 --- /dev/null +++ b/packages/ringcentral-integration/modules/MeetingV2/i18n/es-419.js @@ -0,0 +1,5 @@ +export default { + meetingTitle: "Reunión de {extensionName}" +}; + +// @key: @#@"meetingTitle"@#@ @source: @#@"{extensionName}'s Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/MeetingV2/i18n/es-ES.js b/packages/ringcentral-integration/modules/MeetingV2/i18n/es-ES.js new file mode 100644 index 0000000000..5a1e6c67d3 --- /dev/null +++ b/packages/ringcentral-integration/modules/MeetingV2/i18n/es-ES.js @@ -0,0 +1,5 @@ +export default { + meetingTitle: "Reunión de {extensionName}" +}; + +// @key: @#@"meetingTitle"@#@ @source: @#@"{extensionName}'s Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/MeetingV2/i18n/fr-CA.js b/packages/ringcentral-integration/modules/MeetingV2/i18n/fr-CA.js new file mode 100644 index 0000000000..c419fd5dd3 --- /dev/null +++ b/packages/ringcentral-integration/modules/MeetingV2/i18n/fr-CA.js @@ -0,0 +1,5 @@ +export default { + meetingTitle: "Réunion de {extensionName}" +}; + +// @key: @#@"meetingTitle"@#@ @source: @#@"{extensionName}'s Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/MeetingV2/i18n/fr-FR.js b/packages/ringcentral-integration/modules/MeetingV2/i18n/fr-FR.js new file mode 100644 index 0000000000..c419fd5dd3 --- /dev/null +++ b/packages/ringcentral-integration/modules/MeetingV2/i18n/fr-FR.js @@ -0,0 +1,5 @@ +export default { + meetingTitle: "Réunion de {extensionName}" +}; + +// @key: @#@"meetingTitle"@#@ @source: @#@"{extensionName}'s Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/MeetingV2/i18n/it-IT.js b/packages/ringcentral-integration/modules/MeetingV2/i18n/it-IT.js new file mode 100644 index 0000000000..2ead623b5d --- /dev/null +++ b/packages/ringcentral-integration/modules/MeetingV2/i18n/it-IT.js @@ -0,0 +1,5 @@ +export default { + meetingTitle: "Riunione di {extensionName}" +}; + +// @key: @#@"meetingTitle"@#@ @source: @#@"{extensionName}'s Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/MeetingV2/i18n/ja-JP.js b/packages/ringcentral-integration/modules/MeetingV2/i18n/ja-JP.js new file mode 100644 index 0000000000..dec4db7634 --- /dev/null +++ b/packages/ringcentral-integration/modules/MeetingV2/i18n/ja-JP.js @@ -0,0 +1,5 @@ +export default { + meetingTitle: "{extensionName}の会議" +}; + +// @key: @#@"meetingTitle"@#@ @source: @#@"{extensionName}'s Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/MeetingV2/i18n/ko-KR.js b/packages/ringcentral-integration/modules/MeetingV2/i18n/ko-KR.js new file mode 100644 index 0000000000..955e6c82a9 --- /dev/null +++ b/packages/ringcentral-integration/modules/MeetingV2/i18n/ko-KR.js @@ -0,0 +1,5 @@ +export default { + meetingTitle: "{extensionName}님의 모임" +}; + +// @key: @#@"meetingTitle"@#@ @source: @#@"{extensionName}'s Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/MeetingV2/i18n/nl-NL.js b/packages/ringcentral-integration/modules/MeetingV2/i18n/nl-NL.js new file mode 100644 index 0000000000..8f9cb20d6b --- /dev/null +++ b/packages/ringcentral-integration/modules/MeetingV2/i18n/nl-NL.js @@ -0,0 +1,5 @@ +export default { + meetingTitle: "Meeting van {extensionName}" +}; + +// @key: @#@"meetingTitle"@#@ @source: @#@"{extensionName}'s Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/MeetingV2/i18n/pt-BR.js b/packages/ringcentral-integration/modules/MeetingV2/i18n/pt-BR.js new file mode 100644 index 0000000000..e8077858bc --- /dev/null +++ b/packages/ringcentral-integration/modules/MeetingV2/i18n/pt-BR.js @@ -0,0 +1,5 @@ +export default { + meetingTitle: "Reunião de {extensionName}" +}; + +// @key: @#@"meetingTitle"@#@ @source: @#@"{extensionName}'s Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/MeetingV2/i18n/pt-PT.js b/packages/ringcentral-integration/modules/MeetingV2/i18n/pt-PT.js new file mode 100644 index 0000000000..e8077858bc --- /dev/null +++ b/packages/ringcentral-integration/modules/MeetingV2/i18n/pt-PT.js @@ -0,0 +1,5 @@ +export default { + meetingTitle: "Reunião de {extensionName}" +}; + +// @key: @#@"meetingTitle"@#@ @source: @#@"{extensionName}'s Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/MeetingV2/i18n/zh-CN.js b/packages/ringcentral-integration/modules/MeetingV2/i18n/zh-CN.js new file mode 100644 index 0000000000..3ce661d27c --- /dev/null +++ b/packages/ringcentral-integration/modules/MeetingV2/i18n/zh-CN.js @@ -0,0 +1,5 @@ +export default { + meetingTitle: "{extensionName} 的会议" +}; + +// @key: @#@"meetingTitle"@#@ @source: @#@"{extensionName}'s Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/MeetingV2/i18n/zh-TW.js b/packages/ringcentral-integration/modules/MeetingV2/i18n/zh-TW.js new file mode 100644 index 0000000000..99d1d4da4a --- /dev/null +++ b/packages/ringcentral-integration/modules/MeetingV2/i18n/zh-TW.js @@ -0,0 +1,5 @@ +export default { + meetingTitle: "{extensionName} 的會議" +}; + +// @key: @#@"meetingTitle"@#@ @source: @#@"{extensionName}'s Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/MessageStoreV2/MessageStore.interface.ts b/packages/ringcentral-integration/modules/MessageStoreV2/MessageStore.interface.ts new file mode 100644 index 0000000000..cd0b6357f3 --- /dev/null +++ b/packages/ringcentral-integration/modules/MessageStoreV2/MessageStore.interface.ts @@ -0,0 +1,66 @@ +import { GetMessageInfoResponse } from '@rc-ex/core/definitions'; +import { + DataFetcherV2ConsumerBaseDeps, + DataSourceBaseProps, +} from '../DataFetcherV2'; +import Alert from '../Alert'; +import { Auth } from '../AuthV2'; +import { Subscription } from '../SubscriptionV2'; +import { ConnectivityMonitor } from '../ConnectivityMonitorV2'; +import { RolesAndPermissions } from '../RolesAndPermissionsV2'; +import { TabManager } from '../TabManagerV2'; +import { AvailabilityMonitor } from '../AvailabilityMonitorV2'; +import { + Message, + MessageStoreModel, +} from '../../interfaces/MessageStore.model'; + +export interface MessageStoreOptions extends DataSourceBaseProps { + daySpan?: number; + conversationsLoadLength?: number; + conversationLoadLength?: number; + messagesFilter?: (...args: any) => any; +} + +export interface Deps extends DataFetcherV2ConsumerBaseDeps { + alert: Alert; + auth: Auth; + client: any; + subscription: Subscription; + connectivityMonitor: ConnectivityMonitor; + rolesAndPermissions: RolesAndPermissions; + tabManager?: TabManager; + availabilityMonitor?: AvailabilityMonitor; + messageStoreOptions?: MessageStoreOptions; +} + +export type MessageHandler = (record: Message) => any; + +export type DispatchedMessageIds = { + id: number; + lastModifiedTime: string; +}[]; + +export interface ProcessRawConversationListOptions { + records: GetMessageInfoResponse[]; + conversationStore: MessageStoreModel['conversationStore']; + isFSyncSuccess?: boolean; +} + +export interface ProcessRawConversationStoreOptions { + records: GetMessageInfoResponse[]; + isFSyncSuccess?: boolean; +} + +export interface SyncFunctionOptions { + recordCount?: number; + conversationLoadLength?: number; + dateFrom: Date; + dateTo: Date; + syncToken?: string; + receivedRecordsLength?: number; +} + +export type MessageStoreItem = Message & { unreadCounts: number }; + +export type MessageStoreConversations = MessageStoreItem[]; diff --git a/packages/ringcentral-integration/modules/MessageStoreV2/MessageStore.ts b/packages/ringcentral-integration/modules/MessageStoreV2/MessageStore.ts new file mode 100644 index 0000000000..495110c75f --- /dev/null +++ b/packages/ringcentral-integration/modules/MessageStoreV2/MessageStore.ts @@ -0,0 +1,911 @@ +import { EventEmitter } from 'events'; +import { GetMessageInfoResponse } from '@rc-ex/core/definitions'; +import { watch, computed, track } from '@ringcentral-integration/core'; +import { Module } from '../../lib/di'; +import sleep from '../../lib/sleep'; +import { proxify } from '../../lib/proxy/proxify'; +import { subscriptionFilters } from '../../enums/subscriptionFilters'; +import * as messageHelper from '../../lib/messageHelper'; +import { batchPutApi } from '../../lib/batchApiHelper'; +import { messageStoreErrors } from './messageStoreErrors'; +import { debounce } from '../../lib/debounce-throttle'; +import { DataFetcherV2Consumer, DataSource } from '../DataFetcherV2'; +import { getSyncParams } from './messageStoreHelper'; +import { + Deps, + DispatchedMessageIds, + MessageHandler, + MessageStoreConversations, + ProcessRawConversationListOptions, + ProcessRawConversationStoreOptions, + SyncFunctionOptions, +} from './MessageStore.interface'; +import { + Message, + Messages, + MessageStoreModel, + MessageSyncList, +} from '../../interfaces/MessageStore.model'; +import { trackEvents } from '../Analytics'; +import { callingModes } from '../CallingSettingsV2'; + +const DEFAULT_CONVERSATIONS_LOAD_LENGTH = 10; +const DEFAULT_CONVERSATION_LOAD_LENGTH = 100; +const DEFAULT_POLLING_INTERVAL = 30 * 60 * 1000; // 30 min +const DEFAULT_TTL = 5 * 60 * 1000; // 5 min +const DEFAULT_RETRY = 62 * 1000; // 62 sec + +const DEFAULT_DAY_SPAN = 7; // default to load 7 days messages +const DEFAULT_MESSAGES_FILTER = (list: Messages) => list; +const UPDATE_MESSAGE_ONCE_COUNT = 20; // Number of messages to be updated in one time + +/** + * Messages data managing module + * fetch conversations + * handle new message subscription + */ +@Module({ + name: 'MessageStore', + deps: [ + 'Alert', + 'Auth', + 'Client', + 'DataFetcherV2', + 'Subscription', + 'ConnectivityMonitor', + 'RolesAndPermissions', + { dep: 'AvailabilityMonitor', optional: true }, + { dep: 'TabManager', optional: true }, + { dep: 'MessageStoreOptions', optional: true }, + ], +}) +export class MessageStore extends DataFetcherV2Consumer< + Deps, + MessageStoreModel +> { + protected _conversationsLoadLength = + this._deps.messageStoreOptions?.conversationsLoadLength ?? + DEFAULT_CONVERSATIONS_LOAD_LENGTH; + + protected _conversationLoadLength = + this._deps.messageStoreOptions?.conversationLoadLength ?? + DEFAULT_CONVERSATION_LOAD_LENGTH; + + protected _messagesFilter = + this._deps.messageStoreOptions?.messagesFilter ?? DEFAULT_MESSAGES_FILTER; + + protected _daySpan = + this._deps.messageStoreOptions?.daySpan ?? DEFAULT_DAY_SPAN; + + protected _eventEmitter = new EventEmitter(); + + protected _dispatchedMessageIds: DispatchedMessageIds = []; + + constructor(deps: Deps) { + super({ + deps, + }); + + const { + disableCache = false, + polling = false, + timeToRetry = DEFAULT_RETRY, + pollingInterval = DEFAULT_POLLING_INTERVAL, + ttl = DEFAULT_TTL, + } = this._deps.messageStoreOptions ?? {}; + this._source = new DataSource({ + ...this._deps.messageStoreOptions, + key: 'messageStore', + disableCache, + ttl, + polling, + timeToRetry, + pollingInterval, + cleanOnReset: true, + permissionCheckFunction: () => this._hasPermission, + readyCheckFunction: () => this._deps.rolesAndPermissions.ready, + fetchFunction: async () => this._syncData(), + }); + this._deps.dataFetcherV2.register(this._source); + } + + onInit() { + if (this._hasPermission) { + this._deps.subscription.subscribe([subscriptionFilters.messageStore]); + } + } + + onInitOnce() { + if (this._deps.connectivityMonitor) { + watch( + this, + () => this._deps.connectivityMonitor.connectivity, + (newValue) => { + if (this.ready && this._deps.connectivityMonitor.ready && newValue) { + this._deps.dataFetcherV2.fetchData(this._source); + } + }, + ); + } + watch( + this, + () => this._deps.subscription.message, + (newValue) => { + if ( + !this.ready || + (this._deps.tabManager && !this._deps.tabManager.active) + ) { + return; + } + const accountExtensionEndPoint = /\/message-store$/; + if ( + newValue && + accountExtensionEndPoint.test(newValue.event) && + newValue.body?.changes + ) { + this.fetchData({ passive: true }); + } + }, + ); + } + + @proxify + async _updateData(data: any, timestamp = Date.now()) { + this._deps.dataFetcherV2.updateData(this._source, data, timestamp); + } + + _processRawConversationList({ + records, + conversationStore, + isFSyncSuccess, + }: ProcessRawConversationListOptions) { + const state = this.data?.conversationList || []; + const newState: MessageStoreModel['conversationList'] = []; + const stateMap: Record = {}; + if (!isFSyncSuccess) { + if (!records || records.length === 0) { + return state; + } + state.forEach((oldConversation) => { + newState.push(oldConversation); + stateMap[oldConversation.id] = { + index: newState.length - 1, + }; + }); + } + records.forEach((record) => { + const message = messageHelper.normalizeRecord(record); + const id = message.conversationId; + const newCreationTime = message.creationTime; + const isDeleted = messageHelper.messageIsDeleted(message); + if (stateMap[id]) { + const oldConversation = newState[stateMap[id].index]; + const creationTime = oldConversation.creationTime; + if (creationTime < newCreationTime && !isDeleted) { + newState[stateMap[id].index] = { + id, + creationTime: newCreationTime, + type: message.type, + messageId: message.id, + }; + } + // when user deleted a coversation message + if (isDeleted && message.id === oldConversation.messageId) { + const oldMessageList = conversationStore[id] || []; + const exsitedMessageList = oldMessageList.filter( + (m) => m.id !== message.id, + ); + if (exsitedMessageList.length > 0) { + newState[stateMap[id].index] = { + id, + creationTime: exsitedMessageList[0].creationTime, + type: exsitedMessageList[0].type, + messageId: exsitedMessageList[0].id, + }; + return; + } + // when user delete conversation + newState[stateMap[id].index] = null; + delete stateMap[id]; + } + return; + } + if (isDeleted || !messageHelper.messageIsAcceptable(message)) { + return; + } + newState.push({ + id, + creationTime: newCreationTime, + type: message.type, + messageId: message.id, + }); + stateMap[id] = { + index: newState.length - 1, + }; + }); + return newState + .filter((c) => !!c && typeof c.creationTime === 'number') + .sort(messageHelper.sortByCreationTime); + } + + _processRawConversationStore({ + records, + isFSyncSuccess, + }: ProcessRawConversationStoreOptions) { + const state = this.data?.conversationStore ?? {}; + let newState: MessageStoreModel['conversationStore'] = {}; + const updatedConversations: Record = {}; + if (!isFSyncSuccess) { + if (!records || records.length === 0) { + return state; + } + newState = { + ...state, + }; + } + records.forEach((record) => { + const message = messageHelper.normalizeRecord(record); + const id = message.conversationId; + const newMessages = newState[id] ? [].concat(newState[id]) : []; + const oldMessageIndex = newMessages.findIndex((r) => r.id === record.id); + if (messageHelper.messageIsDeleted(message)) { + newState[id] = newMessages.filter((m) => m.id !== message.id); + if (newState[id].length === 0) { + delete newState[id]; + } + return; + } + if (oldMessageIndex > -1) { + if ( + newMessages[oldMessageIndex].lastModifiedTime < + message.lastModifiedTime + ) { + newMessages[oldMessageIndex] = message; + } + } else if (messageHelper.messageIsAcceptable(message)) { + newMessages.push(message); + } + updatedConversations[id] = 1; + newState[id] = newMessages; + }); + Object.keys(updatedConversations).forEach((id) => { + const noSorted = newState[id]; + newState[id] = noSorted.sort(messageHelper.sortByCreationTime); + }); + return newState; + } + + async _syncFunction({ + recordCount, + conversationLoadLength, + dateFrom, + dateTo, + syncToken, + receivedRecordsLength = 0, + }: SyncFunctionOptions): Promise { + const params = getSyncParams({ + recordCount, + conversationLoadLength, + dateFrom, + dateTo, + syncToken, + }); + const { + records, + syncInfo, + }: MessageSyncList = await this._deps.client + .account() + .extension() + .messageSync() + .list(params); + receivedRecordsLength += records.length; + if (!syncInfo.olderRecordsExist || receivedRecordsLength >= recordCount) { + return { records, syncInfo }; + } + await sleep(500); + const olderDateTo = new Date(records[records.length - 1].creationTime); + const olderRecordResult = await this._syncFunction({ + conversationLoadLength, + dateFrom, + dateTo: olderDateTo, + }); + return { + records: records.concat(olderRecordResult.records), + syncInfo, + }; + } + + async _syncData({ dateTo = null as Date, passive = false } = {}) { + const conversationsLoadLength = this._conversationsLoadLength; + const conversationLoadLength = this._conversationLoadLength; + const { ownerId } = this._deps.auth; + try { + const dateFrom = new Date(); + dateFrom.setDate(dateFrom.getDate() - this._daySpan); + let syncToken = dateTo ? null : this.syncInfo?.syncToken; + const recordCount = conversationsLoadLength * conversationLoadLength; + let data; + try { + data = await this._syncFunction({ + recordCount, + conversationLoadLength, + dateFrom, + syncToken, + dateTo, + }); + } catch (error) { + if ( + error && + (error.message === 'Parameter [syncToken] value is invalid' || + error.message === 'Parameter [syncToken] is invalid') + ) { + data = await this._syncFunction({ + recordCount, + conversationLoadLength, + dateFrom, + syncToken: null, + dateTo, + }); + syncToken = null; + } else { + throw error; + } + } + if (this._deps.auth.ownerId === ownerId) { + const records = this._messagesFilter(data.records); + const isFSyncSuccess = !syncToken; + // this is only executed in passive sync mode (aka. invoked by subscription) + if (passive) { + this._dispatchMessageHandlers(records); + } + return { + conversationList: this._processRawConversationList({ + records, + conversationStore: this.conversationStore, + isFSyncSuccess, + }), + conversationStore: this._processRawConversationStore({ + records, + isFSyncSuccess, + }), + syncInfo: data.syncInfo, + }; + } + } catch (error) { + if (this._deps.auth.ownerId === ownerId) { + console.error(error); + throw error; + } + } + } + + @proxify + async fetchData({ passive = false } = {}) { + const data = await this._syncData({ passive }); + this._updateData(data); + } + + onNewInboundMessage(handler: MessageHandler) { + if (typeof handler === 'function') { + this._eventEmitter.on('newInboundMessageNotification', handler); + } + } + + onMessageUpdated(handler: MessageHandler) { + if (typeof handler === 'function') { + this._eventEmitter.on('messageUpdated', handler); + } + } + + /** + * Dispatch events to different handlers + */ + _dispatchMessageHandlers(records: GetMessageInfoResponse[]) { + // Sort all records by creation time + records = records + .slice() + .sort( + (a, b) => + new Date(a.creationTime).getTime() - + new Date(b.creationTime).getTime(), + ); + for (const record of records) { + const { + id, + direction, + availability, + messageStatus, + readStatus, + lastModifiedTime, + creationTime, + } = record || {}; + // Notify when new message incoming + // fix mix old messages and new messages logic error. + if (!this._messageDispatched(record)) { + // Mark last 10 messages that dispatched + // To present dispatching same record twice + this._dispatchedMessageIds = [{ id, lastModifiedTime }] + .concat(this._dispatchedMessageIds) + .slice(0, 20); + this._eventEmitter.emit('messageUpdated', record); + // For new inbound message notification + if ( + direction === 'Inbound' && + readStatus === 'Unread' && + messageStatus === 'Received' && + availability === 'Alive' && + new Date(creationTime).getTime() > + new Date(lastModifiedTime).getTime() - 600 * 1000 + ) { + this._eventEmitter.emit('newInboundMessageNotification', record); + } + } + } + } + + _messageDispatched(message: GetMessageInfoResponse) { + return this._dispatchedMessageIds.some( + (m) => + m.id === message.id && m.lastModifiedTime === message.lastModifiedTime, + ); + } + + @proxify + async pushMessages(records: GetMessageInfoResponse[]) { + this._deps.dataFetcherV2.updateData( + this._source, + { + ...this.data, + conversationList: this._processRawConversationList({ + records, + conversationStore: this.conversationStore, + }), + conversationStore: this._processRawConversationStore({ + records, + }), + }, + this.timestamp, + ); + } + + pushMessage(record: GetMessageInfoResponse) { + this.pushMessages([record]); + } + + async _updateMessageApi( + messageId: Message['id'], + status: Message['readStatus'], + ) { + const body = { + readStatus: status, + }; + const updateRequest: GetMessageInfoResponse = await this._deps.client + .account() + .extension() + .messageStore(messageId) + .put(body); + return updateRequest; + } + + async deleteMessageApi(messageId: string) { + const response: string = await this._deps.client + .account() + .extension() + .messageStore(messageId) + .delete(); + return response; + } + + sliceConversations() { + const conversationIds = Object.keys(this.conversationStore); + const messages = conversationIds.reduce( + (acc, id) => acc.concat(this.conversationStore[id]), + [] as Messages, + ); + const messageIds = this._messagesFilter(messages).map( + (item: Message) => item.id, + ); + const conversationList = (this.data?.conversationList ?? []).filter( + ({ messageId }) => messageIds.indexOf(messageId) > -1, + ); + const conversationStore = Object.keys(this.conversationStore).reduce( + (acc, key) => { + const messages = this.conversationStore[key]; + const persist = messages.filter( + ({ id }) => messageIds.indexOf(id) > -1, + ); + if (!persist.length) { + return acc; + } + acc[key] = persist; + return acc; + }, + {} as Record, + ); + this._deps.dataFetcherV2.updateData( + this._source, + { + ...this.data, + conversationList, + conversationStore, + }, + this.timestamp, + ); + } + + /** + * Batch update messages status + */ + async _batchUpdateMessagesApi( + messageIds: Message['id'][], + body: { + body: { + readStatus: Message['readStatus']; + }; + }[], + ) { + // Not to request when there're no messages + if (!messageIds || messageIds.length === 0) { + return; + } + + const ids = decodeURIComponent(messageIds.join(',')); + const platform = this._deps.client.service.platform(); + const responses: Response[] = await batchPutApi({ + platform, + url: `/restapi/v1.0/account/~/extension/~/message-store/${ids}`, + body, + }); + return responses; + } + + /** + * Change messages' status to `READ` or `UNREAD`. + * Update 20 messages per time with `_batchUpdateMessagesApi`, + * or `_updateMessageApi` one by one in recursion. + */ + async _updateMessagesApi( + messageIds: Message['id'][], + status: Message['readStatus'], + ) { + const allMessageIds = messageIds; + if (!allMessageIds || allMessageIds.length === 0) { + return []; + } + + const results: GetMessageInfoResponse[] = []; + + for (let index = 0; ; index++) { + let nextLength = (index + 1) * UPDATE_MESSAGE_ONCE_COUNT; + + if (nextLength > allMessageIds.length) { + nextLength = allMessageIds.length - index * UPDATE_MESSAGE_ONCE_COUNT; + } else { + nextLength = UPDATE_MESSAGE_ONCE_COUNT; + } + + // If there's only one message, use another api to update its status + if (nextLength === 1) { + const result = await this._updateMessageApi(messageIds[0], status); + return [result]; + } + + const leftIds = allMessageIds.slice( + index * UPDATE_MESSAGE_ONCE_COUNT, + index * UPDATE_MESSAGE_ONCE_COUNT + nextLength, + ); + + const body = leftIds.map(() => ({ body: { readStatus: status } })); + const responses = await this._batchUpdateMessagesApi(leftIds, body); + await Promise.all( + responses.map(async (res) => { + if (res.status === 200) { + const result = await res.json(); + results.push(result); + } + }), + ); + + const { ownerId } = this._deps.auth; + if (allMessageIds.length > (index + 1) * UPDATE_MESSAGE_ONCE_COUNT) { + await sleep(1300); + // Check if owner ID has been changed. If it is, cancel this update. + if (ownerId !== this._deps.auth.ownerId) { + return []; + } + } else { + break; + } + } + + return results; + } + + /** + * Set message status to `READ`. + */ + @proxify + async readMessages(conversationId: Message['conversationId']) { + this._debouncedSetConversationAsRead(conversationId); + } + + _debouncedSetConversationAsRead = debounce({ + fn: this._setConversationAsRead, + threshold: 500, + leading: true, + }); + + async _setConversationAsRead(conversationId: Message['conversationId']) { + const messageList = this.conversationStore[conversationId]; + if (!messageList || messageList.length === 0) { + return; + } + const unreadMessageIds = messageList + .filter(messageHelper.messageIsUnread) + .map((m) => m.id); + if (unreadMessageIds.length === 0) { + return; + } + try { + const { ownerId } = this._deps.auth; + const updatedMessages = await this._updateMessagesApi( + unreadMessageIds, + 'Read', + ); + + if (ownerId !== this._deps.auth.ownerId) { + return; + } + + this.pushMessages(updatedMessages); + } catch (error) { + console.error(error); + + if ( + !this._deps.availabilityMonitor || + !(await this._deps.availabilityMonitor.checkIfHAError(error)) + ) { + this._deps.alert.warning({ message: messageStoreErrors.readFailed }); + } + } + } + + /** + * Set message status to `UNREAD`. + */ + @proxify + async unreadMessage(messageId: Message['id']) { + this.onUnmarkMessages(); + try { + const message = await this._updateMessageApi(messageId, 'Unread'); + this.pushMessage(message); + } catch (error) { + console.error(error); + + if ( + !this._deps.availabilityMonitor || + !(await this._deps.availabilityMonitor.checkIfHAError(error)) + ) { + this._deps.alert.warning({ message: messageStoreErrors.unreadFailed }); + } + } + } + + @track(trackEvents.flagVoicemail) + @proxify + async onUnmarkMessages() { + // for track mark message + } + + @track((that: MessageStore, conversationId: Message['conversationId']) => { + const [conversation] = that.conversationStore[conversationId] ?? []; + if (!conversation) return; + if (conversation.type === 'VoiceMail') { + return [trackEvents.deleteVoicemail]; + } + if (conversation.type === 'Fax') { + return [trackEvents.deleteFax]; + } + }) + @proxify + async onDeleteConversation(conversationId: Message['conversationId']) { + // for track delete message + } + + _deleteConversationStore(conversationId: Message['conversationId']) { + if (!this.conversationStore[conversationId]) { + return this.conversationStore; + } + const newState = { ...this.conversationStore }; + delete newState[conversationId]; + return newState; + } + + _deleteConversation(conversationId: Message['conversationId']) { + const conversationList = (this.data?.conversationList ?? []).filter( + (c) => c.id !== conversationId, + ); + this.onDeleteConversation(conversationId); + const conversationStore = this._deleteConversationStore(conversationId); + this._deps.dataFetcherV2.updateData( + this._source, + { + ...this.data, + conversationList, + conversationStore, + }, + this.timestamp, + ); + } + + @proxify + async deleteConversationMessages(conversationId: Message['conversationId']) { + if (!conversationId) { + return; + } + const messageList = this.conversationStore[conversationId]; + if (!messageList || messageList.length === 0) { + return; + } + const messageId = messageList.map((m) => m.id).join(','); + try { + await this.deleteMessageApi(messageId); + this._deleteConversation(conversationId); + } catch (error) { + console.error(error); + + if ( + !this._deps.availabilityMonitor || + !(await this._deps.availabilityMonitor.checkIfHAError(error)) + ) { + this._deps.alert.warning({ message: messageStoreErrors.deleteFailed }); + } + } + } + + @proxify + async deleteConversation(conversationId: Message['conversationId']) { + if (!conversationId) { + return; + } + try { + await this._deps.client.account().extension().messageStore().delete({ + conversationId, + }); + this._deleteConversation(conversationId); + } catch (error) { + console.error(error); + + if ( + !this._deps.availabilityMonitor || + !(await this._deps.availabilityMonitor.checkIfHAError(error)) + ) { + this._deps.alert.warning({ message: messageStoreErrors.deleteFailed }); + } + } + } + + @track(trackEvents.clickToSMSVoicemailList) + @proxify + async onClickToSMS() { + // for track click to sms in message list + } + + @track((_: MessageStore, action: { fromType?: Message['type'] }) => { + if (action.fromType === 'Pager' || action.fromType === 'SMS') { + return [trackEvents.clickToDialTextList]; + } + if (action.fromType === 'VoiceMail') { + return [trackEvents.clickToDialVoicemailList]; + } + }) + @proxify + async onClickToCall({ fromType = '' }) { + // for track click to call in message list + this.onClickToCallWithRingout(); + } + + @track((that: MessageStore) => { + if ( + // TODO: refactor for Analytics + (that.parentModule as any).callingSettings?.callingMode === + callingModes.ringout + ) { + return [trackEvents.callPlaceRingOutCallSMSHistory]; + } + }) + @proxify + async onClickToCallWithRingout() { + // for track click to call with Ringout in message list + } + + get data() { + return this._deps.dataFetcherV2.getData(this._source); + } + + get timestamp() { + return this._deps.dataFetcherV2.getTimestamp(this._source); + } + + get syncInfo() { + return this.data?.syncInfo; + } + + get conversationStore() { + return this.data?.conversationStore; + } + + get _hasPermission() { + return this._deps.rolesAndPermissions.hasReadMessagesPermission; + } + + @computed((that) => [ + that.data?.conversationList, + that.conversationStore, + ]) + get allConversations(): MessageStoreConversations { + const { conversationList = [] } = this.data ?? {}; + return conversationList.map((conversationItem) => { + const messageList = this.conversationStore[conversationItem.id] || []; + return { + ...messageList[0], + unreadCounts: messageList.filter(messageHelper.messageIsUnread).length, + }; + }); + } + + @computed((that) => [that.allConversations]) + get textConversations() { + return this.allConversations.filter((conversation) => + messageHelper.messageIsTextMessage(conversation), + ); + } + + @computed((that) => [that.textConversations]) + get textUnreadCounts() { + return this.textConversations.reduce((a, b) => a + b.unreadCounts, 0); + } + + @computed((that) => [that.allConversations]) + get faxMessages() { + return this.allConversations.filter((conversation) => + messageHelper.messageIsFax(conversation), + ); + } + + @computed((that) => [that.faxMessages]) + get faxUnreadCounts() { + return this.faxMessages.reduce((a, b) => a + b.unreadCounts, 0); + } + + @computed((that) => [that.allConversations]) + get voicemailMessages() { + return this.allConversations.filter((conversation) => + messageHelper.messageIsVoicemail(conversation), + ); + } + + @computed((that) => that.voicemailMessages) + get voiceUnreadCounts() { + return this.voicemailMessages.reduce((a, b) => a + b.unreadCounts, 0); + } + + @computed((that) => [ + that.voiceUnreadCounts, + that.textUnreadCounts, + that.faxUnreadCounts, + ]) + get unreadCounts() { + let unreadCounts = 0; + if (this._deps.rolesAndPermissions.readTextPermissions) { + unreadCounts += this.textUnreadCounts; + } + if (this._deps.rolesAndPermissions.voicemailPermissions) { + unreadCounts += this.voiceUnreadCounts; + } + if (this._deps.rolesAndPermissions.readFaxPermissions) { + unreadCounts += this.faxUnreadCounts; + } + return unreadCounts; + } +} diff --git a/packages/ringcentral-integration/modules/MessageStoreV2/index.ts b/packages/ringcentral-integration/modules/MessageStoreV2/index.ts new file mode 100644 index 0000000000..fbedc5edac --- /dev/null +++ b/packages/ringcentral-integration/modules/MessageStoreV2/index.ts @@ -0,0 +1,3 @@ +export * from './MessageStore'; +export * from './MessageStore.interface'; +export * from './messageStoreErrors'; diff --git a/packages/ringcentral-integration/modules/MessageStoreV2/messageStoreErrors.ts b/packages/ringcentral-integration/modules/MessageStoreV2/messageStoreErrors.ts new file mode 100644 index 0000000000..f88fb160b1 --- /dev/null +++ b/packages/ringcentral-integration/modules/MessageStoreV2/messageStoreErrors.ts @@ -0,0 +1,6 @@ +import { ObjectMap } from '@ringcentral-integration/core/lib/ObjectMap'; + +export const messageStoreErrors = ObjectMap.prefixKeys( + ['deleteFailed', 'readFailed', 'unreadFailed'], + 'messageStore', +); diff --git a/packages/ringcentral-integration/modules/MessageStoreV2/messageStoreHelper.ts b/packages/ringcentral-integration/modules/MessageStoreV2/messageStoreHelper.ts new file mode 100644 index 0000000000..4f49e3c7ac --- /dev/null +++ b/packages/ringcentral-integration/modules/MessageStoreV2/messageStoreHelper.ts @@ -0,0 +1,44 @@ +import { syncTypes } from '../../enums/syncTypes'; +import { SyncFunctionOptions } from './MessageStore.interface'; + +type GetSyncParamsOptions = Pick< + SyncFunctionOptions, + Exclude +>; + +interface SyncParams { + syncType: string; + recordCountPerConversation: GetSyncParamsOptions['conversationLoadLength']; + recordCount?: GetSyncParamsOptions['recordCount']; + dateFrom?: string; + dateTo?: string; +} + +export const getSyncParams = ({ + recordCount, + conversationLoadLength, + dateFrom, + dateTo, + syncToken, +}: GetSyncParamsOptions) => { + if (syncToken) { + return { + syncToken, + syncType: syncTypes.iSync, + }; + } + const params: SyncParams = { + recordCountPerConversation: conversationLoadLength, + syncType: syncTypes.fSync, + }; + if (recordCount) { + params.recordCount = recordCount; + } + if (dateFrom) { + params.dateFrom = dateFrom.toISOString(); + } + if (dateTo) { + params.dateTo = dateTo.toISOString(); + } + return params; +}; diff --git a/packages/ringcentral-integration/modules/RcVideoV2/RcVideo.ts b/packages/ringcentral-integration/modules/RcVideoV2/RcVideo.ts index f5253dd464..94b4ac6b6a 100644 --- a/packages/ringcentral-integration/modules/RcVideoV2/RcVideo.ts +++ b/packages/ringcentral-integration/modules/RcVideoV2/RcVideo.ts @@ -186,11 +186,15 @@ export class RcVideo extends RcModuleV2 implements IMeeting { @track((that: RcVideo, status: string) => { if (status !== videoStatus.creating) return; - // TODO: fix analytics V2 - const target = (that.parentModule as any).analytics?._getTrackTarget(); - if (target) { - return [trackEvents.clickMeetingSchedulePage, { router: target.router }]; - } + return (analytics) => { + const target = analytics.getTrackTarget(); + if (target) { + return [ + trackEvents.clickMeetingSchedulePage, + { router: target.router }, + ]; + } + }; }) @action protected _updateVideoStatus(status: ObjectMapValue) { @@ -818,6 +822,11 @@ export class RcVideo extends RcModuleV2 implements IMeeting { return this._isInstantMeeting; } + @computed((that) => [that._deps.locale.currentLocale]) + get currentLocale() { + return this._deps.locale.currentLocale || DEFAULT_LOCALE; + } + @computed(({ preferences, isInstantMeeting }) => [ preferences, isInstantMeeting, @@ -908,26 +917,36 @@ export class RcVideo extends RcModuleV2 implements IMeeting { }; } - @computed(({ currentUser, extensionName, brandName }) => [ + @computed(({ currentUser, defaultTopic }) => [ currentUser, - extensionName, - brandName, + defaultTopic, ]) get initialVideoSetting(): RcVMeetingModel { const startTime = getInitializedStartTime(); - let extensionName = this.extensionName; - if (this.currentUser?.extensionId !== `${this.extensionId}`) { - extensionName = this.currentUser?.name; - } - const topic = getTopic(extensionName, this.brandName, this._currentLocale); return getDefaultVideoSettings({ - topic, + topic: this.defaultTopic, startTime: new Date(startTime), accountId: this.currentUser.accountId, extensionId: this.currentUser.extensionId, }); } + @computed( + ({ currentUser, extensionName, brandName, currentLocale }) => [ + currentUser, + extensionName, + brandName, + currentLocale, + ], + ) + get defaultTopic(): string { + let extensionName = this.extensionName; + if (this.currentUser?.extensionId !== `${this.extensionId}`) { + extensionName = this.currentUser?.name; + } + return getTopic(extensionName, this.brandName, this.currentLocale); + } + @computed(({ extensionId, accountId }) => [extensionId, accountId]) get loginUser(): RcvDelegator { return { diff --git a/packages/ringcentral-integration/modules/RcVideoV2/i18n/de-DE.js b/packages/ringcentral-integration/modules/RcVideoV2/i18n/de-DE.js new file mode 100644 index 0000000000..220236f728 --- /dev/null +++ b/packages/ringcentral-integration/modules/RcVideoV2/i18n/de-DE.js @@ -0,0 +1,7 @@ +export default { + videoMeeting: "RingCentral Video-Besprechung von {extensionName}", + videoMeetingWithBrand: "Besprechung von {extensionName}{brandName}" +}; + +// @key: @#@"videoMeeting"@#@ @source: @#@"{extensionName}'s RingCentral Video Meeting"@#@ +// @key: @#@"videoMeetingWithBrand"@#@ @source: @#@"{extensionName}'s {brandName} Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/RcVideoV2/i18n/en-GB.js b/packages/ringcentral-integration/modules/RcVideoV2/i18n/en-GB.js new file mode 100644 index 0000000000..cc6407b9c0 --- /dev/null +++ b/packages/ringcentral-integration/modules/RcVideoV2/i18n/en-GB.js @@ -0,0 +1,7 @@ +export default { + videoMeeting: "{extensionName}'s RingCentral Video Meeting", + videoMeetingWithBrand: "{extensionName}'s {brandName} Meeting" +}; + +// @key: @#@"videoMeeting"@#@ @source: @#@"{extensionName}'s RingCentral Video Meeting"@#@ +// @key: @#@"videoMeetingWithBrand"@#@ @source: @#@"{extensionName}'s {brandName} Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/RcVideoV2/i18n/es-419.js b/packages/ringcentral-integration/modules/RcVideoV2/i18n/es-419.js new file mode 100644 index 0000000000..83bc9c8cc6 --- /dev/null +++ b/packages/ringcentral-integration/modules/RcVideoV2/i18n/es-419.js @@ -0,0 +1,7 @@ +export default { + videoMeeting: "Reunión de RingCentral Video de {extensionName}", + videoMeetingWithBrand: "{brandName} Meeting de {extensionName}" +}; + +// @key: @#@"videoMeeting"@#@ @source: @#@"{extensionName}'s RingCentral Video Meeting"@#@ +// @key: @#@"videoMeetingWithBrand"@#@ @source: @#@"{extensionName}'s {brandName} Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/RcVideoV2/i18n/es-ES.js b/packages/ringcentral-integration/modules/RcVideoV2/i18n/es-ES.js new file mode 100644 index 0000000000..d63f59e633 --- /dev/null +++ b/packages/ringcentral-integration/modules/RcVideoV2/i18n/es-ES.js @@ -0,0 +1,7 @@ +export default { + videoMeeting: "Reunión de RingCentral Video de {extensionName}", + videoMeetingWithBrand: "Reunión de {extensionName} de {brandName}" +}; + +// @key: @#@"videoMeeting"@#@ @source: @#@"{extensionName}'s RingCentral Video Meeting"@#@ +// @key: @#@"videoMeetingWithBrand"@#@ @source: @#@"{extensionName}'s {brandName} Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/RcVideoV2/i18n/fr-CA.js b/packages/ringcentral-integration/modules/RcVideoV2/i18n/fr-CA.js new file mode 100644 index 0000000000..672c6836b6 --- /dev/null +++ b/packages/ringcentral-integration/modules/RcVideoV2/i18n/fr-CA.js @@ -0,0 +1,7 @@ +export default { + videoMeeting: "Réunion RingCentral Video de {extensionName}", + videoMeetingWithBrand: "Réunion {brandName} de {extensionName}" +}; + +// @key: @#@"videoMeeting"@#@ @source: @#@"{extensionName}'s RingCentral Video Meeting"@#@ +// @key: @#@"videoMeetingWithBrand"@#@ @source: @#@"{extensionName}'s {brandName} Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/RcVideoV2/i18n/fr-FR.js b/packages/ringcentral-integration/modules/RcVideoV2/i18n/fr-FR.js new file mode 100644 index 0000000000..e878f45b99 --- /dev/null +++ b/packages/ringcentral-integration/modules/RcVideoV2/i18n/fr-FR.js @@ -0,0 +1,7 @@ +export default { + videoMeeting: "Réunion RingCentral Video de {extensionName}", + videoMeetingWithBrand: " Réunion {brandName} de {extensionName} " +}; + +// @key: @#@"videoMeeting"@#@ @source: @#@"{extensionName}'s RingCentral Video Meeting"@#@ +// @key: @#@"videoMeetingWithBrand"@#@ @source: @#@"{extensionName}'s {brandName} Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/RcVideoV2/i18n/it-IT.js b/packages/ringcentral-integration/modules/RcVideoV2/i18n/it-IT.js new file mode 100644 index 0000000000..94dfacf8f7 --- /dev/null +++ b/packages/ringcentral-integration/modules/RcVideoV2/i18n/it-IT.js @@ -0,0 +1,7 @@ +export default { + videoMeeting: "Riunione RingCentral Video di {extensionName}", + videoMeetingWithBrand: "Riunione {brandName} di {extensionName}" +}; + +// @key: @#@"videoMeeting"@#@ @source: @#@"{extensionName}'s RingCentral Video Meeting"@#@ +// @key: @#@"videoMeetingWithBrand"@#@ @source: @#@"{extensionName}'s {brandName} Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/RcVideoV2/i18n/ja-JP.js b/packages/ringcentral-integration/modules/RcVideoV2/i18n/ja-JP.js new file mode 100644 index 0000000000..8f076112c5 --- /dev/null +++ b/packages/ringcentral-integration/modules/RcVideoV2/i18n/ja-JP.js @@ -0,0 +1,7 @@ +export default { + videoMeeting: "{extensionName}のRingCentral Video会議", + videoMeetingWithBrand: "{extensionName}の{brandName}会議" +}; + +// @key: @#@"videoMeeting"@#@ @source: @#@"{extensionName}'s RingCentral Video Meeting"@#@ +// @key: @#@"videoMeetingWithBrand"@#@ @source: @#@"{extensionName}'s {brandName} Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/RcVideoV2/i18n/ko-KR.js b/packages/ringcentral-integration/modules/RcVideoV2/i18n/ko-KR.js new file mode 100644 index 0000000000..776432e85c --- /dev/null +++ b/packages/ringcentral-integration/modules/RcVideoV2/i18n/ko-KR.js @@ -0,0 +1,7 @@ +export default { + videoMeeting: "{extensionName}님의 RingCentral Video 모임", + videoMeetingWithBrand: "{extensionName}님의 {brandName} 모임" +}; + +// @key: @#@"videoMeeting"@#@ @source: @#@"{extensionName}'s RingCentral Video Meeting"@#@ +// @key: @#@"videoMeetingWithBrand"@#@ @source: @#@"{extensionName}'s {brandName} Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/RcVideoV2/i18n/nl-NL.js b/packages/ringcentral-integration/modules/RcVideoV2/i18n/nl-NL.js new file mode 100644 index 0000000000..8437569761 --- /dev/null +++ b/packages/ringcentral-integration/modules/RcVideoV2/i18n/nl-NL.js @@ -0,0 +1,7 @@ +export default { + videoMeeting: "RingCentral Video Meeting van {extensionName}", + videoMeetingWithBrand: "{brandName} Meeting van {extensionName}" +}; + +// @key: @#@"videoMeeting"@#@ @source: @#@"{extensionName}'s RingCentral Video Meeting"@#@ +// @key: @#@"videoMeetingWithBrand"@#@ @source: @#@"{extensionName}'s {brandName} Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/RcVideoV2/i18n/pt-BR.js b/packages/ringcentral-integration/modules/RcVideoV2/i18n/pt-BR.js new file mode 100644 index 0000000000..b0a630986f --- /dev/null +++ b/packages/ringcentral-integration/modules/RcVideoV2/i18n/pt-BR.js @@ -0,0 +1,7 @@ +export default { + videoMeeting: "Reunião de {extensionName} no RingCentral Video", + videoMeetingWithBrand: "Reunião de {extensionName} no {brandName}" +}; + +// @key: @#@"videoMeeting"@#@ @source: @#@"{extensionName}'s RingCentral Video Meeting"@#@ +// @key: @#@"videoMeetingWithBrand"@#@ @source: @#@"{extensionName}'s {brandName} Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/RcVideoV2/i18n/pt-PT.js b/packages/ringcentral-integration/modules/RcVideoV2/i18n/pt-PT.js new file mode 100644 index 0000000000..d95921c315 --- /dev/null +++ b/packages/ringcentral-integration/modules/RcVideoV2/i18n/pt-PT.js @@ -0,0 +1,7 @@ +export default { + videoMeeting: "Reunião do RingCentral Video de {extensionName}", + videoMeetingWithBrand: "Reunião do {brandName} de {extensionName}" +}; + +// @key: @#@"videoMeeting"@#@ @source: @#@"{extensionName}'s RingCentral Video Meeting"@#@ +// @key: @#@"videoMeetingWithBrand"@#@ @source: @#@"{extensionName}'s {brandName} Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/RcVideoV2/i18n/zh-CN.js b/packages/ringcentral-integration/modules/RcVideoV2/i18n/zh-CN.js new file mode 100644 index 0000000000..4e093e12d7 --- /dev/null +++ b/packages/ringcentral-integration/modules/RcVideoV2/i18n/zh-CN.js @@ -0,0 +1,7 @@ +export default { + videoMeeting: "{extensionName} 的 RingCentral Video 会议", + videoMeetingWithBrand: "{extensionName} 的 {brandName} 会议" +}; + +// @key: @#@"videoMeeting"@#@ @source: @#@"{extensionName}'s RingCentral Video Meeting"@#@ +// @key: @#@"videoMeetingWithBrand"@#@ @source: @#@"{extensionName}'s {brandName} Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/RcVideoV2/i18n/zh-TW.js b/packages/ringcentral-integration/modules/RcVideoV2/i18n/zh-TW.js new file mode 100644 index 0000000000..db6cabe9c3 --- /dev/null +++ b/packages/ringcentral-integration/modules/RcVideoV2/i18n/zh-TW.js @@ -0,0 +1,7 @@ +export default { + videoMeeting: "{extensionName} 的 RingCentral Video 會議", + videoMeetingWithBrand: "{extensionName} 的 {brandName} 會議" +}; + +// @key: @#@"videoMeeting"@#@ @source: @#@"{extensionName}'s RingCentral Video Meeting"@#@ +// @key: @#@"videoMeetingWithBrand"@#@ @source: @#@"{extensionName}'s {brandName} Meeting"@#@ diff --git a/packages/ringcentral-integration/modules/RecentMessages/index.js b/packages/ringcentral-integration/modules/RecentMessages/index.js index 1f90e5276e..38d59dc7a5 100644 --- a/packages/ringcentral-integration/modules/RecentMessages/index.js +++ b/packages/ringcentral-integration/modules/RecentMessages/index.js @@ -50,10 +50,8 @@ export default class RecentMessages extends RcModule { }); } else if (Object.keys(this.messages).length > 0) { // Listen to messageStore state changes - if ( - this._messageStore.updatedTimestamp !== this._prevMessageStoreTimestamp - ) { - this._prevMessageStoreTimestamp = this._messageStore.updatedTimestamp; + if (this._messageStore.timestamp !== this._prevMessageStoreTimestamp) { + this._prevMessageStoreTimestamp = this._messageStore.timestamp; // for (const contact of Object.values(this.contacts)) { // this.getMessages(contact, false, true); // } @@ -112,7 +110,7 @@ export default class RecentMessages extends RcModule { ) { return; } - this._prevMessageStoreTimestamp = this._messageStore.updatedTimestamp; + this._prevMessageStoreTimestamp = this._messageStore.timestamp; this.store.dispatch({ type: this.actionTypes.initLoad, }); @@ -251,11 +249,10 @@ export default class RecentMessages extends RcModule { const recentMessagesPromise = phoneNumbers.reduce( (acc, { phoneNumber }) => { if (phoneNumber) { - const promise = this._fetchMessageList( - Object.assign({}, params, { - phoneNumber, - }), - ); + const promise = this._fetchMessageList({ + ...params, + phoneNumber, + }); return acc.concat(promise); } return acc; @@ -272,12 +269,7 @@ export default class RecentMessages extends RcModule { } _fetchMessageList(params) { - return () => - this._client - .account() - .extension() - .messageStore() - .list(params); + return () => this._client.account().extension().messageStore().list(params); } _countUnreadMessages(messages) { diff --git a/packages/ringcentral-integration/modules/RecentMessagesV2/RecentMessages.interface.ts b/packages/ringcentral-integration/modules/RecentMessagesV2/RecentMessages.interface.ts new file mode 100644 index 0000000000..b0836b0afa --- /dev/null +++ b/packages/ringcentral-integration/modules/RecentMessagesV2/RecentMessages.interface.ts @@ -0,0 +1,46 @@ +import { GetMessageInfoResponse } from '@rc-ex/core/definitions'; +import { Entity } from '../../interfaces/Entity.interface'; +import { Message } from '../../interfaces/MessageStore.model'; +import { MessageStore } from '../MessageStoreV2'; + +export interface Deps { + client: any; + messageStore: MessageStore; +} + +export interface GetMessagesOptions { + currentContact: Entity; + sessionId?: string; + fromLocal?: boolean; + forceUpdate?: boolean; +} + +export interface CleanUpMessagesOptions { + contact: Entity; + sessionId?: string; +} + +export interface FetchMessageListOptions { + dateTo: string; + dateFrom: string; + messageType: string[]; + perPage: number; + phoneNumber: string; +} + +export interface LoadSuccessOptions { + contact: Entity; + messages: (Message | RecentMessage)[]; + sessionId: string; +} + +export interface LoadResetOptions { + contact: Entity; + sessionId: string; +} + +export interface RecentMessage extends GetMessageInfoResponse { + fromRemote: boolean; +} + +export type RecentMessages = RecentMessage[]; diff --git a/packages/ringcentral-integration/modules/RecentMessagesV2/RecentMessages.ts b/packages/ringcentral-integration/modules/RecentMessagesV2/RecentMessages.ts new file mode 100644 index 0000000000..9ed5559e1f --- /dev/null +++ b/packages/ringcentral-integration/modules/RecentMessagesV2/RecentMessages.ts @@ -0,0 +1,282 @@ +import { + state, + action, + computed, + watch, + RcModuleV2, +} from '@ringcentral-integration/core'; +import { GetMessageList } from '@rc-ex/core/definitions'; +import { proxify } from '../../lib/proxy/proxify'; +import { Module } from '../../lib/di'; +import { MessageStatus } from './messageStatus'; +import getDateFrom from '../../lib/getDateFrom'; +import concurrentExecute from '../../lib/concurrentExecute'; +import { sortByDate } from '../../lib/messageHelper'; +import { + CleanUpMessagesOptions, + Deps, + FetchMessageListOptions, + GetMessagesOptions, + LoadResetOptions, + LoadSuccessOptions, + RecentMessage, +} from './RecentMessages.interface'; +import { MessageStoreConversations } from '../MessageStoreV2'; +import { Message, Messages } from '../../interfaces/MessageStore.model'; +import { Entity } from '../../interfaces/Entity.interface'; +import { + dedup, + filterPhoneNumber, + flattenToMessageRecords, + markAsRemoteMessage, + sortMessages, +} from './recentMessagesHelper'; + +/** + * Retrieve all recent messages related to a specified contact. + */ +@Module({ + name: 'RecentMessages', + deps: ['Client', 'MessageStore'], +}) +export class RecentMessages extends RcModuleV2 { + constructor(deps: Deps) { + super({ + deps, + }); + } + + onInitOnce() { + watch( + this, + () => this._deps.messageStore.timestamp, + () => { + if (this.ready && Object.keys(this.messages).length > 0) { + for (const key of Object.keys(this.contacts)) { + this.getMessages({ + currentContact: this.contacts[key], + sessionId: key.indexOf('-') > -1 ? key.split('-')[1] : null, + fromLocal: false, + forceUpdate: true, + }); + } + } + }, + ); + } + + @state + contacts: Record = {}; + + @state + messages: Record = {}; + + @state + messageStatus: MessageStatus = null; + + @action + initLoad() { + this.messageStatus = MessageStatus.loading; + } + + @action + loadSuccess({ contact, messages, sessionId }: LoadSuccessOptions) { + this.messageStatus = MessageStatus.loaded; + const contactId = String(contact && contact.id); + const id = sessionId ? `${contactId}-${sessionId}` : contactId; + this.contacts[id] = contact; + this.messages[id] = messages; + } + + @action + loadReset({ contact, sessionId }: LoadResetOptions) { + const contactId = String(contact && contact.id); + const id = sessionId ? `${contactId}-${sessionId}` : contactId; + delete this.contacts[id]; + delete this.messages[id]; + } + + @computed((that) => [that.messages]) + get unreadMessageCounts() { + return Object.keys(this.messages).reduce((unreadCounts, contactId) => { + unreadCounts[contactId] = this.messages[contactId].reduce( + (acc, cur) => acc + (cur.readStatus !== 'Read' ? 1 : 0), + 0, + ); + return unreadCounts; + }, {} as Record); + } + + get isMessagesLoaded() { + return this.messageStatus === MessageStatus.loaded; + } + + @proxify + async getMessages({ + currentContact, + sessionId = null, + fromLocal = false, + forceUpdate = false, + }: GetMessagesOptions) { + // No need to calculate recent messages of the same contact repeatly + if (!currentContact) { + return; + } + const contactId = currentContact.id; + if ( + !forceUpdate && + !!this.messages[sessionId ? `${contactId}-${sessionId}` : contactId] + ) { + return; + } + this.initLoad(); + const messages = await this._getRecentMessages( + currentContact, + this._deps.messageStore.textConversations, + fromLocal, + ); + this.loadSuccess({ + messages, + contact: currentContact, + sessionId, + }); + } + + cleanUpMessages({ contact, sessionId = null }: CleanUpMessagesOptions) { + this.loadReset({ + contact, + sessionId, + }); + } + + /** + * Searching for recent messages of specific contact. + * @param {Object} currentContact - Current contact + * @param {Array} messages - Messages in messageStore + * @param {Boolean} fromLocal - Only get recent messages locally + * @param {Number} daySpan - Find messages within certain days + * @param {Number} length - Maximum length of recent messages + * @return {Array} + * @private + */ + async _getRecentMessages( + currentContact: Entity, + conversations: MessageStoreConversations = [], + fromLocal: boolean, + daySpan = 60, + length = 5, + ) { + const dateFrom = getDateFrom(daySpan); + let recentMessages: ( + | Message + | RecentMessage + )[] = this._getLocalRecentMessages( + currentContact, + conversations, + dateFrom, + length, + ); + + // If we could not find enough recent messages, + // we need to search for messages on server. + if (!fromLocal && recentMessages.length < length) { + const dateTo = + recentMessages.length > 0 + ? new Date(recentMessages[recentMessages.length - 1].creationTime) + : undefined; + + try { + // This will always be sorted + recentMessages = recentMessages.concat( + await this._fetchRemoteRecentMessages( + currentContact, + dateFrom.toISOString(), + dateTo && dateTo.toISOString(), + length, + ), + ); + } catch (error) { + console.error(error); + } + } + + recentMessages = dedup(recentMessages); + return recentMessages.length > length + ? recentMessages.slice(0, length) + : recentMessages; + } + + /** + * Get recent messages from messageStore. + */ + _getLocalRecentMessages( + { phoneNumbers }: Entity, + conversations: MessageStoreConversations, + dateFrom: Date, + length: number, + ) { + // Get all messages related to this contact + let recentMessages: Messages = []; + let matches; + for (let i = conversations.length - 1; i >= 0; i -= 1) { + const conversation = conversations[i]; + const messageList = + this._deps.messageStore.conversationStore[ + conversation.conversationId + ] || []; + matches = phoneNumbers.find(filterPhoneNumber(conversation)); + // Check if message is within certain days + if (!!matches && new Date(conversation.creationTime) > dateFrom) { + recentMessages = recentMessages.concat(messageList); + } + if (recentMessages.length >= length) break; + } + return recentMessages.sort(sortByDate).slice(0, length); + } + + /** + * Fetch recent messages from server by given current contact. + */ + _fetchRemoteRecentMessages( + { phoneNumbers }: Entity, + dateFrom: string, + dateTo = new Date().toISOString(), + length: number, + ) { + const params = { + dateTo, + dateFrom, + messageType: ['SMS', 'Text', 'Pager'], + perPage: length, + }; + const recentMessagesPromise = phoneNumbers.reduce( + (acc, { phoneNumber }) => { + if (phoneNumber) { + const promise = this._fetchMessageList({ + ...params, + phoneNumber, + }); + return acc.concat(promise); + } + return acc; + }, + [] as (() => Promise)[], + ); + + // TODO: Because we need to navigate to the message page, + // So we may need to push new messages to messageStore + return concurrentExecute(recentMessagesPromise, 5, 500) + .then(flattenToMessageRecords) + .then(markAsRemoteMessage) + .then((messages) => sortMessages(messages)); + } + + _fetchMessageList(params: FetchMessageListOptions) { + return () => + this._deps.client + .account() + .extension() + .messageStore() + .list(params) as Promise; + } +} diff --git a/packages/ringcentral-integration/modules/RecentMessagesV2/index.ts b/packages/ringcentral-integration/modules/RecentMessagesV2/index.ts new file mode 100644 index 0000000000..0f2f06503e --- /dev/null +++ b/packages/ringcentral-integration/modules/RecentMessagesV2/index.ts @@ -0,0 +1,4 @@ +export * from './RecentMessages'; +export * from './RecentMessages.interface'; +export * from './messageStatus'; +export * from './recentMessagesHelper'; diff --git a/packages/ringcentral-integration/modules/RecentMessagesV2/messageStatus.ts b/packages/ringcentral-integration/modules/RecentMessagesV2/messageStatus.ts new file mode 100644 index 0000000000..0bac690dc1 --- /dev/null +++ b/packages/ringcentral-integration/modules/RecentMessagesV2/messageStatus.ts @@ -0,0 +1,4 @@ +export const enum MessageStatus { + loading = 'loading', + loaded = 'loaded', +} diff --git a/packages/ringcentral-integration/modules/RecentMessagesV2/recentMessagesHelper.ts b/packages/ringcentral-integration/modules/RecentMessagesV2/recentMessagesHelper.ts new file mode 100644 index 0000000000..74530571ad --- /dev/null +++ b/packages/ringcentral-integration/modules/RecentMessagesV2/recentMessagesHelper.ts @@ -0,0 +1,49 @@ +import { + GetMessageInfoResponse, + GetMessageList, +} from '@rc-ex/core/definitions'; +import { Entity } from '../../interfaces/Entity.interface'; +import { Message } from '../../interfaces/MessageStore.model'; +import { MessageStoreItem } from '../MessageStoreV2'; +import { RecentMessage } from './RecentMessages.interface'; + +export const filterPhoneNumber = (message: MessageStoreItem) => { + return ({ phoneNumber }: Entity['phoneNumbers'][number]) => + phoneNumber === message.from.phoneNumber || + !!message.to.find((to) => to.phoneNumber === phoneNumber) || + phoneNumber === message.from.extensionNumber || + !!message.to.find((to) => to.extensionNumber === phoneNumber); +}; + +export const flattenToMessageRecords = (allMessages: GetMessageList[]) => { + return allMessages.reduce( + (acc, { records }) => acc.concat(records), + [] as GetMessageInfoResponse[], + ); +}; + +export const sortMessages = (recentMessages: RecentMessage[]) => { + // Sort by time in descending order + return recentMessages.sort( + (a, b) => + new Date(b.creationTime).getTime() - new Date(a.creationTime).getTime(), + ); +}; + +export const markAsRemoteMessage = (messages: GetMessageInfoResponse[]) => { + return messages.map((message) => { + return { + ...message, + fromRemote: true, + } as RecentMessage; + }); +}; + +export const dedup = (messages: (Message | RecentMessage)[]) => { + const hash: Record = {}; + return messages.reduce((acc, cur) => { + if (hash[cur.id]) return acc; + hash[cur.id] = true; + return acc.concat(cur); + }, []); +}; diff --git a/packages/ringcentral-integration/modules/RingCentralExtensions/RingCentralExtensions.ts b/packages/ringcentral-integration/modules/RingCentralExtensions/RingCentralExtensions.ts index 6f515a14e0..ee7b91446b 100644 --- a/packages/ringcentral-integration/modules/RingCentralExtensions/RingCentralExtensions.ts +++ b/packages/ringcentral-integration/modules/RingCentralExtensions/RingCentralExtensions.ts @@ -11,7 +11,7 @@ import { watch, } from '@ringcentral-integration/core'; import WebSocket from 'isomorphic-ws'; -import proxify from '../../lib/proxy/proxify'; +import { proxify } from '../../lib/proxy/proxify'; import { Module } from '../../lib/di'; import { WebSocketReadyState, diff --git a/packages/ringcentral-integration/modules/StorageV2/Storage.ts b/packages/ringcentral-integration/modules/StorageV2/Storage.ts index 90caefdc82..98b95b0f74 100644 --- a/packages/ringcentral-integration/modules/StorageV2/Storage.ts +++ b/packages/ringcentral-integration/modules/StorageV2/Storage.ts @@ -12,7 +12,7 @@ import { Deps } from './Storage.interface'; { dep: 'StorageOptions', optional: true }, ], }) -export class Storage extends StorageBase { +export class Storage extends StorageBase { /* migration storage v1 to v2 */ public migrationMapping: Record> = {}; /* migration storage v1 to v2 */ @@ -105,24 +105,7 @@ export class Storage extends StorageBase { this._deps.auth.notLoggedIn) && this.ready ) { - this.store.dispatch({ - type: this._storageActionTypes.reset, - }); - if (this._storageHandler) { - if (this._storage.off) { - this._storage.off('storage', this._storageHandler); - } else if (this._storage.removeListener) { - this._storage.removeListener('storage', this._storageHandler); - } - this._storageHandler = null; - } - if (this._storage) { - this._storage.destroy(); - this._storage = null; - } - this.store.dispatch({ - type: this._storageActionTypes.resetSuccess, - }); + this.resetStorage(); } if ( this.status === moduleStatuses.ready && @@ -141,4 +124,25 @@ export class Storage extends StorageBase { } }); } + + public resetStorage() { + this.store.dispatch({ + type: this._storageActionTypes.reset, + }); + if (this._storageHandler) { + if (this._storage.off) { + this._storage.off('storage', this._storageHandler); + } else if (this._storage.removeListener) { + this._storage.removeListener('storage', this._storageHandler); + } + this._storageHandler = null; + } + if (this._storage) { + this._storage.destroy(); + this._storage = null; + } + this.store.dispatch({ + type: this._storageActionTypes.resetSuccess, + }); + } } diff --git a/packages/ringcentral-integration/modules/WebSocketSubscription/WebSocketSubscription.ts b/packages/ringcentral-integration/modules/WebSocketSubscription/WebSocketSubscription.ts index 4046d2d8f9..a7cf3b0e6a 100644 --- a/packages/ringcentral-integration/modules/WebSocketSubscription/WebSocketSubscription.ts +++ b/packages/ringcentral-integration/modules/WebSocketSubscription/WebSocketSubscription.ts @@ -6,7 +6,7 @@ import { watch, } from '@ringcentral-integration/core'; import { Module } from '../../lib/di'; -import proxify from '../../lib/proxy/proxify'; +import { proxify } from '../../lib/proxy/proxify'; import { debounce } from '../../lib/debounce-throttle'; import { SubscriptionFilter } from '../../enums/subscriptionFilters'; import { webSocketReadyStates } from '../RingCentralExtensions/webSocketReadyStates'; @@ -67,6 +67,9 @@ export class WebSocketSubscription extends RcModuleV2 { } private async _createSubscription() { + if (!this._deps.ringCentralExtensions.webSocketExtension.ws) { + return; + } this._wsSubscription = await this._deps.ringCentralExtensions.webSocketExtension.subscribe( this.filters, (message) => { diff --git a/packages/ringcentral-integration/modules/Webphone/index.js b/packages/ringcentral-integration/modules/Webphone/index.js index 873724113c..cc20fa1901 100644 --- a/packages/ringcentral-integration/modules/Webphone/index.js +++ b/packages/ringcentral-integration/modules/Webphone/index.js @@ -753,6 +753,7 @@ export default class Webphone extends RcModule { // Pause video elements to release system Video Wake Lock RCINT-15582 if (!this._remoteVideo.paused) { this._remoteVideo.pause(); + this._remoteVideo.srcObject = null; } if (!this._localVideo.paused) { this._localVideo.pause(); @@ -1218,16 +1219,25 @@ export default class Webphone extends RcModule { await session.forward(validPhoneNumber, this.acceptOptions); console.log('Forwarded'); this._onCallEnd(session); + this._addTrackAfterForward(); return true; } catch (e) { console.error(e); this._alert.warning({ message: webphoneErrors.forwardError, }); + this._addTrackAfterForward(); return false; } } + _addTrackAfterForward() { + if (this.activeSession && !this.activeSession.isOnHold) { + const rawActiveSession = this._sessions.get(this.activeSession.id); + this._addTrack(rawActiveSession); + } + } + @proxify async mute(sessionId) { try { @@ -1463,28 +1473,63 @@ export default class Webphone extends RcModule { } @proxify - async transferWarm(transferNumber, sessionId) { + async startWarmTransfer(transferNumber, sessionId) { const session = this._sessions.get(sessionId); if (!session) { return; } try { - await session.hold(); - const newSession = session.ua.invite(transferNumber, { - sessionDescriptionHandlerOptions: this.acceptOptions - .sessionDescriptionHandlerOptions, + session.__rc_isOnTransfer = true; + this._updateSessions(); + const numberResult = validateNumbers( + [transferNumber], + this._regionSettings, + this._brand.id, + ); + const validPhoneNumber = numberResult && numberResult[0]; + const fromNumber = + session.__rc_direction === callDirections.outbound + ? session.request.from.uri.user + : session.request.to.uri.user; + await this.makeCall({ + toNumber: validPhoneNumber, + fromNumber, + homeCountryId: this._regionSettings.homeCountryId, + extendedControls: '', + transferSessionId: sessionId, }); - newSession.once('accepted', async () => { - try { - await session.warmTransfer(newSession); - console.log('Transferred'); - this._onCallEnd(session); - } catch (e) { - console.error(e); - } + } catch (e) { + console.error(e); + session.__rc_isOnTransfer = false; + this._updateSessions(); + this._alert.danger({ + message: webphoneErrors.transferError, }); + } + } + + @proxify + async completeWarmTransfer(newSessionId) { + const newSession = this._sessions.get(newSessionId); + if (!newSession) { + return; + } + const oldSessionId = newSession.__rc_transferSessionId; + const oldSession = this._sessions.get(oldSessionId); + if (!oldSession) { + return; + } + newSession.__rc_isOnTransfer = true; + this._updateSessions(); + try { + await oldSession.warmTransfer(newSession); } catch (e) { console.error(e); + newSession.__rc_isOnTransfer = false; + this._updateSessions(); + this._alert.danger({ + message: webphoneErrors.transferError, + }); } } @@ -1574,6 +1619,12 @@ export default class Webphone extends RcModule { } } + _addTrack(rawSession) { + if (rawSession) { + rawSession.addTrack(this._remoteVideo, this._localVideo); + } + } + _sessionHandleWithId(sessionId, func) { const session = this._sessions.get(sessionId); if (!session) { @@ -1582,7 +1633,10 @@ export default class Webphone extends RcModule { return func(session); } - async _invite(toNumber, { inviteOptions, extendedControls }) { + async _invite( + toNumber, + { inviteOptions, extendedControls, transferSessionId }, + ) { if (!this._webphone) { this._alert.warning({ message: this.errorCode, @@ -1612,6 +1666,7 @@ export default class Webphone extends RcModule { session.__rc_fromNumber = inviteOptions.fromNumber; session.__rc_extendedControls = extendedControls; session.__rc_extendedControlStatus = extendedControlStatus.pending; + session.__rc_transferSessionId = transferSessionId; this._onAccepted(session); this._onCallInit(session); return session; @@ -1624,7 +1679,13 @@ export default class Webphone extends RcModule { * @param {homeCountryId} homeCountry Id */ @proxify - async makeCall({ toNumber, fromNumber, homeCountryId, extendedControls }) { + async makeCall({ + toNumber, + fromNumber, + homeCountryId, + extendedControls, + transferSessionId, + }) { const inviteOptions = { sessionDescriptionHandlerOptions: this.acceptOptions .sessionDescriptionHandlerOptions, @@ -1634,6 +1695,7 @@ export default class Webphone extends RcModule { const result = await this._invite(toNumber, { inviteOptions, extendedControls, + transferSessionId, }); return result; } @@ -1801,6 +1863,14 @@ export default class Webphone extends RcModule { if (!normalizedSession) { return; } + if (session.__rc_transferSessionId) { + const transferSession = this._sessions.get( + session.__rc_transferSessionId, + ); + if (transferSession) { + transferSession.__rc_isOnTransfer = false; + } + } this._removeSession(session); this.store.dispatch({ type: this.actionTypes.callEnd, diff --git a/packages/ringcentral-integration/modules/Webphone/webphoneHelper.js b/packages/ringcentral-integration/modules/Webphone/webphoneHelper.js index 2f32b0eb3a..359c53ba61 100644 --- a/packages/ringcentral-integration/modules/Webphone/webphoneHelper.js +++ b/packages/ringcentral-integration/modules/Webphone/webphoneHelper.js @@ -143,6 +143,7 @@ export function normalizeSession(session) { toUserName: session.request.to.displayName, fromUserName: session.request.from.displayName, }), + warmTransferSessionId: session.__rc_transferSessionId, }; } diff --git a/packages/ringcentral-integration/modules/WebphoneV2/Webphone.ts b/packages/ringcentral-integration/modules/WebphoneV2/Webphone.ts index 5b4cc8b26b..5dc60d1ed0 100644 --- a/packages/ringcentral-integration/modules/WebphoneV2/Webphone.ts +++ b/packages/ringcentral-integration/modules/WebphoneV2/Webphone.ts @@ -1,46 +1,46 @@ -import { find, filter } from 'ramda'; -import { state, action, computed, track } from '@ringcentral-integration/core'; +import { action, computed, state, track } from '@ringcentral-integration/core'; import { ObjectMapKey } from '@ringcentral-integration/core/lib/ObjectMap'; +import { filter, find } from 'ramda'; import { InviteOptions } from 'ringcentral-web-phone/lib/userAgent'; +import callDirections from '../../enums/callDirections'; +import { extendedControlStatus } from '../../enums/extendedControlStatus'; +import { + NormalizedSession, + WebphoneSession, +} from '../../interfaces/Webphone.interface'; import { Module } from '../../lib/di'; +import { proxify } from '../../lib/proxy/proxify'; import sleep from '../../lib/sleep'; -import { sessionStatus } from './sessionStatus'; -import { recordStatus } from './recordStatus'; -import callDirections from '../../enums/callDirections'; -import { webphoneErrors } from './webphoneErrors'; -import { webphoneMessages } from './webphoneMessages'; -import { EVENTS } from './events'; -import { callErrors } from '../CallV2/callErrors'; -import proxify from '../../lib/proxy/proxify'; import validateNumbers from '../../lib/validateNumbers'; +import { trackEvents } from '../Analytics'; +import { callErrors } from '../CallV2/callErrors'; +import { EVENTS } from './events'; +import { recordStatus } from './recordStatus'; +import { sessionStatus } from './sessionStatus'; import { - Deps, - SwitchCallActiveCallParams, - SessionReplyOptions, - CallStartHandler, - CallEndHandler, - CallResumeHandler, - CallRingHandler, - CallInitHandler, BeforeCallEndHandler, BeforeCallResumeHandler, + CallEndHandler, CallHoldHandler, + CallInitHandler, + CallResumeHandler, + CallRingHandler, + CallStartHandler, + Deps, OffEventHandler, + SessionReplyOptions, + SwitchCallActiveCallParams, } from './Webphone.interface'; import { WebphoneBase } from './WebphoneBase'; +import { webphoneErrors } from './webphoneErrors'; import { - normalizeSession, - isRing, - isOnHold, extractHeadersData, + isOnHold, + isRing, + normalizeSession, sortByLastActiveTimeDesc, } from './webphoneHelper'; -import { trackEvents } from '../Analytics'; -import { extendedControlStatus } from '../../enums/extendedControlStatus'; -import { - NormalizedSession, - WebphoneSession, -} from '../../interfaces/Webphone.interface'; +import { webphoneMessages } from './webphoneMessages'; export const INCOMING_CALL_INVALID_STATE_ERROR_CODE = 2; @@ -421,16 +421,25 @@ export class Webphone extends WebphoneBase { await session.forward(validPhoneNumber, this.acceptOptions, {}); console.log('Forwarded'); this._onCallEnd(session); + this._addTrackAfterForward(); return true; } catch (e) { console.error(e); this._deps.alert.warning({ message: webphoneErrors.forwardError, }); + this._addTrackAfterForward(); return false; } } + _addTrackAfterForward() { + if (this.activeSession && !this.activeSession.isOnHold) { + const rawActiveSession = this.originalSessions[this.activeSession.id]; + this._addTrack(rawActiveSession); + } + } + @proxify async mute(sessionId: string) { try { @@ -667,28 +676,63 @@ export class Webphone extends WebphoneBase { } @proxify - async transferWarm(transferNumber: string, sessionId: string) { + async startWarmTransfer(transferNumber: string, sessionId: string) { const session = this.originalSessions[sessionId]; if (!session) { return; } try { - await session.hold(); - const newSession = session.ua.invite(transferNumber, { - sessionDescriptionHandlerOptions: this.acceptOptions - .sessionDescriptionHandlerOptions, + session.__rc_isOnTransfer = true; + this._updateSessions(); + const numberResult = validateNumbers( + [transferNumber], + this._deps.regionSettings, + this._deps.brand.id, + ); + const validPhoneNumber = numberResult && numberResult[0]; + const fromNumber = + session.__rc_direction === callDirections.outbound + ? session.request.from.uri.user + : session.request.to.uri.user; + await this.makeCall({ + toNumber: validPhoneNumber, + fromNumber, + homeCountryId: this._deps.regionSettings.homeCountryId, + extendedControls: '', + transferSessionId: sessionId, }); - newSession.once('accepted', async () => { - try { - await session.warmTransfer(newSession); - console.log('Transferred'); - this._onCallEnd(session); - } catch (e) { - console.error(e); - } + } catch (e) { + console.error(e); + session.__rc_isOnTransfer = false; + this._updateSessions(); + this._deps.alert.danger({ + message: webphoneErrors.transferError, }); + } + } + + @proxify + async completeWarmTransfer(newSessionId: string) { + const newSession = this.originalSessions[newSessionId]; + if (!newSession) { + return; + } + const oldSessionId = newSession.__rc_transferSessionId; + const oldSession = this.originalSessions[oldSessionId]; + if (!oldSession) { + return; + } + newSession.__rc_isOnTransfer = true; + this._updateSessions(); + try { + await oldSession.warmTransfer(newSession); } catch (e) { console.error(e); + newSession.__rc_isOnTransfer = false; + this._updateSessions(); + this._deps.alert.danger({ + message: webphoneErrors.transferError, + }); } } @@ -778,6 +822,12 @@ export class Webphone extends WebphoneBase { } } + _addTrack(rawSession: WebphoneSession) { + if (rawSession) { + rawSession.addTrack(this._remoteVideo, this._localVideo); + } + } + _sessionHandleWithId( sessionId: string, func: (session: WebphoneSession) => void, @@ -794,9 +844,11 @@ export class Webphone extends WebphoneBase { { inviteOptions, extendedControls, + transferSessionId, }: { inviteOptions: InviteOptions; extendedControls?: string; + transferSessionId?: string; }, ) { if (!this._webphone) { @@ -831,6 +883,7 @@ export class Webphone extends WebphoneBase { session.__rc_fromNumber = inviteOptions.fromNumber; session.__rc_extendedControls = extendedControls; session.__rc_extendedControlStatus = extendedControlStatus.pending; + session.__rc_transferSessionId = transferSessionId; this._onAccepted(session); this._onCallInit(session); return session; @@ -848,11 +901,13 @@ export class Webphone extends WebphoneBase { fromNumber, homeCountryId, extendedControls, + transferSessionId, }: { toNumber: string; fromNumber: string; homeCountryId: string; extendedControls: string; + transferSessionId?: string; }) { const inviteOptions = { sessionDescriptionHandlerOptions: this.acceptOptions @@ -863,6 +918,7 @@ export class Webphone extends WebphoneBase { const result = await this._invite(toNumber, { inviteOptions, extendedControls, + transferSessionId, }); return result; } @@ -1011,6 +1067,7 @@ export class Webphone extends WebphoneBase { // Pause video elements to release system Video Wake Lock RCINT-15582 if (!this._remoteVideo.paused) { this._remoteVideo.pause(); + this._remoteVideo.srcObject = null; } if (!this._localVideo.paused) { this._localVideo.pause(); @@ -1037,6 +1094,14 @@ export class Webphone extends WebphoneBase { if (!normalizedSession) { return; } + if (session.__rc_transferSessionId) { + const transferSession = this.originalSessions[ + session.__rc_transferSessionId + ]; + if (transferSession) { + transferSession.__rc_isOnTransfer = false; + } + } this._updateSessions(); this._setStateOnCallEnd(normalizedSession); this._eventEmitter.emit( diff --git a/packages/ringcentral-integration/modules/WebphoneV2/webphoneHelper.ts b/packages/ringcentral-integration/modules/WebphoneV2/webphoneHelper.ts index aa7cb579f5..6159769625 100644 --- a/packages/ringcentral-integration/modules/WebphoneV2/webphoneHelper.ts +++ b/packages/ringcentral-integration/modules/WebphoneV2/webphoneHelper.ts @@ -160,6 +160,7 @@ export function normalizeSession(session: WebphoneSession): NormalizedSession { toUserName: session.request.to.displayName, fromUserName: session.request.from.displayName, }), + warmTransferSessionId: session.__rc_transferSessionId, }; } diff --git a/packages/ringcentral-integration/package.json b/packages/ringcentral-integration/package.json index 77d0fa1130..ef687507d4 100644 --- a/packages/ringcentral-integration/package.json +++ b/packages/ringcentral-integration/package.json @@ -38,6 +38,7 @@ "@ringcentral-integration/locale-settings": "*", "@ringcentral-integration/phone-number": "*", "@ringcentral-integration/test-utils": "*", + "@ringcentral/juno": "^1.4.1", "@ringcentral/sdk": "^4.3.2", "@ringcentral/subscriptions": "^4.3.2", "babel-istanbul": "^0.12.1", @@ -97,6 +98,7 @@ "@sentry/browser": "^5.11.1", "bowser": "^2.5.3", "camelcase": "^6.0.0", + "data-transport": "^1.2.0", "file-loader": "^3.0.1", "json-mask": "^0.3.8", "localforage": "^1.7.3", @@ -106,7 +108,7 @@ "ramda": "^0.27.0", "redux": "^4.0.5", "reselect": "^2.5.4", - "ringcentral-call": "^0.2.11", + "ringcentral-call": "^0.2.12", "ringcentral-call-control": "^0.2.5", "ringcentral-web-phone": "^0.8.3", "url-loader": "^1.1.2", diff --git a/packages/ringcentral-widgets-cli/templates/Project/package-template.json b/packages/ringcentral-widgets-cli/templates/Project/package-template.json index f3a1bef8f6..7ce5294d52 100644 --- a/packages/ringcentral-widgets-cli/templates/Project/package-template.json +++ b/packages/ringcentral-widgets-cli/templates/Project/package-template.json @@ -36,7 +36,7 @@ "@ringcentral-integration/core": "^0.12.0", "@ringcentral-integration/i18n": "^2.0.1", "@ringcentral-integration/phone-number": "^1.0.2", - "@ringcentral/juno": "^1.3.2-beta.3102-608720c9", + "@ringcentral/juno": "^1.4.1", "@ringcentral/sdk": "^4.3.2", "@ringcentral/subscriptions": "^4.3.2", "classnames": "^2.2.5", diff --git a/packages/ringcentral-widgets-demo/dev-server/Phone.js b/packages/ringcentral-widgets-demo/dev-server/Phone.js index 647d089cc8..669edaa543 100644 --- a/packages/ringcentral-widgets-demo/dev-server/Phone.js +++ b/packages/ringcentral-widgets-demo/dev-server/Phone.js @@ -33,22 +33,23 @@ import { Storage } from 'ringcentral-integration/modules/StorageV2'; import { Subscription } from 'ringcentral-integration/modules/SubscriptionV2'; import { TabManager } from 'ringcentral-integration/modules/TabManagerV2'; import { NumberValidate } from 'ringcentral-integration/modules/NumberValidateV2'; -import MessageStore from 'ringcentral-integration/modules/MessageStore'; +import { MessageStore } from 'ringcentral-integration/modules/MessageStoreV2'; import { ContactSearch } from 'ringcentral-integration/modules/ContactSearchV2'; import { DateTimeFormat } from 'ringcentral-integration/modules/DateTimeFormatV2'; import Conference from 'ringcentral-integration/modules/Conference'; +// import { ConferenceCall } from 'ringcentral-integration/modules/ConferenceCallV2'; import ConferenceCall from 'ringcentral-integration/modules/ConferenceCall'; import { QuickAccess } from 'ringcentral-integration/modules/QuickAccessV2'; import { CallLog } from 'ringcentral-integration/modules/CallLogV2'; import { CallMonitor } from 'ringcentral-integration/modules/CallMonitorV2'; import { CallHistory } from 'ringcentral-integration/modules/CallHistoryV2'; -import RecentMessages from 'ringcentral-integration/modules/RecentMessages'; +import { RecentMessages } from 'ringcentral-integration/modules/RecentMessagesV2'; import { RecentCalls } from 'ringcentral-integration/modules/RecentCallsV2'; import { AudioSettings } from 'ringcentral-integration/modules/AudioSettingsV2'; import Meeting from 'ringcentral-integration/modules/Meeting'; import { LocaleSettings } from 'ringcentral-integration/modules/LocaleSettingsV2'; import { ContactMatcher } from 'ringcentral-integration/modules/ContactMatcherV2'; -import { Analytics } from 'ringcentral-integration/modules/Analytics'; +import { Analytics } from 'ringcentral-integration/modules/AnalyticsV2'; import { Feedback } from 'ringcentral-integration/modules/FeedbackV2'; import { UserGuide } from 'ringcentral-integration/modules/UserGuideV2'; import { SleepDetector } from 'ringcentral-integration/modules/SleepDetectorV2'; @@ -245,7 +246,6 @@ const history = conversationsLoadLength: 10, conversationLoadLength: 15, }, - spread: true, }, { provide: 'ConversationsOptions', @@ -299,9 +299,10 @@ const history = { provide: 'AnalyticsOptions', useValue: { + analyticsKey: '', + appVersion: '', useLog: true, }, - spread: true, }, { provide: 'AuthOptions', useValue: { usePKCE: true } }, ], diff --git a/packages/ringcentral-widgets-demo/dev-server/containers/App/index.js b/packages/ringcentral-widgets-demo/dev-server/containers/App/index.js index bf7e269a2c..1be9722eec 100644 --- a/packages/ringcentral-widgets-demo/dev-server/containers/App/index.js +++ b/packages/ringcentral-widgets-demo/dev-server/containers/App/index.js @@ -186,7 +186,10 @@ const App = ({ phone, icon }) => { ( - + )} /> { }, ); }); + +describe('stripMeetingLinks', () => { + describe.each` + text | result + ${'https://meetings.ringcentral.com/j/1491234567,http://meetings.btcloudphone.bt.com/j/1481234567'} | ${','} + ${'Please join https://v.ringcentral.com/join/148123456'} | ${'Please join '} + ${'Please join https://amrupams-shr-1-v.lab.nordigy.ru/join/823808420 post'} | ${'Please join post'} + `( + 'Links of $text should be stripped -> $result', + ({ text, result }: { text: string; result: string }) => { + test(text, () => { + expect(stripMeetingLinks(text)).toEqual(result); + }); + }, + ); +}); diff --git a/packages/ringcentral-widgets/components/ActionMenuList/index.js b/packages/ringcentral-widgets/components/ActionMenuList/index.js index ef5e8c7311..5cdda4e3f9 100644 --- a/packages/ringcentral-widgets/components/ActionMenuList/index.js +++ b/packages/ringcentral-widgets/components/ActionMenuList/index.js @@ -453,7 +453,7 @@ export default class ActionMenuList extends Component { onClickToDial={onClickToDial} phoneNumber={phoneNumber} disableLinks={disableLinks} - disableCallButton={disableCallButton} + disableCallButton={disableLinks || disableCallButton} disableClickToDial={disableClickToDial} currentLocale={currentLocale} title={callTitle} diff --git a/packages/ringcentral-widgets/components/ActiveCallItem/styles.scss b/packages/ringcentral-widgets/components/ActiveCallItem/styles.scss index a897e2f44d..642963d8ba 100644 --- a/packages/ringcentral-widgets/components/ActiveCallItem/styles.scss +++ b/packages/ringcentral-widgets/components/ActiveCallItem/styles.scss @@ -39,6 +39,8 @@ $fixed-icon-width: 21px; padding-right: 8px; box-sizing: border-box; // Add bellow style if you have calllog button on callitem // max-width: calc(100% - 23px - 21px); + overflow: hidden; + text-overflow: ellipsis; } .activeCall { diff --git a/packages/ringcentral-widgets/components/ActiveCallItemV2/ActiveCallItem.tsx b/packages/ringcentral-widgets/components/ActiveCallItemV2/ActiveCallItem.tsx index b76ce24f36..cd9be13ed5 100644 --- a/packages/ringcentral-widgets/components/ActiveCallItemV2/ActiveCallItem.tsx +++ b/packages/ringcentral-widgets/components/ActiveCallItemV2/ActiveCallItem.tsx @@ -104,6 +104,7 @@ const WebphoneButtons: FunctionComponent = ({ ) : ( { e.stopPropagation(); webphoneAnswer(session.id, telephonySessionId, true); @@ -593,7 +594,7 @@ export class ActiveCallItem extends Component< ); if (useCallDetailV2) { return ( -
    +
    ); diff --git a/packages/ringcentral-widgets/components/ActiveCallList/index.js b/packages/ringcentral-widgets/components/ActiveCallList/index.js index 58a887081d..f3ed0ebbcf 100644 --- a/packages/ringcentral-widgets/components/ActiveCallList/index.js +++ b/packages/ringcentral-widgets/components/ActiveCallList/index.js @@ -72,6 +72,7 @@ const ActiveCallList = ({ useCallDetailV2, newCallIcon, clickSwitchTrack, + showMultipleMatch, }) => { if (!calls.length) { return null; @@ -143,7 +144,7 @@ const ActiveCallList = ({ ringoutReject={ringoutReject} disableLinks={disableLinks} showRingoutCallControl={showRingoutCallControl} - showMultipleMatch={!showRingoutCallControl} // disabled for salesforce + showMultipleMatch={!showRingoutCallControl && showMultipleMatch} // disabled for salesforce showSwitchCall={showSwitchCall} showTransferCall={showTransferCall} showHoldOnOtherDevice={showHoldOnOtherDevice} @@ -209,6 +210,7 @@ ActiveCallList.propTypes = { ringoutReject: PropTypes.func, disableLinks: PropTypes.bool, showRingoutCallControl: PropTypes.bool, + showMultipleMatch: PropTypes.bool, showSwitchCall: PropTypes.bool, showTransferCall: PropTypes.bool, showHoldOnOtherDevice: PropTypes.bool, @@ -262,6 +264,7 @@ ActiveCallList.defaultProps = { ringoutReject: undefined, disableLinks: false, showRingoutCallControl: false, + showMultipleMatch: true, showSwitchCall: false, showTransferCall: true, showHoldOnOtherDevice: false, diff --git a/packages/ringcentral-widgets/components/ActiveCallPad/i18n/en-US.js b/packages/ringcentral-widgets/components/ActiveCallPad/i18n/en-US.js index 3a3655d107..3c6a49395c 100644 --- a/packages/ringcentral-widgets/components/ActiveCallPad/i18n/en-US.js +++ b/packages/ringcentral-widgets/components/ActiveCallPad/i18n/en-US.js @@ -12,4 +12,6 @@ export default { flip: 'Flip', more: 'Call Actions', mergeToConference: 'Merge', + end: 'End', + completeTransfer: 'Complete\nTransfer' }; diff --git a/packages/ringcentral-widgets/components/ActiveCallPad/index.js b/packages/ringcentral-widgets/components/ActiveCallPad/index.js index f3445dc2f7..898ff638ee 100644 --- a/packages/ringcentral-widgets/components/ActiveCallPad/index.js +++ b/packages/ringcentral-widgets/components/ActiveCallPad/index.js @@ -1,7 +1,7 @@ import React, { Component, createRef } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import recordStatus from 'ringcentral-integration/modules/Webphone/recordStatus'; +import { recordStatus as recordStatuses } from 'ringcentral-integration/modules/Webphone/recordStatus'; import { isObject } from 'ringcentral-integration/lib/di/utils/is_type'; import CircleButton from '../CircleButton'; @@ -39,6 +39,7 @@ export const ACTIONS_CTRL_MAP = { transferCtrl: 'transferCtrl', flipCtrl: 'flipCtrl', parkCtrl: 'parkCtrl', + completeTransferCtrl: 'completeTransferCtrl', }; class ActiveCallPad extends Component { @@ -116,27 +117,58 @@ class ActiveCallPad extends Component { } render() { - const { controlBusy } = this.props; + const { + controlBusy, + actions, + currentLocale, + isOnWaitingTransfer, + onHangup, + onCompleteTransfer, + conferenceCallEquipped, + isOnMute, + isOnHold, + onUnmute, + onMute, + onShowKeyPad, + layout, + onUnhold, + onHold, + hasConferenceCall, + mergeDisabled, + onMerge, + addDisabled, + onAdd, + recordStatus, + onStopRecord, + onRecord, + onTransfer, + disableFlip, + onFlip, + showPark, + onPark, + className, + isOnTransfer, + } = this.props; let buttons = []; /* --------------------- Mute/Unmute --------------------------- */ buttons.push( - this.props.isOnMute + isOnMute ? { icon: MuteIcon, id: ACTIONS_CTRL_MAP.muteCtrl, dataSign: 'mute', - title: i18n.getString('unmute', this.props.currentLocale), - disabled: this.props.isOnHold || controlBusy, - onClick: this.props.onUnmute, + title: i18n.getString('unmute', currentLocale), + disabled: isOnHold || controlBusy, + onClick: onUnmute, } : { icon: UnmuteIcon, id: ACTIONS_CTRL_MAP.muteCtrl, dataSign: 'unmute', - title: i18n.getString('mute', this.props.currentLocale), - disabled: this.props.isOnHold || controlBusy, - onClick: this.props.onMute, + title: i18n.getString('mute', currentLocale), + disabled: isOnHold || controlBusy, + onClick: onMute, }, ); @@ -145,9 +177,9 @@ class ActiveCallPad extends Component { icon: KeypadIcon, id: ACTIONS_CTRL_MAP.keypadCtrl, dataSign: 'keypad', - title: i18n.getString('keypad', this.props.currentLocale), - onClick: this.props.onShowKeyPad, - disabled: this.props.layout === callCtrlLayouts.conferenceCtrl, + title: i18n.getString('keypad', currentLocale), + onClick: onShowKeyPad, + disabled: layout === callCtrlLayouts.conferenceCtrl, }); /* --------------------- Hold/Unhold --------------------------- */ @@ -158,42 +190,49 @@ class ActiveCallPad extends Component { iconHeight: 160, iconX: 190, iconY: 165, - dataSign: this.props.isOnHold ? 'onHold' : 'hold', - title: this.props.isOnHold - ? i18n.getString('onHold', this.props.currentLocale) - : i18n.getString('hold', this.props.currentLocale), - active: this.props.isOnHold, - onClick: this.props.isOnHold ? this.props.onUnhold : this.props.onHold, + dataSign: isOnHold ? 'onHold' : 'hold', + title: isOnHold + ? i18n.getString('onHold', currentLocale) + : i18n.getString('hold', currentLocale), + active: isOnHold, + onClick: isOnHold ? onUnhold : onHold, disabled: controlBusy, }); + if (isOnWaitingTransfer) { + buttons.push({ + icon: TransferIcon, + id: ACTIONS_CTRL_MAP.completeTransferCtrl, + dataSign: 'completeTransfer', + title: i18n.getString('completeTransfer', currentLocale), + disabled: isOnTransfer || controlBusy, + onClick: onCompleteTransfer, + showRipple: true, + }); + } /* --------------------- Add/Merge --------------------------- */ - if (this.props.conferenceCallEquipped) { + if (!isOnWaitingTransfer && conferenceCallEquipped) { const showMerge = - this.props.layout === callCtrlLayouts.mergeCtrl || - (this.props.layout === callCtrlLayouts.normalCtrl && - this.props.hasConferenceCall); + layout === callCtrlLayouts.mergeCtrl || + (layout === callCtrlLayouts.normalCtrl && hasConferenceCall); buttons.push( showMerge ? { icon: MergeIcon, id: ACTIONS_CTRL_MAP.mergeOrAddCtrl, dataSign: 'merge', - title: i18n.getString( - 'mergeToConference', - this.props.currentLocale, - ), - disabled: this.props.mergeDisabled || controlBusy, - onClick: this.props.onMerge, - showRipple: !this.props.mergeDisabled, + title: i18n.getString('mergeToConference', currentLocale), + disabled: mergeDisabled || controlBusy, + onClick: onMerge, + showRipple: !mergeDisabled, } : { icon: CombineIcon, id: ACTIONS_CTRL_MAP.mergeOrAddCtrl, dataSign: 'add', - title: i18n.getString('add', this.props.currentLocale), - disabled: this.props.addDisabled || controlBusy, - onClick: this.props.onAdd, + title: i18n.getString('add', currentLocale), + disabled: addDisabled || controlBusy, + onClick: onAdd, }, ); } @@ -203,62 +242,59 @@ class ActiveCallPad extends Component { icon: RecordIcon, id: ACTIONS_CTRL_MAP.recordCtrl, dataSign: - this.props.recordStatus === recordStatus.recording - ? 'stopRecord' - : 'record', + recordStatus === recordStatuses.recording ? 'stopRecord' : 'record', title: - this.props.recordStatus === recordStatus.recording - ? i18n.getString('stopRecord', this.props.currentLocale) - : i18n.getString('record', this.props.currentLocale), - active: this.props.recordStatus === recordStatus.recording, + recordStatus === recordStatuses.recording + ? i18n.getString('stopRecord', currentLocale) + : i18n.getString('record', currentLocale), + active: recordStatus === recordStatuses.recording, disabled: - this.props.isOnHold || - this.props.recordStatus === recordStatus.pending || - this.props.layout === callCtrlLayouts.mergeCtrl || - this.props.recordStatus === recordStatus.noAccess || + isOnHold || + recordStatus === recordStatuses.pending || + layout === callCtrlLayouts.mergeCtrl || + recordStatus === recordStatuses.noAccess || controlBusy, onClick: - this.props.recordStatus === recordStatus.recording - ? this.props.onStopRecord - : this.props.onRecord, + recordStatus === recordStatuses.recording ? onStopRecord : onRecord, }); /* --------------------- Transfer --------------------------- */ - const disabledTransfer = this.props.layout !== callCtrlLayouts.normalCtrl; - buttons.push({ - icon: TransferIcon, - id: ACTIONS_CTRL_MAP.transferCtrl, - dataSign: 'transfer', - title: i18n.getString('transfer', this.props.currentLocale), - disabled: disabledTransfer || controlBusy, - onClick: this.props.onTransfer, - }); + const disabledTransfer = layout !== callCtrlLayouts.normalCtrl; + if (!isOnWaitingTransfer) { + buttons.push({ + icon: TransferIcon, + id: ACTIONS_CTRL_MAP.transferCtrl, + dataSign: 'transfer', + title: i18n.getString('transfer', currentLocale), + disabled: disabledTransfer || controlBusy, + onClick: onTransfer, + }); + } /* --------------------- Flip --------------------------- */ const disableControlButton = - this.props.isOnHold || this.props.layout !== callCtrlLayouts.normalCtrl; - const disabledFlip = this.props.disableFlip || disableControlButton + isOnHold || layout !== callCtrlLayouts.normalCtrl; + const disabledFlip = disableFlip || disableControlButton; buttons.push({ icon: FlipIcon, id: ACTIONS_CTRL_MAP.flipCtrl, dataSign: 'flip', - title: i18n.getString('flip', this.props.currentLocale), + title: i18n.getString('flip', currentLocale), disabled: disabledFlip || controlBusy, - onClick: this.props.onFlip, + onClick: onFlip, }); /* --------------------- Park --------------------------- */ - if (this.props.showPark) { + if (showPark) { buttons.push({ icon: ParkIcon, id: ACTIONS_CTRL_MAP.parkCtrl, dataSign: 'park', - title: i18n.getString('park', this.props.currentLocale), + title: i18n.getString('park', currentLocale), disabled: disableControlButton || controlBusy, - onClick: this.props.onPark, + onClick: onPark, }); } // filter actions - const { actions } = this.props; if (actions.length > 0) { buttons = pickElements(actions, buttons); } @@ -266,14 +302,18 @@ class ActiveCallPad extends Component { /* --------------------- More Actions --------------------------- */ let moreActions = null; if (buttons.length > DisplayButtonNumber) { + const disableMoreButton = + isOnWaitingTransfer || + (disabledFlip && disabledTransfer) || + controlBusy; moreActions = ( @@ -296,7 +336,7 @@ class ActiveCallPad extends Component { const isLessBtn = buttons.length <= 3 && moreActions === null; return ( -
    +
    { const backHeader = showBackButton ? ( {backHeader} - {layout !== callCtrlLayouts.mergeCtrl ? timeCounter : null} + {showTimeCounter ? timeCounter : null} {callInfo} {children} @@ -234,6 +243,9 @@ ActiveCallPanel.propTypes = { actions: PropTypes.array, controlBusy: PropTypes.bool, callQueueName: PropTypes.string, + isOnWaitingTransfer: PropTypes.bool, + onCompleteTransfer: PropTypes.func, + isOnTransfer: PropTypes.bool, }; ActiveCallPanel.defaultProps = { @@ -256,6 +268,7 @@ ActiveCallPanel.defaultProps = { onFlip: () => null, onPark: () => null, gotoParticipantsCtrl: () => null, + onCompleteTransfer: () => null, sourceIcons: undefined, phoneTypeRenderer: undefined, phoneSourceNameRenderer: undefined, @@ -270,6 +283,8 @@ ActiveCallPanel.defaultProps = { actions: [], controlBusy: false, callQueueName: null, + isOnWaitingTransfer: false, + isOnTransfer: false, }; export default ActiveCallPanel; diff --git a/packages/ringcentral-widgets/components/ActiveCallsPanel/index.js b/packages/ringcentral-widgets/components/ActiveCallsPanel/index.js index e83e3191e4..828dc58e0f 100644 --- a/packages/ringcentral-widgets/components/ActiveCallsPanel/index.js +++ b/packages/ringcentral-widgets/components/ActiveCallsPanel/index.js @@ -173,6 +173,7 @@ export default class ActiveCallsPanel extends Component { ringoutReject, disableLinks, showRingoutCallControl, + showMultipleMatch, showSwitchCall, showTransferCall, showHoldOnOtherDevice, @@ -236,6 +237,7 @@ export default class ActiveCallsPanel extends Component { ringoutReject={ringoutReject} disableLinks={disableLinks} showRingoutCallControl={showRingoutCallControl} + showMultipleMatch={showMultipleMatch} showSwitchCall={showSwitchCall} showTransferCall={showTransferCall} showHoldOnOtherDevice={showHoldOnOtherDevice} @@ -386,6 +388,7 @@ ActiveCallsPanel.propTypes = { ringoutReject: PropTypes.func, disableLinks: PropTypes.bool, showRingoutCallControl: PropTypes.bool, + showMultipleMatch: PropTypes.bool, showSwitchCall: PropTypes.bool, showTransferCall: PropTypes.bool, showHoldOnOtherDevice: PropTypes.bool, @@ -460,6 +463,7 @@ ActiveCallsPanel.defaultProps = { ringoutReject: undefined, disableLinks: false, showRingoutCallControl: false, + showMultipleMatch: true, showSwitchCall: false, showTransferCall: true, showHoldOnOtherDevice: false, diff --git a/packages/ringcentral-widgets/components/BackHeaderV2/styles.scss b/packages/ringcentral-widgets/components/BackHeaderV2/styles.scss index 293bfc15eb..4a0cc63cad 100644 --- a/packages/ringcentral-widgets/components/BackHeaderV2/styles.scss +++ b/packages/ringcentral-widgets/components/BackHeaderV2/styles.scss @@ -38,10 +38,10 @@ $back-margin-left-classic: 6px; top: 0; width: 100%; height: 100%; - font-weight: 500; overflow: hidden; span { - @include text-ellipsis; + @include rc-typography('body2'); + color: rc-palette('text', 'Dark'); } } // 32px is back icon's width diff --git a/packages/ringcentral-widgets/components/BasicCallInfo/BasicCallInfo.tsx b/packages/ringcentral-widgets/components/BasicCallInfo/BasicCallInfo.tsx index 5d297c1573..4ad5eb1e11 100644 --- a/packages/ringcentral-widgets/components/BasicCallInfo/BasicCallInfo.tsx +++ b/packages/ringcentral-widgets/components/BasicCallInfo/BasicCallInfo.tsx @@ -43,7 +43,7 @@ export const BasicCallInfo: FunctionComponent = ({ const toggleOpen = () => setOpen(!open); useEffect(() => { - if (callControlRef.current) { + if (callControlRef?.current) { setPanelHeight(`calc(100% - ${callControlRef.current.clientHeight}px)`); } }, [callControlRef, status]); diff --git a/packages/ringcentral-widgets/components/CallCtrlPanel/index.js b/packages/ringcentral-widgets/components/CallCtrlPanel/index.js index f0af1298d5..ce76426fb5 100644 --- a/packages/ringcentral-widgets/components/CallCtrlPanel/index.js +++ b/packages/ringcentral-widgets/components/CallCtrlPanel/index.js @@ -142,6 +142,9 @@ class CallCtrlPanel extends Component { disableFlip, callQueueName, showPark, + isOnWaitingTransfer, + onCompleteTransfer, + isOnTransfer, } = this.props; const { isShowKeyPad, isShowMergeConfirm } = this.state; @@ -168,6 +171,8 @@ class CallCtrlPanel extends Component { startTime={startTime} isOnMute={isOnMute} isOnHold={isOnHold} + isOnTransfer={isOnTransfer} + isOnWaitingTransfer={isOnWaitingTransfer} recordStatus={recordStatus} onMute={onMute} onUnmute={onUnmute} @@ -180,6 +185,7 @@ class CallCtrlPanel extends Component { onPark={onPark} onAdd={onAdd} onMerge={this.onMerge} + onCompleteTransfer={onCompleteTransfer} nameMatches={nameMatches} fallBackName={fallBackName} areaCode={areaCode} @@ -285,6 +291,9 @@ CallCtrlPanel.propTypes = { controlBusy: PropTypes.bool, callQueueName: PropTypes.string, showPark: PropTypes.bool, + onCompleteTransfer: PropTypes.func, + isOnWaitingTransfer: PropTypes.bool, + isOnTransfer: PropTypes.bool, }; CallCtrlPanel.defaultProps = { @@ -326,12 +335,15 @@ CallCtrlPanel.defaultProps = { onPark: () => null, onKeyPadChange: () => null, onSelectMatcherName: () => null, + onCompleteTransfer: () => null, actions: [], recordStatus: '', controlBusy: false, disableFlip: false, callQueueName: null, showPark: false, + isOnWaitingTransfer: false, + isOnTransfer: false, }; export default CallCtrlPanel; diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/ActionButton.tsx b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/ActionButton.tsx new file mode 100644 index 0000000000..30795303e7 --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/ActionButton.tsx @@ -0,0 +1,22 @@ +import React, { FunctionComponent } from 'react'; +import { RcIconButton } from '@ringcentral/juno'; +import { CallLogActionButton } from 'ringcentral-integration/interfaces/CallLog.interface'; + +export const ActionButton: FunctionComponent = ({ + icon, + label, + disabled, + action, +}) => { + return ( + + ); +}; diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/CallHistoryActions.tsx b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/CallHistoryActions.tsx new file mode 100644 index 0000000000..fd3e6dde3d --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/CallHistoryActions.tsx @@ -0,0 +1,52 @@ +import React, { FunctionComponent } from 'react'; +import classnames from 'classnames'; +import { CallLogMenu } from 'ringcentral-integration/interfaces/CallLog.interface'; + +import { ActionButton } from './ActionButton'; +import { MenuButton } from './MenuButton'; +import styles from './styles.scss'; + +export type CallHistoryActionProps = { + actionMenu?: CallLogMenu; + isWide?: boolean; +}; + +export const CallHistoryActions: FunctionComponent = ({ + actionMenu = [], + isWide = true, +}) => { + // only show first 3 buttons + const displayedButtons = actionMenu.slice(0, 3); + + return ( +
    + {displayedButtons.map( + ({ icon, label, disabled, action, subMenu }, index) => { + if (action) { + return ( + + ); + } + if (subMenu) { + return ( + + ); + } + return null; + }, + )} +
    + ); +}; diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/MenuButton.tsx b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/MenuButton.tsx new file mode 100644 index 0000000000..615cf959d9 --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/MenuButton.tsx @@ -0,0 +1,83 @@ +import React, { useState, memo, FunctionComponent } from 'react'; +import { + RcIconButton, + RcIcon, + RcMenu, + RcMenuItem, + RcSubMenu, +} from '@ringcentral/juno'; +import { CallLogMenuItem } from 'ringcentral-integration/interfaces/CallLog.interface'; + +import styles from './styles.scss'; + +export const MenuButton: FunctionComponent = memo( + ({ icon, label, disabled, subMenu }) => { + const [anchorEl, setAnchorEl] = useState(null); + + const handleClick = (event: any) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const renderMenuItem = ({ + icon, + label, + disabled, + action, + subMenu, + }: CallLogMenuItem) => { + const menuIcon = icon && ; + + if (action) { + return ( + + {label} + + ); + } + + if (subMenu) { + return ( + + {subMenu.map(renderMenuItem)} + + ); + } + }; + + return ( +
    + + + {subMenu.map(renderMenuItem)} + +
    + ); + }, +); diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/index.ts b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/index.ts new file mode 100644 index 0000000000..073db4213a --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/index.ts @@ -0,0 +1 @@ +export * from './CallHistoryActions'; diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/styles.scss b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/styles.scss new file mode 100644 index 0000000000..fcb6136f94 --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryActions/styles.scss @@ -0,0 +1,13 @@ +@import '../../../lib/commonStyles/variable'; + +.actions { + @include display_flex; + + align-items: center; + justify-content: space-between; + width: 96px; + + &.classic { + width: 68px; + } +} diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryItem/CallHistoryItem.tsx b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryItem/CallHistoryItem.tsx index 1d8be3238e..e6143407b5 100644 --- a/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryItem/CallHistoryItem.tsx +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryItem/CallHistoryItem.tsx @@ -1,26 +1,49 @@ import React, { FunctionComponent } from 'react'; +import classnames from 'classnames'; import { callDirection } from 'ringcentral-integration/enums/callDirections'; +import { + CallLog, + CallLogMenu, +} from 'ringcentral-integration/interfaces/CallLog.interface'; -import { Call } from '../CallHistoryPanel.interface'; import { CallIcon } from '../CallIcon'; +import { CallHistoryActions } from '../CallHistoryActions'; import styles from './styles.scss'; export type CallHistoryItemProps = { - call: Call; + call: CallLog; + actionMenu?: CallLogMenu; + isWide?: boolean; }; export const CallHistoryItem: FunctionComponent = ({ call, -}) => ( -
    - -
    - - {call.direction === callDirection.outbound - ? call.toName - : call.fromName} - - {call.callTime} + actionMenu, + isWide = true, +}) => { + const displayName = + call.direction === callDirection.outbound ? call.toName : call.fromName; + + return ( +
    +
    + +
    + + {displayName} + + + {call.callTime} + +
    +
    +
    + +
    -
    -); + ); +}; diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryItem/styles.scss b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryItem/styles.scss index 5ba7a684b0..340379c744 100644 --- a/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryItem/styles.scss +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryItem/styles.scss @@ -3,20 +3,37 @@ .item { display: flex; align-items: center; + justify-content: space-between; height: 64px; box-sizing: border-box; - padding: 12px 18px; + padding: 12px 16px; border-bottom: 1px solid rc-palette(border, light); + + &.classic { + padding: 12px; + } +} + +.left { + @include display_flex; + @include flex_center; } .info { margin-left: 14px; display: flex; flex-flow: column; + overflow: hidden; + + &.classic { + max-width: 60px; + margin-left: 12px; + } } .name { @include rc-typography('body1'); + @include text-ellipsis; color: rc-palette(text, dark); margin-bottom: 2px; diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryPanel.interface.ts b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryPanel.interface.ts index cad0eb3202..6de99d891b 100644 --- a/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryPanel.interface.ts +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryPanel.interface.ts @@ -1,15 +1,11 @@ -import { Call as OriginCall } from 'ringcentral-integration/interfaces/Call.interface'; - -export interface Call extends OriginCall { - callTime?: string; - callDate?: string; -} +import { CallLog } from 'ringcentral-integration/interfaces/CallLog.interface'; +import { SvgSymbol } from '@ringcentral/juno'; export interface CallsTreeNode { name: string; depth: number; children?: string[]; - call?: Call; + call?: CallLog; } export interface CallsTree { diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryPanel.tsx b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryPanel.tsx index 373cc00781..a8b6b5eadc 100644 --- a/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryPanel.tsx +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/CallHistoryPanel.tsx @@ -1,17 +1,23 @@ import moment from 'moment'; import { AutoSizer } from 'react-virtualized'; import React, { FunctionComponent, useMemo, useCallback } from 'react'; +import { + CallLog, + CallLogMenu, +} from 'ringcentral-integration/interfaces/CallLog.interface'; import { CallHistoryItem } from './CallHistoryItem'; import { StickyVirtualizedList } from './StickyVirtualizedList'; import { RowRendererProps } from './StickyVirtualizedList/StickyVirtualizedList.interface'; -import { Call, CallsTree } from './CallHistoryPanel.interface'; +import { CallsTree } from './CallHistoryPanel.interface'; import styles from './styles.scss'; import i18n from './i18n'; export type CallHistoryPanelProps = { - calls: Call[]; + calls: CallLog[]; currentLocale: string; + getActionMenu?: (call: CallLog) => CallLogMenu; + isWide?: boolean; }; const DATE_ITEM_HEIGHT = 32; // ./styles.scss .date @@ -39,12 +45,14 @@ function formatCallDate(timestamp: number) { } function formatCallTime(timestamp: number) { - return moment(timestamp).format('h:mmA'); + return moment(timestamp).format('h:mm A'); } export const CallHistoryPanel: FunctionComponent = ({ calls, currentLocale, + getActionMenu, + isWide = true, }) => { const tree = useMemo(() => { const _tree: CallsTree = { @@ -55,7 +63,7 @@ export const CallHistoryPanel: FunctionComponent = ({ }, }; - calls.forEach((call: Call) => { + calls.forEach((call: CallLog) => { const { id, startTime } = call; const callDate = formatCallDate(startTime); @@ -119,14 +127,23 @@ export const CallHistoryPanel: FunctionComponent = ({ if (node.children) { return ( -
    +
    {i18n.getString(node.name, currentLocale)}
    ); } return ( -
    - +
    +
    ); }, @@ -156,3 +173,8 @@ export const CallHistoryPanel: FunctionComponent = ({
    ); }; + +CallHistoryPanel.defaultProps = { + calls: [], + getActionMenu: () => [], +}; diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/CallIcon/CallIcon.tsx b/packages/ringcentral-widgets/components/CallHistoryPanel/CallIcon/CallIcon.tsx index 39155089a6..4a22e0f730 100644 --- a/packages/ringcentral-widgets/components/CallHistoryPanel/CallIcon/CallIcon.tsx +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/CallIcon/CallIcon.tsx @@ -39,14 +39,15 @@ export const CallIcon: FunctionComponent = ({ }, [missed, direction]); return ( - - - + ); }; diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/de-DE.js b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/de-DE.js new file mode 100644 index 0000000000..2bd5ffa76a --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/de-DE.js @@ -0,0 +1,9 @@ +export default { + today: "Heute", + yesterday: "Gestern", + empty: "Keine Anrufaufzeichnungen" +}; + +// @key: @#@"today"@#@ @source: @#@"Today"@#@ +// @key: @#@"yesterday"@#@ @source: @#@"Yesterday"@#@ +// @key: @#@"empty"@#@ @source: @#@"No call records"@#@ diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/en-AU.js b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/en-AU.js new file mode 100644 index 0000000000..3af4f2c4c8 --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/en-AU.js @@ -0,0 +1,9 @@ +export default { + today: "Today", + yesterday: "Yesterday", + empty: "No call records" +}; + +// @key: @#@"today"@#@ @source: @#@"Today"@#@ +// @key: @#@"yesterday"@#@ @source: @#@"Yesterday"@#@ +// @key: @#@"empty"@#@ @source: @#@"No call records"@#@ diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/en-GB.js b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/en-GB.js new file mode 100644 index 0000000000..3af4f2c4c8 --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/en-GB.js @@ -0,0 +1,9 @@ +export default { + today: "Today", + yesterday: "Yesterday", + empty: "No call records" +}; + +// @key: @#@"today"@#@ @source: @#@"Today"@#@ +// @key: @#@"yesterday"@#@ @source: @#@"Yesterday"@#@ +// @key: @#@"empty"@#@ @source: @#@"No call records"@#@ diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/es-419.js b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/es-419.js new file mode 100644 index 0000000000..48e497f9e5 --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/es-419.js @@ -0,0 +1,9 @@ +export default { + today: "Hoy", + yesterday: "Ayer", + empty: "No hay registros de llamadas" +}; + +// @key: @#@"today"@#@ @source: @#@"Today"@#@ +// @key: @#@"yesterday"@#@ @source: @#@"Yesterday"@#@ +// @key: @#@"empty"@#@ @source: @#@"No call records"@#@ diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/es-ES.js b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/es-ES.js new file mode 100644 index 0000000000..48e497f9e5 --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/es-ES.js @@ -0,0 +1,9 @@ +export default { + today: "Hoy", + yesterday: "Ayer", + empty: "No hay registros de llamadas" +}; + +// @key: @#@"today"@#@ @source: @#@"Today"@#@ +// @key: @#@"yesterday"@#@ @source: @#@"Yesterday"@#@ +// @key: @#@"empty"@#@ @source: @#@"No call records"@#@ diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/fr-CA.js b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/fr-CA.js new file mode 100644 index 0000000000..a19cb29c92 --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/fr-CA.js @@ -0,0 +1,9 @@ +export default { + today: "Aujourd'hui", + yesterday: "Hier", + empty: "Aucun enregistrement de l'appel" +}; + +// @key: @#@"today"@#@ @source: @#@"Today"@#@ +// @key: @#@"yesterday"@#@ @source: @#@"Yesterday"@#@ +// @key: @#@"empty"@#@ @source: @#@"No call records"@#@ diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/fr-FR.js b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/fr-FR.js new file mode 100644 index 0000000000..8a12407d91 --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/fr-FR.js @@ -0,0 +1,9 @@ +export default { + today: "Aujourd'hui", + yesterday: "Hier", + empty: "Aucun enregistrement d'appel" +}; + +// @key: @#@"today"@#@ @source: @#@"Today"@#@ +// @key: @#@"yesterday"@#@ @source: @#@"Yesterday"@#@ +// @key: @#@"empty"@#@ @source: @#@"No call records"@#@ diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/it-IT.js b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/it-IT.js new file mode 100644 index 0000000000..38dbad7d81 --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/it-IT.js @@ -0,0 +1,9 @@ +export default { + today: "Oggi", + yesterday: "Ieri", + empty: "Nessuna registrazione chiamata" +}; + +// @key: @#@"today"@#@ @source: @#@"Today"@#@ +// @key: @#@"yesterday"@#@ @source: @#@"Yesterday"@#@ +// @key: @#@"empty"@#@ @source: @#@"No call records"@#@ diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/ja-JP.js b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/ja-JP.js new file mode 100644 index 0000000000..1c8250bea6 --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/ja-JP.js @@ -0,0 +1,9 @@ +export default { + today: "今日", + yesterday: "昨日", + empty: "通話記録なし" +}; + +// @key: @#@"today"@#@ @source: @#@"Today"@#@ +// @key: @#@"yesterday"@#@ @source: @#@"Yesterday"@#@ +// @key: @#@"empty"@#@ @source: @#@"No call records"@#@ diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/ko-KR.js b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/ko-KR.js new file mode 100644 index 0000000000..56c7229a7e --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/ko-KR.js @@ -0,0 +1,9 @@ +export default { + today: "오늘", + yesterday: "내일", + empty: "통화 기록 없음" +}; + +// @key: @#@"today"@#@ @source: @#@"Today"@#@ +// @key: @#@"yesterday"@#@ @source: @#@"Yesterday"@#@ +// @key: @#@"empty"@#@ @source: @#@"No call records"@#@ diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/nl-NL.js b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/nl-NL.js new file mode 100644 index 0000000000..541c2f547a --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/nl-NL.js @@ -0,0 +1,9 @@ +export default { + today: "Vandaag", + yesterday: "Gisteren", + empty: "Geen oproepgegevens" +}; + +// @key: @#@"today"@#@ @source: @#@"Today"@#@ +// @key: @#@"yesterday"@#@ @source: @#@"Yesterday"@#@ +// @key: @#@"empty"@#@ @source: @#@"No call records"@#@ diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/pt-BR.js b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/pt-BR.js new file mode 100644 index 0000000000..82df76dfc5 --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/pt-BR.js @@ -0,0 +1,9 @@ +export default { + today: "Hoje", + yesterday: "Ontem", + empty: "Sem registros de chamada" +}; + +// @key: @#@"today"@#@ @source: @#@"Today"@#@ +// @key: @#@"yesterday"@#@ @source: @#@"Yesterday"@#@ +// @key: @#@"empty"@#@ @source: @#@"No call records"@#@ diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/pt-PT.js b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/pt-PT.js new file mode 100644 index 0000000000..fd33c5f907 --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/pt-PT.js @@ -0,0 +1,9 @@ +export default { + today: "Hoje", + yesterday: "Ontem", + empty: "Sem registos de chamadas" +}; + +// @key: @#@"today"@#@ @source: @#@"Today"@#@ +// @key: @#@"yesterday"@#@ @source: @#@"Yesterday"@#@ +// @key: @#@"empty"@#@ @source: @#@"No call records"@#@ diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/zh-CN.js b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/zh-CN.js new file mode 100644 index 0000000000..38aa290659 --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/zh-CN.js @@ -0,0 +1,9 @@ +export default { + today: "今天", + yesterday: "昨天", + empty: "没有通话记录" +}; + +// @key: @#@"today"@#@ @source: @#@"Today"@#@ +// @key: @#@"yesterday"@#@ @source: @#@"Yesterday"@#@ +// @key: @#@"empty"@#@ @source: @#@"No call records"@#@ diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/zh-HK.js b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/zh-HK.js new file mode 100644 index 0000000000..a786a95558 --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/zh-HK.js @@ -0,0 +1,9 @@ +export default { + today: "今天", + yesterday: "昨天", + empty: "無通話紀錄" +}; + +// @key: @#@"today"@#@ @source: @#@"Today"@#@ +// @key: @#@"yesterday"@#@ @source: @#@"Yesterday"@#@ +// @key: @#@"empty"@#@ @source: @#@"No call records"@#@ diff --git a/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/zh-TW.js b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/zh-TW.js new file mode 100644 index 0000000000..a786a95558 --- /dev/null +++ b/packages/ringcentral-widgets/components/CallHistoryPanel/i18n/zh-TW.js @@ -0,0 +1,9 @@ +export default { + today: "今天", + yesterday: "昨天", + empty: "無通話紀錄" +}; + +// @key: @#@"today"@#@ @source: @#@"Today"@#@ +// @key: @#@"yesterday"@#@ @source: @#@"Yesterday"@#@ +// @key: @#@"empty"@#@ @source: @#@"No call records"@#@ diff --git a/packages/ringcentral-widgets/components/CallItem/styles.scss b/packages/ringcentral-widgets/components/CallItem/styles.scss index 2154aa7537..4af66c267f 100644 --- a/packages/ringcentral-widgets/components/CallItem/styles.scss +++ b/packages/ringcentral-widgets/components/CallItem/styles.scss @@ -36,6 +36,8 @@ padding-right: 8px; box-sizing: border-box; // Add bellow style if you have calllog button on callitem // max-width: calc(100% - 23px - 21px); + overflow: hidden; + text-overflow: ellipsis; } .callIcon { diff --git a/packages/ringcentral-widgets/components/CallLogFields/FieldItem/FieldItem.tsx b/packages/ringcentral-widgets/components/CallLogFields/FieldItem/FieldItem.tsx index ea09c2943b..51b1db5aa1 100644 --- a/packages/ringcentral-widgets/components/CallLogFields/FieldItem/FieldItem.tsx +++ b/packages/ringcentral-widgets/components/CallLogFields/FieldItem/FieldItem.tsx @@ -357,7 +357,7 @@ export class FieldItem extends Component { task: { ...task, option: value, - ticketId: task.ticketId || task.tickets[0], + ticketId: task.ticketId || task.tickets[0]?.id, }, }, currentSessionId, @@ -369,6 +369,7 @@ export class FieldItem extends Component { const { currentLog, fieldOption } = this.props; const { renderCondition, label } = fieldOption; const { task } = currentLog; + // TODO: consider move this logic to zendesk if (task.option !== renderCondition) { return null; } @@ -380,9 +381,10 @@ export class FieldItem extends Component { }; }) : []; - // TODO: need to double check the logic here - const defaultValue = - task.ticketId || (task.tickets && task.tickets[0]?.id) || ''; + const defaultTicket = + task.tickets.find((ticket: any) => ticket.id === task.ticketId) || + (task.tickets && task.tickets[0]); + const defaultValue = defaultTicket?.id || ''; return ( = {...rest} title={`${value}`} value={value} + gutterBottom disabled={disabled} InputProps={{ classes: { diff --git a/packages/ringcentral-widgets/components/CallLogFields/FieldItem/LogFieldsInput/LogFieldsInput.tsx b/packages/ringcentral-widgets/components/CallLogFields/FieldItem/LogFieldsInput/LogFieldsInput.tsx index 2823e8152d..3b914a5aea 100644 --- a/packages/ringcentral-widgets/components/CallLogFields/FieldItem/LogFieldsInput/LogFieldsInput.tsx +++ b/packages/ringcentral-widgets/components/CallLogFields/FieldItem/LogFieldsInput/LogFieldsInput.tsx @@ -42,7 +42,10 @@ export class LogFieldsInput extends Component< this.checkPropsUpdate(nextProps, 'value'); } - updateValue(value: string | number, onChange: LogFieldsInputProps['onChange']) { + updateValue( + value: string | number, + onChange: LogFieldsInputProps['onChange'], + ) { this.setState({ value }); this.debounce(() => onChange(value)); } @@ -59,6 +62,7 @@ export class LogFieldsInput extends Component< required={required} error={error} value={value} + gutterBottom onChange={(e) => this.updateValue( type === 'number' ? Number(e.target.value) : e.target.value, diff --git a/packages/ringcentral-widgets/components/CallLogFields/FieldItem/SelectField/SelectField.tsx b/packages/ringcentral-widgets/components/CallLogFields/FieldItem/SelectField/SelectField.tsx index 3c28e59b5e..d410430b62 100644 --- a/packages/ringcentral-widgets/components/CallLogFields/FieldItem/SelectField/SelectField.tsx +++ b/packages/ringcentral-widgets/components/CallLogFields/FieldItem/SelectField/SelectField.tsx @@ -1,4 +1,4 @@ -import { RcLineSelect, RcLineSelectProps, RcMenuItem } from '@ringcentral/juno'; +import { RcMenuItem, RcSelect, RcSelectProps } from '@ringcentral/juno'; import React, { FunctionComponent } from 'react'; export type SelectFieldProps = { @@ -7,14 +7,14 @@ export type SelectFieldProps = { value: string; disabled: boolean; }[]; -} & RcLineSelectProps; +} & RcSelectProps; export const SelectField: FunctionComponent = ({ options, ...rest }) => { return ( - + {options.map((item, i) => ( = ({ {item.label} ))} - + ); }; diff --git a/packages/ringcentral-widgets/components/CallLogPanel/CallLog.interface.ts b/packages/ringcentral-widgets/components/CallLogPanel/CallLog.interface.ts index 145fbef14d..2b6af17dac 100644 --- a/packages/ringcentral-widgets/components/CallLogPanel/CallLog.interface.ts +++ b/packages/ringcentral-widgets/components/CallLogPanel/CallLog.interface.ts @@ -130,3 +130,5 @@ export interface NavigateToEntities { } export type CallStatus = 'onHold' | 'active' | 'callEnd'; + +export type CallLogTitle = 'createCallLog' | 'updateCallLog'; diff --git a/packages/ringcentral-widgets/components/CallLogPanel/CallLogPanel.interface.tsx b/packages/ringcentral-widgets/components/CallLogPanel/CallLogPanel.interface.tsx index df06cc9ce3..0accbee4f0 100644 --- a/packages/ringcentral-widgets/components/CallLogPanel/CallLogPanel.interface.tsx +++ b/packages/ringcentral-widgets/components/CallLogPanel/CallLogPanel.interface.tsx @@ -2,7 +2,7 @@ import { RcIconProps } from '@ringcentral/juno'; import { MutableRefObject } from 'react'; import { DateTimeFormatter } from 'ringcentral-integration/lib/getIntlDateTimeFormatter'; -import { Call, CallLog } from './CallLog.interface'; +import { Call, CallLog, CallLogTitle } from './CallLog.interface'; interface CallLogPanelConfig { showSpinner: boolean; @@ -10,6 +10,7 @@ interface CallLogPanelConfig { isWide?: boolean; header?: boolean; + headerTitle?: CallLogTitle; showSmallCallControl?: boolean; disableLinks?: boolean; useNewNotification?: boolean; diff --git a/packages/ringcentral-widgets/components/CallLogPanel/CallLogPanel.tsx b/packages/ringcentral-widgets/components/CallLogPanel/CallLogPanel.tsx index 6e836a2cdd..cb46664cc3 100644 --- a/packages/ringcentral-widgets/components/CallLogPanel/CallLogPanel.tsx +++ b/packages/ringcentral-widgets/components/CallLogPanel/CallLogPanel.tsx @@ -30,6 +30,7 @@ export default class CallLogPanel extends Component { currentNotificationIdentify: '', shrinkNotification: () => null, header: true, + headerTitle: 'createCallLog', showSmallCallControl: false, isInTransferPage: false, showSpinner: true, @@ -319,6 +320,7 @@ export default class CallLogPanel extends Component { refs: { root: rootRef }, backIcon, header, + headerTitle, isInTransferPage, isWide, children, @@ -340,7 +342,7 @@ export default class CallLogPanel extends Component { backIcon={backIcon} isWide={isWide} rightIcon={this.genSaveLogButtonV2()} - title={i18n.getString('createCallLog', currentLocale)} + title={i18n.getString(headerTitle, currentLocale)} className={classnames(styles.header, backHeader)} onBackClick={() => this.goBack()} /> diff --git a/packages/ringcentral-widgets/components/CallLogPanel/i18n/en-US.js b/packages/ringcentral-widgets/components/CallLogPanel/i18n/en-US.js index ff859cc9e6..c8cde1eb25 100644 --- a/packages/ringcentral-widgets/components/CallLogPanel/i18n/en-US.js +++ b/packages/ringcentral-widgets/components/CallLogPanel/i18n/en-US.js @@ -1,4 +1,4 @@ export default { - createCallLog: 'Create call log' + createCallLog: 'Create call log', + updateCallLog: 'Update call log', }; - diff --git a/packages/ringcentral-widgets/components/ComposeTextPanel/index.js b/packages/ringcentral-widgets/components/ComposeTextPanel/index.js index 6908685dd9..2f3a511988 100644 --- a/packages/ringcentral-widgets/components/ComposeTextPanel/index.js +++ b/packages/ringcentral-widgets/components/ComposeTextPanel/index.js @@ -35,7 +35,7 @@ class ComposeTextPanel extends Component { }; } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.messageText !== this.state.messageText) { this.setState({ messageText: nextProps.messageText, diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/de-DE.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/de-DE.js new file mode 100644 index 0000000000..75aa7d65d9 --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/de-DE.js @@ -0,0 +1,5 @@ +export default { + doNotCall: "Nicht kontaktieren" +}; + +// @key: @#@"doNotCall"@#@ @source: @#@"Do Not Contact"@#@ diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/en-AU.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/en-AU.js new file mode 100644 index 0000000000..78ab2bdb51 --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/en-AU.js @@ -0,0 +1,5 @@ +export default { + doNotCall: "Do Not Contact" +}; + +// @key: @#@"doNotCall"@#@ @source: @#@"Do Not Contact"@#@ diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/en-GB.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/en-GB.js new file mode 100644 index 0000000000..78ab2bdb51 --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/en-GB.js @@ -0,0 +1,5 @@ +export default { + doNotCall: "Do Not Contact" +}; + +// @key: @#@"doNotCall"@#@ @source: @#@"Do Not Contact"@#@ diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/en-US.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/en-US.js new file mode 100644 index 0000000000..8cf1209252 --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/en-US.js @@ -0,0 +1,3 @@ +export default { + doNotCall: 'Do Not Contact', +}; diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/es-419.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/es-419.js new file mode 100644 index 0000000000..7885aa7464 --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/es-419.js @@ -0,0 +1,5 @@ +export default { + doNotCall: "No contactar" +}; + +// @key: @#@"doNotCall"@#@ @source: @#@"Do Not Contact"@#@ diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/es-ES.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/es-ES.js new file mode 100644 index 0000000000..7885aa7464 --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/es-ES.js @@ -0,0 +1,5 @@ +export default { + doNotCall: "No contactar" +}; + +// @key: @#@"doNotCall"@#@ @source: @#@"Do Not Contact"@#@ diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/fr-CA.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/fr-CA.js new file mode 100644 index 0000000000..2f1cd5bec6 --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/fr-CA.js @@ -0,0 +1,5 @@ +export default { + doNotCall: "Ne pas contacter" +}; + +// @key: @#@"doNotCall"@#@ @source: @#@"Do Not Contact"@#@ diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/fr-FR.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/fr-FR.js new file mode 100644 index 0000000000..2f1cd5bec6 --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/fr-FR.js @@ -0,0 +1,5 @@ +export default { + doNotCall: "Ne pas contacter" +}; + +// @key: @#@"doNotCall"@#@ @source: @#@"Do Not Contact"@#@ diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/index.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/index.js new file mode 100644 index 0000000000..b67573a87a --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/index.js @@ -0,0 +1,4 @@ +import I18n from '@ringcentral-integration/i18n'; +import loadLocale from './loadLocale'; + +export default new I18n(loadLocale); diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/it-IT.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/it-IT.js new file mode 100644 index 0000000000..7eda502047 --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/it-IT.js @@ -0,0 +1,5 @@ +export default { + doNotCall: "Non contattare" +}; + +// @key: @#@"doNotCall"@#@ @source: @#@"Do Not Contact"@#@ diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/ja-JP.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/ja-JP.js new file mode 100644 index 0000000000..8afae11a61 --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/ja-JP.js @@ -0,0 +1,5 @@ +export default { + doNotCall: "連絡を取らない" +}; + +// @key: @#@"doNotCall"@#@ @source: @#@"Do Not Contact"@#@ diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/ko-KR.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/ko-KR.js new file mode 100644 index 0000000000..f731847ac8 --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/ko-KR.js @@ -0,0 +1,5 @@ +export default { + doNotCall: "연락하지 마십시오" +}; + +// @key: @#@"doNotCall"@#@ @source: @#@"Do Not Contact"@#@ diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/loadLocale.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/loadLocale.js new file mode 100644 index 0000000000..12b11cfa2e --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/loadLocale.js @@ -0,0 +1 @@ +/* loadLocale */ diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/nl-NL.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/nl-NL.js new file mode 100644 index 0000000000..cbe428867a --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/nl-NL.js @@ -0,0 +1,5 @@ +export default { + doNotCall: "Neem geen contact op" +}; + +// @key: @#@"doNotCall"@#@ @source: @#@"Do Not Contact"@#@ diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/pt-BR.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/pt-BR.js new file mode 100644 index 0000000000..b42d618655 --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/pt-BR.js @@ -0,0 +1,5 @@ +export default { + doNotCall: "Não contatar" +}; + +// @key: @#@"doNotCall"@#@ @source: @#@"Do Not Contact"@#@ diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/pt-PT.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/pt-PT.js new file mode 100644 index 0000000000..ba1d8ef31f --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/pt-PT.js @@ -0,0 +1,5 @@ +export default { + doNotCall: "Não contactar" +}; + +// @key: @#@"doNotCall"@#@ @source: @#@"Do Not Contact"@#@ diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/zh-CN.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/zh-CN.js new file mode 100644 index 0000000000..df8fe6acd7 --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/zh-CN.js @@ -0,0 +1,5 @@ +export default { + doNotCall: "请勿联系" +}; + +// @key: @#@"doNotCall"@#@ @source: @#@"Do Not Contact"@#@ diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/zh-HK.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/zh-HK.js new file mode 100644 index 0000000000..fa27c32710 --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/zh-HK.js @@ -0,0 +1,5 @@ +export default { + doNotCall: "請勿聯絡" +}; + +// @key: @#@"doNotCall"@#@ @source: @#@"Do Not Contact"@#@ diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/i18n/zh-TW.js b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/zh-TW.js new file mode 100644 index 0000000000..fa27c32710 --- /dev/null +++ b/packages/ringcentral-widgets/components/ContactDropdownList/i18n/zh-TW.js @@ -0,0 +1,5 @@ +export default { + doNotCall: "請勿聯絡" +}; + +// @key: @#@"doNotCall"@#@ @source: @#@"Do Not Contact"@#@ diff --git a/packages/ringcentral-widgets/components/ContactDropdownList/index.js b/packages/ringcentral-widgets/components/ContactDropdownList/index.js index 1ec9d45787..0b04002f93 100644 --- a/packages/ringcentral-widgets/components/ContactDropdownList/index.js +++ b/packages/ringcentral-widgets/components/ContactDropdownList/index.js @@ -5,6 +5,7 @@ import { RcIcon } from '@ringcentral/juno'; import { Blocked } from '@ringcentral/juno/icon'; import { Tooltip } from '../Rcui/Tooltip'; import styles from './styles.scss'; +import i18n from './i18n'; import phoneTypeNames from '../../lib/phoneTypeNames'; import phoneSourceNames from '../../lib/phoneSourceNames'; @@ -24,14 +25,15 @@ const ContactInfo = ({ const nameTitle = `${name} ${spliter} ${phoneSourceName}`; return (
    {name} {spliter} {phoneSourceName} -
    ); }; @@ -48,18 +50,19 @@ ContactInfo.defaultProps = { doNotCall: false, }; -const DoNotCallIndicator = ({ doNotCall }) => { +const DoNotCallIndicator = ({ doNotCall, currentLocale }) => { if (!doNotCall) return null; return ( -
    - - - -
    + +
    + +
    +
    ); }; DoNotCallIndicator.propTypes = { doNotCall: PropTypes.bool, + currentLocale: PropTypes.string.isRequired, }; DoNotCallIndicator.defaultProps = { doNotCall: false, @@ -145,6 +148,10 @@ const ContactItem = ({ titleEnabled={titleEnabled} doNotCall={doNotCall} /> + {conversationBody} - + {this.props.restrictSendMessage?.(this.getSelectedContact()) ? ( + + This contact is on a Do Not Contact list. + + ) : ( + + )}
    ); } @@ -340,6 +351,7 @@ ConversationPanel.propTypes = { addAttachment: PropTypes.func, removeAttachment: PropTypes.func, onAttachmentDownload: PropTypes.func, + restrictSendMessage: PropTypes.func, }; ConversationPanel.defaultProps = { disableLinks: false, @@ -364,6 +376,7 @@ ConversationPanel.defaultProps = { addAttachment: () => null, removeAttachment: () => null, onAttachmentDownload: undefined, + restrictSendMessage: undefined, }; export default ConversationPanel; diff --git a/packages/ringcentral-widgets/components/ConversationPanel/styles.scss b/packages/ringcentral-widgets/components/ConversationPanel/styles.scss index 37dbcbf93b..6b023762f4 100644 --- a/packages/ringcentral-widgets/components/ConversationPanel/styles.scss +++ b/packages/ringcentral-widgets/components/ConversationPanel/styles.scss @@ -57,3 +57,7 @@ margin-top: 5px; } } + +.alert { + margin: 0 16px; +} diff --git a/packages/ringcentral-widgets/components/DialerPanel/index.js b/packages/ringcentral-widgets/components/DialerPanel/index.js index 0943f2503e..6cf8e0ab1a 100644 --- a/packages/ringcentral-widgets/components/DialerPanel/index.js +++ b/packages/ringcentral-widgets/components/DialerPanel/index.js @@ -53,7 +53,6 @@ const DialerPanel = ({ inputEl.current.focus(); } }, []); - const input = useV2 ? ( ) : ( { + inputEl.current = element; + }} value={toNumber} onChange={onToNumberChange} onClean={clearToNumber} @@ -158,7 +160,7 @@ const DialerPanel = ({ {children}
    ); -} +}; DialerPanel.propTypes = { currentLocale: PropTypes.string.isRequired, diff --git a/packages/ringcentral-widgets/components/EntityModal/i18n/en-US.js b/packages/ringcentral-widgets/components/EntityModal/i18n/en-US.js index 8a87598140..47125e8b4d 100644 --- a/packages/ringcentral-widgets/components/EntityModal/i18n/en-US.js +++ b/packages/ringcentral-widgets/components/EntityModal/i18n/en-US.js @@ -3,6 +3,7 @@ export default { contact: 'Contact', lead: 'Lead', opportunity: 'Opportunity', + case: 'Case', chooseEntity: 'Please select entity type', create: 'Create', }; diff --git a/packages/ringcentral-widgets/components/GenericMeetingPanel/GenericMeetingPanel.tsx b/packages/ringcentral-widgets/components/GenericMeetingPanel/GenericMeetingPanel.tsx index 92cd48f89a..20f01b948c 100644 --- a/packages/ringcentral-widgets/components/GenericMeetingPanel/GenericMeetingPanel.tsx +++ b/packages/ringcentral-widgets/components/GenericMeetingPanel/GenericMeetingPanel.tsx @@ -6,7 +6,8 @@ import { SpinnerOverlay } from '../SpinnerOverlay'; import MeetingConfigs from '../MeetingConfigs'; import isSafari from '../../lib/isSafari'; -import { VideoConfig, Topic } from '../VideoPanel/VideoConfig'; +import { VideoConfig } from '../VideoPanel/VideoConfig'; +import { Topic } from '../InnerTopic'; import { MeetingConfigs as MeetingConfigsV2 } from '../MeetingConfigsV2'; import { GenericMeetingPanelProps } from './interface'; @@ -68,6 +69,7 @@ const GenericMeetingPanel: React.ComponentType = ( showSpinnerInConfigPanel, enableServiceWebSettings, putRecurringMeetingInMiddle, + defaultTopic, } = props; if (showSpinner) { @@ -99,6 +101,7 @@ const GenericMeetingPanel: React.ComponentType = ( {isRCM && useRcmV2 && ( = ( }} currentLocale={currentLocale} setTopicRef={setTopicRef} + defaultTopic={defaultTopic} /> )} diff --git a/packages/ringcentral-widgets/components/GenericMeetingPanel/interface.ts b/packages/ringcentral-widgets/components/GenericMeetingPanel/interface.ts index b869ce96d4..7896bf06f8 100644 --- a/packages/ringcentral-widgets/components/GenericMeetingPanel/interface.ts +++ b/packages/ringcentral-widgets/components/GenericMeetingPanel/interface.ts @@ -44,6 +44,7 @@ export interface CommonProps { hasSettingsChanged?: boolean; personalMeetingId: string; switchUsePersonalMeetingId: (usePersonalMeetingId: boolean) => any; + defaultTopic: string; } export interface VideoPanelProps extends CommonProps { diff --git a/packages/ringcentral-widgets/components/IncomingCallPad/index.js b/packages/ringcentral-widgets/components/IncomingCallPad/index.js index d6ca76e5ed..6d115be81a 100644 --- a/packages/ringcentral-widgets/components/IncomingCallPad/index.js +++ b/packages/ringcentral-widgets/components/IncomingCallPad/index.js @@ -156,7 +156,7 @@ export default class IncomingCallPad extends Component { iconWidth={274} iconX={116} showBorder={!this.state.toVoiceMailEnabled} - dataSign="toVoicemail" + dataSign="toVoiceMail" className={styles.bigCallButton} disabled={!this.state.toVoiceMailEnabled} /> diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/de-DE.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/de-DE.js new file mode 100644 index 0000000000..db45e591e1 --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/de-DE.js @@ -0,0 +1,5 @@ +export default { + topic: "Titel des Meetings" +}; + +// @key: @#@"topic"@#@ @source: @#@"Meeting title"@#@ diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/en-AU.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/en-AU.js new file mode 100644 index 0000000000..024a2dac05 --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/en-AU.js @@ -0,0 +1,5 @@ +export default { + topic: "Meeting title" +}; + +// @key: @#@"topic"@#@ @source: @#@"Meeting title"@#@ diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/en-GB.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/en-GB.js new file mode 100644 index 0000000000..024a2dac05 --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/en-GB.js @@ -0,0 +1,5 @@ +export default { + topic: "Meeting title" +}; + +// @key: @#@"topic"@#@ @source: @#@"Meeting title"@#@ diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/en-US.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/en-US.js new file mode 100644 index 0000000000..23d3ea2c10 --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/en-US.js @@ -0,0 +1,3 @@ +export default { + topic: 'Meeting title', +} diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/es-419.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/es-419.js new file mode 100644 index 0000000000..5115aa615c --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/es-419.js @@ -0,0 +1,5 @@ +export default { + topic: "Título de la reunión" +}; + +// @key: @#@"topic"@#@ @source: @#@"Meeting title"@#@ diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/es-ES.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/es-ES.js new file mode 100644 index 0000000000..c29995c6c6 --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/es-ES.js @@ -0,0 +1,5 @@ +export default { + topic: "Nombre de la reunión" +}; + +// @key: @#@"topic"@#@ @source: @#@"Meeting title"@#@ diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/fr-CA.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/fr-CA.js new file mode 100644 index 0000000000..e0e41ed519 --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/fr-CA.js @@ -0,0 +1,5 @@ +export default { + topic: "Titre du meeting" +}; + +// @key: @#@"topic"@#@ @source: @#@"Meeting title"@#@ diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/fr-FR.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/fr-FR.js new file mode 100644 index 0000000000..f87598ec11 --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/fr-FR.js @@ -0,0 +1,5 @@ +export default { + topic: "Titre de la réunion" +}; + +// @key: @#@"topic"@#@ @source: @#@"Meeting title"@#@ diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/index.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/index.js new file mode 100644 index 0000000000..b67573a87a --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/index.js @@ -0,0 +1,4 @@ +import I18n from '@ringcentral-integration/i18n'; +import loadLocale from './loadLocale'; + +export default new I18n(loadLocale); diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/it-IT.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/it-IT.js new file mode 100644 index 0000000000..807f2e6c0d --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/it-IT.js @@ -0,0 +1,5 @@ +export default { + topic: "Titolo della riunione" +}; + +// @key: @#@"topic"@#@ @source: @#@"Meeting title"@#@ diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/ja-JP.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/ja-JP.js new file mode 100644 index 0000000000..99b1823acf --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/ja-JP.js @@ -0,0 +1,5 @@ +export default { + topic: "会議タイトル" +}; + +// @key: @#@"topic"@#@ @source: @#@"Meeting title"@#@ diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/ko-KR.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/ko-KR.js new file mode 100644 index 0000000000..1529dfe878 --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/ko-KR.js @@ -0,0 +1,5 @@ +export default { + topic: "모임 제목" +}; + +// @key: @#@"topic"@#@ @source: @#@"Meeting title"@#@ diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/loadLocale.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/loadLocale.js new file mode 100644 index 0000000000..12b11cfa2e --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/loadLocale.js @@ -0,0 +1 @@ +/* loadLocale */ diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/nl-NL.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/nl-NL.js new file mode 100644 index 0000000000..c05195d70c --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/nl-NL.js @@ -0,0 +1,5 @@ +export default { + topic: "Meetingtitel" +}; + +// @key: @#@"topic"@#@ @source: @#@"Meeting title"@#@ diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/pt-BR.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/pt-BR.js new file mode 100644 index 0000000000..ab0ad38cdd --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/pt-BR.js @@ -0,0 +1,5 @@ +export default { + topic: "Título da reunião" +}; + +// @key: @#@"topic"@#@ @source: @#@"Meeting title"@#@ diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/pt-PT.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/pt-PT.js new file mode 100644 index 0000000000..ab0ad38cdd --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/pt-PT.js @@ -0,0 +1,5 @@ +export default { + topic: "Título da reunião" +}; + +// @key: @#@"topic"@#@ @source: @#@"Meeting title"@#@ diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/zh-CN.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/zh-CN.js new file mode 100644 index 0000000000..3d5961fd18 --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/zh-CN.js @@ -0,0 +1,5 @@ +export default { + topic: "会议标题" +}; + +// @key: @#@"topic"@#@ @source: @#@"Meeting title"@#@ diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/zh-HK.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/zh-HK.js new file mode 100644 index 0000000000..b46ae38eef --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/zh-HK.js @@ -0,0 +1,5 @@ +export default { + topic: "會議標題" +}; + +// @key: @#@"topic"@#@ @source: @#@"Meeting title"@#@ diff --git a/packages/ringcentral-widgets/components/InnerTopic/i18n/zh-TW.js b/packages/ringcentral-widgets/components/InnerTopic/i18n/zh-TW.js new file mode 100644 index 0000000000..b46ae38eef --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/i18n/zh-TW.js @@ -0,0 +1,5 @@ +export default { + topic: "會議標題" +}; + +// @key: @#@"topic"@#@ @source: @#@"Meeting title"@#@ diff --git a/packages/ringcentral-widgets/components/InnerTopic/index.tsx b/packages/ringcentral-widgets/components/InnerTopic/index.tsx new file mode 100644 index 0000000000..3abcdda9f1 --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/index.tsx @@ -0,0 +1,66 @@ +import React, { FC, useState, useRef, useEffect } from 'react'; +import { RcTextField } from '@ringcentral/juno'; +import i18n from './i18n'; +import styles from './styles.scss'; + +export const InnerTopic: FC<{ + name: string; + currentLocale: string; + defaultTopic: string; + setTopicRef: (ref: any) => void; + updateMeetingTopic: (name: string) => void; +}> = ({ + name, + currentLocale, + defaultTopic, + setTopicRef, + updateMeetingTopic, +}) => { + const [topic, setTopic] = useState(name); + const [isTopicChange, setIsTopicChange] = useState(false); + const topicRef = useRef(); + + useEffect(() => { + setTopic(name); + setTopicRef(topicRef); + }, [name, setTopicRef]); + + useEffect(() => { + if (!isTopicChange) { + setTopic(defaultTopic); + setTopicRef(topicRef); + } + }, [defaultTopic, isTopicChange, setTopicRef]); + + return ( + { + setIsTopicChange(true); + setTopic(e.target.value); + }} + onBlur={() => { + updateMeetingTopic(topic); + }} + classes={{ + root: styles.input, + }} + gutterBottom + /> + ); +}; + +export const Topic = React.memo( + InnerTopic, + (prevProps, nextProps) => + prevProps.name === nextProps.name && + prevProps.currentLocale === nextProps.currentLocale, +); diff --git a/packages/ringcentral-widgets/components/InnerTopic/styles.scss b/packages/ringcentral-widgets/components/InnerTopic/styles.scss new file mode 100644 index 0000000000..b0c1186964 --- /dev/null +++ b/packages/ringcentral-widgets/components/InnerTopic/styles.scss @@ -0,0 +1,5 @@ +@import '../../lib/commonStyles/fonts'; + +.input { + font-size: $primary-font-size !important; +} \ No newline at end of file diff --git a/packages/ringcentral-widgets/components/InputSelect/InputSelect.tsx b/packages/ringcentral-widgets/components/InputSelect/InputSelect.tsx index 228651d59d..fea647f0d5 100644 --- a/packages/ringcentral-widgets/components/InputSelect/InputSelect.tsx +++ b/packages/ringcentral-widgets/components/InputSelect/InputSelect.tsx @@ -105,6 +105,7 @@ export default class InputSelect extends Component< = ({ isRinging && styles.ringing, className, )} + data-sign={`shinyBar-${status}`} >
    diff --git a/packages/ringcentral-widgets/components/LogNotificationV2/styles.scss b/packages/ringcentral-widgets/components/LogNotificationV2/styles.scss index 2db968d875..f0348f2eef 100644 --- a/packages/ringcentral-widgets/components/LogNotificationV2/styles.scss +++ b/packages/ringcentral-widgets/components/LogNotificationV2/styles.scss @@ -33,13 +33,21 @@ Notice: the media query(200px) is for salesforce classics mode display: flex; flex-direction: column; line-height: 1.5; + max-width: calc(100% - 30px); + overflow: hidden; + text-overflow: ellipsis; + .contactName { font-weight: 500; color: #212121; + overflow: hidden; + text-overflow: ellipsis; } .phoneNumber { color: #757575; font-size: 12px; + overflow: hidden; + text-overflow: ellipsis; } @media screen and(max-width: 200px) { .contactName { diff --git a/packages/ringcentral-widgets/components/MeetingConfigs/MeetingIdSection.tsx b/packages/ringcentral-widgets/components/MeetingConfigs/MeetingIdSection.tsx index fbb4b1735b..77a0189ee0 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigs/MeetingIdSection.tsx +++ b/packages/ringcentral-widgets/components/MeetingConfigs/MeetingIdSection.tsx @@ -66,7 +66,7 @@ export const MeetingIdSection: FunctionComponent = ({ {meeting.usePersonalMeetingId && !isChangePmiConfirmed ? (
    {i18n.getString('pmiChangeConfirm', currentLocale)} - handlePmiConfirmed(true)}> + handlePmiConfirmed(true)}> {i18n.getString('changePmiSettings', currentLocale)} . diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/de-DE.ts b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/de-DE.ts index 1d8a1923ef..ba044f0389 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/de-DE.ts +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/de-DE.ts @@ -23,6 +23,7 @@ export default { turnOffCamera: "Kamera für Teilnehmer ausschalten", turnOffHostCamera: "Kamera für Gastgeber bei Teilnahme an Meeting deaktivieren", requirePassword: "Kennwort erforderlich", + enterPassword: "Kennwort eingeben", setPassword: "Kennwort festlegen *", passwordEmptyError: "Besprechungskennwort erforderlich", rcmPasswordInvalidError: "Ihr Passwort muss 1–10 Zeichen oder Ziffern lang sein und darf keine Symbole außer @, * oder - enthalten", @@ -31,7 +32,7 @@ export default { pmiChangeConfirm: "Wenn Sie an Ihrem persönlichen Meeting Änderungen vornehmen möchten, ", changePmiSettings: "ändern Sie die PMI-Einstellungen", pmiSettingChangeAlert: "Wenn Sie die Einstellungen ändern und dieses Meeting ansetzen, verwenden alle Meetings mit persönlicher Meeting-ID dieselben aktuellen Einstellungen.", - lockedTooltip: "Diese Einstellung wird vom Admin Ihres Unternehmens verwaltet", + lockedTooltip: "Ihr Unternehmens-Admin verwaltet diese Einstellung", when: "Wann", recurringDescribe: "Aktivieren Sie in der Kalendereinladung für die Teilnehmer die Option für Serien oder Wiederholungen." }; @@ -59,6 +60,7 @@ export default { // @key: @#@"turnOffCamera"@#@ @source: @#@"Turn off camera for participants"@#@ // @key: @#@"turnOffHostCamera"@#@ @source: @#@"Turn off camera for host when joining meeting"@#@ // @key: @#@"requirePassword"@#@ @source: @#@"Require password"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"setPassword"@#@ @source: @#@"Set password *"@#@ // @key: @#@"passwordEmptyError"@#@ @source: @#@"Meeting password required"@#@ // @key: @#@"rcmPasswordInvalidError"@#@ @source: @#@"Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -"@#@ diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/en-AU.ts b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/en-AU.ts index 7cf1c004f3..a3a36dad44 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/en-AU.ts +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/en-AU.ts @@ -23,6 +23,7 @@ export default { turnOffCamera: "Turn off camera for participants", turnOffHostCamera: "Turn off camera for host when joining meeting", requirePassword: "Require password", + enterPassword: "Enter Password", setPassword: "Set password *", passwordEmptyError: "Meeting password required", rcmPasswordInvalidError: "Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -", @@ -59,6 +60,7 @@ export default { // @key: @#@"turnOffCamera"@#@ @source: @#@"Turn off camera for participants"@#@ // @key: @#@"turnOffHostCamera"@#@ @source: @#@"Turn off camera for host when joining meeting"@#@ // @key: @#@"requirePassword"@#@ @source: @#@"Require password"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"setPassword"@#@ @source: @#@"Set password *"@#@ // @key: @#@"passwordEmptyError"@#@ @source: @#@"Meeting password required"@#@ // @key: @#@"rcmPasswordInvalidError"@#@ @source: @#@"Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -"@#@ diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/en-GB.ts b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/en-GB.ts index 7cf1c004f3..a3a36dad44 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/en-GB.ts +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/en-GB.ts @@ -23,6 +23,7 @@ export default { turnOffCamera: "Turn off camera for participants", turnOffHostCamera: "Turn off camera for host when joining meeting", requirePassword: "Require password", + enterPassword: "Enter Password", setPassword: "Set password *", passwordEmptyError: "Meeting password required", rcmPasswordInvalidError: "Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -", @@ -59,6 +60,7 @@ export default { // @key: @#@"turnOffCamera"@#@ @source: @#@"Turn off camera for participants"@#@ // @key: @#@"turnOffHostCamera"@#@ @source: @#@"Turn off camera for host when joining meeting"@#@ // @key: @#@"requirePassword"@#@ @source: @#@"Require password"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"setPassword"@#@ @source: @#@"Set password *"@#@ // @key: @#@"passwordEmptyError"@#@ @source: @#@"Meeting password required"@#@ // @key: @#@"rcmPasswordInvalidError"@#@ @source: @#@"Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -"@#@ diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/en-US.ts b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/en-US.ts index 4959b90ace..7444b0a58d 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/en-US.ts +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/en-US.ts @@ -24,6 +24,7 @@ export default { turnOffCamera: 'Turn off camera for participants', turnOffHostCamera: 'Turn off camera for host when joining meeting', requirePassword: 'Require password', + enterPassword: 'Enter Password', setPassword: 'Set password *', passwordEmptyError: 'Meeting password required', rcmPasswordInvalidError: diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/es-419.ts b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/es-419.ts index 3960aa8895..ee91f0c5a3 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/es-419.ts +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/es-419.ts @@ -23,6 +23,7 @@ export default { turnOffCamera: "Apagar la cámara de los participantes", turnOffHostCamera: "Apagar la cámara del anfitrión cuando se une a la reunión", requirePassword: "Solicitar contraseña", + enterPassword: "Ingrese la contraseña", setPassword: "Configurar contraseña *", passwordEmptyError: "Se requiere la contraseña de la reunión", rcmPasswordInvalidError: "Su contraseña debe contener de 1 a 10 caracteres o números y no puede tener símbolos, salvo @, * o -", @@ -59,6 +60,7 @@ export default { // @key: @#@"turnOffCamera"@#@ @source: @#@"Turn off camera for participants"@#@ // @key: @#@"turnOffHostCamera"@#@ @source: @#@"Turn off camera for host when joining meeting"@#@ // @key: @#@"requirePassword"@#@ @source: @#@"Require password"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"setPassword"@#@ @source: @#@"Set password *"@#@ // @key: @#@"passwordEmptyError"@#@ @source: @#@"Meeting password required"@#@ // @key: @#@"rcmPasswordInvalidError"@#@ @source: @#@"Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -"@#@ diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/es-ES.ts b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/es-ES.ts index 700bf09808..52d88f5345 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/es-ES.ts +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/es-ES.ts @@ -23,6 +23,7 @@ export default { turnOffCamera: "Desactivar la cámara para los participantes", turnOffHostCamera: "Desactivar la cámara del organizador al unirse a la reunión", requirePassword: "Solicitar contraseña", + enterPassword: "Introducir contraseña", setPassword: "Establecer contraseña *", passwordEmptyError: "Se requiere la contraseña de la reunión", rcmPasswordInvalidError: "La contraseña debe tener un máximo de 10 caracteres o números y no puede incluir símbolos, excepto “@”, “*” o “-”.", @@ -59,6 +60,7 @@ export default { // @key: @#@"turnOffCamera"@#@ @source: @#@"Turn off camera for participants"@#@ // @key: @#@"turnOffHostCamera"@#@ @source: @#@"Turn off camera for host when joining meeting"@#@ // @key: @#@"requirePassword"@#@ @source: @#@"Require password"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"setPassword"@#@ @source: @#@"Set password *"@#@ // @key: @#@"passwordEmptyError"@#@ @source: @#@"Meeting password required"@#@ // @key: @#@"rcmPasswordInvalidError"@#@ @source: @#@"Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -"@#@ diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/fr-CA.ts b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/fr-CA.ts index a907655cf0..409e678f22 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/fr-CA.ts +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/fr-CA.ts @@ -23,6 +23,7 @@ export default { turnOffCamera: "Éteindre la caméra pour les participants", turnOffHostCamera: "Désactiver la caméra de l'hôte lorsqu'il se joint au meeting", requirePassword: "Exiger un mot de passe", + enterPassword: "Saisissez le mot de passe", setPassword: "Définir le mot de passe *", passwordEmptyError: "Mot de passe de réunion requis", rcmPasswordInvalidError: "Votre mot de passe doit contenir de 1 à 10 caractères, être composé de chiffres et ne peut pas comporter de symboles autres que @, * ou -", @@ -59,6 +60,7 @@ export default { // @key: @#@"turnOffCamera"@#@ @source: @#@"Turn off camera for participants"@#@ // @key: @#@"turnOffHostCamera"@#@ @source: @#@"Turn off camera for host when joining meeting"@#@ // @key: @#@"requirePassword"@#@ @source: @#@"Require password"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"setPassword"@#@ @source: @#@"Set password *"@#@ // @key: @#@"passwordEmptyError"@#@ @source: @#@"Meeting password required"@#@ // @key: @#@"rcmPasswordInvalidError"@#@ @source: @#@"Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -"@#@ diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/fr-FR.ts b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/fr-FR.ts index a3cadd019c..1f2d2c9f90 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/fr-FR.ts +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/fr-FR.ts @@ -23,6 +23,7 @@ export default { turnOffCamera: "Désactiver la caméra pour les participants", turnOffHostCamera: "Désactiver la caméra de l'hôte lorsqu'il rejoint la réunion", requirePassword: "Exiger un mot de passe", + enterPassword: "Entrer le mot de passe", setPassword: "Définir un mot de passe *", passwordEmptyError: "Mot de passe de la réunion requis", rcmPasswordInvalidError: "Votre mot de passe doit être composé de 1 à 10 lettres et chiffres et ne peut pas comporter de symboles autres que @, * ou -", @@ -59,6 +60,7 @@ export default { // @key: @#@"turnOffCamera"@#@ @source: @#@"Turn off camera for participants"@#@ // @key: @#@"turnOffHostCamera"@#@ @source: @#@"Turn off camera for host when joining meeting"@#@ // @key: @#@"requirePassword"@#@ @source: @#@"Require password"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"setPassword"@#@ @source: @#@"Set password *"@#@ // @key: @#@"passwordEmptyError"@#@ @source: @#@"Meeting password required"@#@ // @key: @#@"rcmPasswordInvalidError"@#@ @source: @#@"Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -"@#@ diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/it-IT.ts b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/it-IT.ts index 9862ee10ba..e14538c303 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/it-IT.ts +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/it-IT.ts @@ -23,6 +23,7 @@ export default { turnOffCamera: "Spegni la videocamera per i partecipanti", turnOffHostCamera: "Spegni la videocamera per l'host durante l'accesso alla riunione", requirePassword: "Richiedi password", + enterPassword: "Inserisci password", setPassword: "Imposta la password *", passwordEmptyError: "La password per la riunione è obbligatoria", rcmPasswordInvalidError: "La password deve avere 1-10 caratteri, deve contenere numeri e non può contenere simboli, tranne @, * o -", @@ -31,7 +32,7 @@ export default { pmiChangeConfirm: "Se vuoi apportare modifiche alla tua riunione personale, ", changePmiSettings: "modifica le impostazioni del PMI", pmiSettingChangeAlert: "Se modifichi le impostazioni e programmi questa riunione, tutte le riunioni con ID riunione personale utilizzeranno le stesse impostazioni più recenti.", - lockedTooltip: "Questa impostazione è gestita dall'amministratore della tua azienda", + lockedTooltip: "L'impostazione è gestita dall'amministratore dell'azienda", when: "Quando", recurringDescribe: "Ricorda di verificare la ricorrenza o la ripetizione dell'invito sul calendario ai tuoi partecipanti." }; @@ -59,6 +60,7 @@ export default { // @key: @#@"turnOffCamera"@#@ @source: @#@"Turn off camera for participants"@#@ // @key: @#@"turnOffHostCamera"@#@ @source: @#@"Turn off camera for host when joining meeting"@#@ // @key: @#@"requirePassword"@#@ @source: @#@"Require password"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"setPassword"@#@ @source: @#@"Set password *"@#@ // @key: @#@"passwordEmptyError"@#@ @source: @#@"Meeting password required"@#@ // @key: @#@"rcmPasswordInvalidError"@#@ @source: @#@"Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -"@#@ diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/ja-JP.ts b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/ja-JP.ts index aa090bab24..befba651de 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/ja-JP.ts +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/ja-JP.ts @@ -23,6 +23,7 @@ export default { turnOffCamera: "参加者のカメラをオフにする", turnOffHostCamera: "会議への参加時にホストのカメラをオフにする", requirePassword: "パスワードを必須にする", + enterPassword: "パスワードを入力", setPassword: "パスワードを設定*", passwordEmptyError: "会議パスワードが必要です", rcmPasswordInvalidError: "パスワードの長さは1~10文字で、文字および数字と@*-のみ使用できます", @@ -59,6 +60,7 @@ export default { // @key: @#@"turnOffCamera"@#@ @source: @#@"Turn off camera for participants"@#@ // @key: @#@"turnOffHostCamera"@#@ @source: @#@"Turn off camera for host when joining meeting"@#@ // @key: @#@"requirePassword"@#@ @source: @#@"Require password"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"setPassword"@#@ @source: @#@"Set password *"@#@ // @key: @#@"passwordEmptyError"@#@ @source: @#@"Meeting password required"@#@ // @key: @#@"rcmPasswordInvalidError"@#@ @source: @#@"Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -"@#@ diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/ko-KR.js b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/ko-KR.js index 8b2183c221..04e5d636a5 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/ko-KR.js +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/ko-KR.js @@ -23,6 +23,7 @@ export default { turnOffCamera: "참가자의 카메라 끄기", turnOffHostCamera: "모임에 참가할 때 호스트용 카메라 끄기", requirePassword: "비밀번호가 필요함", + enterPassword: "비밀번호 입력", setPassword: "비밀번호 설정 *", passwordEmptyError: "모임 비밀번호 필요", rcmPasswordInvalidError: "비밀번호는 1~10자 길이의 문자, 숫자여야 하며 @, * or -를 제외한 기호를 포함할 수 없습니다.", @@ -59,6 +60,7 @@ export default { // @key: @#@"turnOffCamera"@#@ @source: @#@"Turn off camera for participants"@#@ // @key: @#@"turnOffHostCamera"@#@ @source: @#@"Turn off camera for host when joining meeting"@#@ // @key: @#@"requirePassword"@#@ @source: @#@"Require password"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"setPassword"@#@ @source: @#@"Set password *"@#@ // @key: @#@"passwordEmptyError"@#@ @source: @#@"Meeting password required"@#@ // @key: @#@"rcmPasswordInvalidError"@#@ @source: @#@"Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -"@#@ diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/nl-NL.js b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/nl-NL.js index b9651778bf..9e51b38a2a 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/nl-NL.js +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/nl-NL.js @@ -23,6 +23,7 @@ export default { turnOffCamera: "Camera uitschakelen voor deelnemers", turnOffHostCamera: "Schakel camera uit voor host wanneer u deelneemt aan een meeting", requirePassword: "Wachtwoord verplichten", + enterPassword: "Wachtwoord invoeren", setPassword: "Wachtwoord instellen *", passwordEmptyError: "Wachtwoord voor meeting verplicht", rcmPasswordInvalidError: "Uw wachtwoord moet 1-10 tekens en cijfers lang zijn en mag geen symbolen bevatten, behalve @, * of -", @@ -59,6 +60,7 @@ export default { // @key: @#@"turnOffCamera"@#@ @source: @#@"Turn off camera for participants"@#@ // @key: @#@"turnOffHostCamera"@#@ @source: @#@"Turn off camera for host when joining meeting"@#@ // @key: @#@"requirePassword"@#@ @source: @#@"Require password"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"setPassword"@#@ @source: @#@"Set password *"@#@ // @key: @#@"passwordEmptyError"@#@ @source: @#@"Meeting password required"@#@ // @key: @#@"rcmPasswordInvalidError"@#@ @source: @#@"Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -"@#@ diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/pt-BR.ts b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/pt-BR.ts index 4596ffba7b..e21f283324 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/pt-BR.ts +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/pt-BR.ts @@ -23,6 +23,7 @@ export default { turnOffCamera: "Desativar a câmera dos participantes", turnOffHostCamera: "Desligar a câmera do host ao entrar na reunião", requirePassword: "Exigir senha", + enterPassword: "Insira a senha", setPassword: "Definir senha *", passwordEmptyError: "Necessária senha da reunião", rcmPasswordInvalidError: "Sua senha deve ter de 1 a 10 caracteres ou números e não pode conter símbolos, exceto @, * ou -", @@ -31,7 +32,7 @@ export default { pmiChangeConfirm: "Se quiser fazer alterações em sua reunião pessoal, ", changePmiSettings: "altere as configurações de PMI.", pmiSettingChangeAlert: "Se você alterar as configurações e agendar esta reunião, todas as reuniões com ID da reunião pessoal usarão essas mesmas configurações mais recentes.", - lockedTooltip: "Esta configuração é gerenciada pelo administrador da sua empresa", + lockedTooltip: "Configuração gerenciada pelo administrador da empresa.", when: "Quando", recurringDescribe: "Lembre-se de verificar a recorrência ou repetir no convite de calendário para os participantes." }; @@ -59,6 +60,7 @@ export default { // @key: @#@"turnOffCamera"@#@ @source: @#@"Turn off camera for participants"@#@ // @key: @#@"turnOffHostCamera"@#@ @source: @#@"Turn off camera for host when joining meeting"@#@ // @key: @#@"requirePassword"@#@ @source: @#@"Require password"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"setPassword"@#@ @source: @#@"Set password *"@#@ // @key: @#@"passwordEmptyError"@#@ @source: @#@"Meeting password required"@#@ // @key: @#@"rcmPasswordInvalidError"@#@ @source: @#@"Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -"@#@ diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/pt-PT.js b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/pt-PT.js index 63530a1509..aacd1377bb 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/pt-PT.js +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/pt-PT.js @@ -23,6 +23,7 @@ export default { turnOffCamera: "Desligar a câmara dos participantes", turnOffHostCamera: "Desligar a câmara do anfitrião ao entrar na reunião", requirePassword: "Requer palavra-passe", + enterPassword: "Introduzir palavra-passe", setPassword: "Definir palavra-passe *", passwordEmptyError: "Palavra-passe da reunião obrigatória", rcmPasswordInvalidError: "A palavra-passe tem de ter 1–10 carateres e números e não pode conter símbolos exceto @, * ou -", @@ -59,6 +60,7 @@ export default { // @key: @#@"turnOffCamera"@#@ @source: @#@"Turn off camera for participants"@#@ // @key: @#@"turnOffHostCamera"@#@ @source: @#@"Turn off camera for host when joining meeting"@#@ // @key: @#@"requirePassword"@#@ @source: @#@"Require password"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"setPassword"@#@ @source: @#@"Set password *"@#@ // @key: @#@"passwordEmptyError"@#@ @source: @#@"Meeting password required"@#@ // @key: @#@"rcmPasswordInvalidError"@#@ @source: @#@"Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -"@#@ diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/zh-CN.ts b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/zh-CN.ts index 5a5d194e12..6d602be4ea 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/zh-CN.ts +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/zh-CN.ts @@ -23,6 +23,7 @@ export default { turnOffCamera: "关闭参与者的摄像头", turnOffHostCamera: "加入会议时关闭主持人的摄像头", requirePassword: "需要输入密码", + enterPassword: "输入密码", setPassword: "设置密码 *", passwordEmptyError: "会议密码为必填项", rcmPasswordInvalidError: "密码必须包含 1 到 10 个字符和数字,且不能使用除 @、* 或 - 以外的特殊符号", @@ -59,6 +60,7 @@ export default { // @key: @#@"turnOffCamera"@#@ @source: @#@"Turn off camera for participants"@#@ // @key: @#@"turnOffHostCamera"@#@ @source: @#@"Turn off camera for host when joining meeting"@#@ // @key: @#@"requirePassword"@#@ @source: @#@"Require password"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"setPassword"@#@ @source: @#@"Set password *"@#@ // @key: @#@"passwordEmptyError"@#@ @source: @#@"Meeting password required"@#@ // @key: @#@"rcmPasswordInvalidError"@#@ @source: @#@"Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -"@#@ diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/zh-HK.ts b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/zh-HK.ts index 64ca2a9610..a07936cf88 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/zh-HK.ts +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/zh-HK.ts @@ -23,6 +23,7 @@ export default { turnOffCamera: "關閉參與者的相機", turnOffHostCamera: "主持人加入會議時請關閉攝影機", requirePassword: "需要密碼", + enterPassword: "輸入密碼", setPassword: "請設定密碼 *", passwordEmptyError: "必須提供會議密碼", rcmPasswordInvalidError: "您的密碼必須包含 1 到 10 個字元和數字,並且不得包含任何符號 (除 @、* 或 - 以外)", @@ -59,6 +60,7 @@ export default { // @key: @#@"turnOffCamera"@#@ @source: @#@"Turn off camera for participants"@#@ // @key: @#@"turnOffHostCamera"@#@ @source: @#@"Turn off camera for host when joining meeting"@#@ // @key: @#@"requirePassword"@#@ @source: @#@"Require password"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"setPassword"@#@ @source: @#@"Set password *"@#@ // @key: @#@"passwordEmptyError"@#@ @source: @#@"Meeting password required"@#@ // @key: @#@"rcmPasswordInvalidError"@#@ @source: @#@"Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -"@#@ diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/zh-TW.ts b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/zh-TW.ts index 64ca2a9610..a07936cf88 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/zh-TW.ts +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/i18n/zh-TW.ts @@ -23,6 +23,7 @@ export default { turnOffCamera: "關閉參與者的相機", turnOffHostCamera: "主持人加入會議時請關閉攝影機", requirePassword: "需要密碼", + enterPassword: "輸入密碼", setPassword: "請設定密碼 *", passwordEmptyError: "必須提供會議密碼", rcmPasswordInvalidError: "您的密碼必須包含 1 到 10 個字元和數字,並且不得包含任何符號 (除 @、* 或 - 以外)", @@ -59,6 +60,7 @@ export default { // @key: @#@"turnOffCamera"@#@ @source: @#@"Turn off camera for participants"@#@ // @key: @#@"turnOffHostCamera"@#@ @source: @#@"Turn off camera for host when joining meeting"@#@ // @key: @#@"requirePassword"@#@ @source: @#@"Require password"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"setPassword"@#@ @source: @#@"Set password *"@#@ // @key: @#@"passwordEmptyError"@#@ @source: @#@"Meeting password required"@#@ // @key: @#@"rcmPasswordInvalidError"@#@ @source: @#@"Your password must be 1-10 characters, numbers long and cannot have symbols except @, * or -"@#@ diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/index.tsx b/packages/ringcentral-widgets/components/MeetingConfigsV2/index.tsx index f48ada9353..b8b7cca5b8 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/index.tsx +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/index.tsx @@ -1,38 +1,37 @@ import { RcAlert, - RcBoxSelect, RcCheckbox, + RcDatePicker, + RcDatePickerProps, RcIcon, RcLink, RcMenuItem, - RcOutlineTextField, - RcTypography, - RcDatePicker, - RcDatePickerProps, + RcSelect, + RcTextField, RcTimePicker, RcTimePickerProps, - RcLineSelect, - RcTextField, + RcTypography, } from '@ringcentral/juno'; -import { reduce } from 'ramda'; import classnames from 'classnames'; -import React, { useEffect, useRef, useState, useMemo } from 'react'; +import { reduce } from 'ramda'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { generateRandomPassword, - updateFullYear, updateFullTime, + updateFullYear, } from 'ringcentral-integration/helpers/meetingHelper'; import { + ASSISTED_USERS_MYSELF, isRecurringMeeting, MeetingType, - ASSISTED_USERS_MYSELF, } from 'ringcentral-integration/modules/Meeting'; import { - RcMMeetingModel, MeetingDelegator, + RcMMeetingModel, } from 'ringcentral-integration/modules/MeetingV2'; import { formatMeetingId } from '../../lib/MeetingCalendarHelper'; +import { Topic } from '../InnerTopic'; import { SpinnerOverlay } from '../SpinnerOverlay'; import { ExtendedTooltip as MeetingOptionLocked } from './ExtendedTooltip'; import i18n from './i18n'; @@ -70,6 +69,7 @@ export interface MeetingConfigsProps { datePickerSize?: RcDatePickerProps['size']; timePickerSize?: RcTimePickerProps['size']; putRecurringMeetingInMiddle?: boolean; + defaultTopic: string; } export function getMinutesList(MINUTE_SCALE: number) { return reduce( @@ -147,6 +147,7 @@ const MeetingOptionLabel: React.FunctionComponent<{ hasScrollBar?: boolean; className?: string; labelPlacement?: string; + dataSign?: string; }> = ({ children, labelPlacement, @@ -154,10 +155,12 @@ const MeetingOptionLabel: React.FunctionComponent<{ currentLocale, hasScrollBar = false, className = '', + dataSign = '', }) => { return (
    {i18n.getString('lockedTooltip', currentLocale)} - } + title={i18n.getString('lockedTooltip', currentLocale)} > @@ -207,6 +209,7 @@ export const MeetingConfigs: React.FunctionComponent = ({ showSpinnerInConfigPanel, enableServiceWebSettings, putRecurringMeetingInMiddle, + defaultTopic, }) => { useEffect(() => { if (init) { @@ -309,6 +312,7 @@ export const MeetingConfigs: React.FunctionComponent = ({ topic, }); }} + defaultTopic={defaultTopic} currentLocale={currentLocale} setTopicRef={setTopicRef} /> @@ -361,7 +365,8 @@ export const MeetingConfigs: React.FunctionComponent = ({ {showDuration && !isRecurring ? (
    - { @@ -392,10 +397,11 @@ export const MeetingConfigs: React.FunctionComponent = ({ {item !== null ? item.text : 'defaultValue'} ))} - +
    - = ({ {item !== null ? item.text : 'defaultValue'} ))} - +
    ) : null} @@ -445,7 +451,10 @@ export const MeetingConfigs: React.FunctionComponent = ({ toggleRecurring(!isRecurring); }} label={ - + {i18n.getString('recurringMeeting', currentLocale)} } @@ -469,14 +478,12 @@ export const MeetingConfigs: React.FunctionComponent = ({
    - { updateScheduleFor(e.target.value as string); }} @@ -498,7 +505,7 @@ export const MeetingConfigs: React.FunctionComponent = ({ ); })} - +
    ) : null} @@ -519,6 +526,7 @@ export const MeetingConfigs: React.FunctionComponent = ({ }} label={ @@ -538,7 +546,7 @@ export const MeetingConfigs: React.FunctionComponent = ({ <> {i18n.getString('pmiChangeConfirm', currentLocale)} setPmiConfirm(!isPmiConfirm)} + onClick={() => setPmiConfirm(!isPmiConfirm)} data-sign="setPmiConfirm" > {i18n.getString('changePmiSettings', currentLocale)} @@ -578,6 +586,7 @@ export const MeetingConfigs: React.FunctionComponent = ({ }} label={ = ({ }, )} > - = ({ }} label={ = ({ }} label={ = ({
    - = ({ classes={{ root: styles.boxSelectWrapper, }} - className={styles.autoFullWidth} - automationId="audioOptions" + className={classnames(styles.boxSelect, styles.autoFullWidth)} onChange={(e) => { updateAudioOptions(e.target.value as string); }} value={audioOptions} > {i18n.getString('telephonyOnly', currentLocale)} {i18n.getString('voIPOnly', currentLocale)} {i18n.getString('both', currentLocale)} - +
    {enableServiceWebSettings && meeting.settingLock?.audioOptions ? (
    = ({ )} > - {i18n.getString('lockedTooltip', currentLocale)} - - } + title={i18n.getString('lockedTooltip', currentLocale)} > @@ -785,6 +800,7 @@ export const MeetingConfigs: React.FunctionComponent = ({ }} label={ = ({ toggleRecurring(!isRecurring); }} label={ - + {i18n.getString('recurringMeeting', currentLocale)} } @@ -836,47 +855,3 @@ MeetingConfigs.defaultProps = { datePickerSize: 'medium', timePickerSize: 'medium', }; - -const InnerTopic: React.FunctionComponent<{ - name: string; - currentLocale: string; - setTopicRef: (ref: any) => void; - updateMeetingTopic: (name: string) => void; -}> = ({ name, currentLocale, setTopicRef, updateMeetingTopic }) => { - const [topic, setTopic] = useState(name); - const topicRef = useRef(); - useEffect(() => { - setTopic(name); - setTopicRef(topicRef); - }, [name, setTopicRef]); - return ( - { - setTopic(e.target.value); - }} - onBlur={() => { - updateMeetingTopic(topic); - }} - classes={{ - root: styles.input, - }} - /> - ); -}; - -export const Topic = React.memo( - InnerTopic, - (prevProps, nextProps) => - prevProps.name === nextProps.name && - prevProps.currentLocale === nextProps.currentLocale, -); diff --git a/packages/ringcentral-widgets/components/MeetingConfigsV2/styles.scss b/packages/ringcentral-widgets/components/MeetingConfigsV2/styles.scss index 112bd71c83..e4523423ec 100644 --- a/packages/ringcentral-widgets/components/MeetingConfigsV2/styles.scss +++ b/packages/ringcentral-widgets/components/MeetingConfigsV2/styles.scss @@ -184,10 +184,6 @@ div.expansionPanel div.toggleGroup label.labelPlacementEnd { width: 100% !important; } input { - // TODO: will remove it after Juno updated - &[class*="Mui-disabled"] { - -webkit-text-fill-color: rc-palette(text, 'disabled'); - } @extend .normalFontSize; } input::-ms-clear { @@ -216,16 +212,20 @@ div.expansionPanel div.toggleGroup label.labelPlacementEnd { } } -.scheduleForBoxSelect { - & > div > div { - display: block !important; - line-height: 18px; - @extend .normalFontSize; +.boxSelect { + & > div { + margin-top: 0 !important; + & > div { + display: block !important; + line-height: 18px; + @extend .normalFontSize; + } } } .autoFullWidth { max-width: none !important; + width: 100% !important; & > div { @extend .forceFullWidth; } diff --git a/packages/ringcentral-widgets/components/NotificationPanel/styles.scss b/packages/ringcentral-widgets/components/NotificationPanel/styles.scss index 92b4b9272b..f2f336b822 100644 --- a/packages/ringcentral-widgets/components/NotificationPanel/styles.scss +++ b/packages/ringcentral-widgets/components/NotificationPanel/styles.scss @@ -24,7 +24,7 @@ $root-zIndex: 100; .container { display: flex; flex-direction: column; - margin: 0 16px; + margin: 0 20px; &:first-child { margin-top: 12px; diff --git a/packages/ringcentral-widgets/components/RecipientsInput/RecipientsInput.tsx b/packages/ringcentral-widgets/components/RecipientsInput/RecipientsInput.tsx index 022551562a..66aea09833 100644 --- a/packages/ringcentral-widgets/components/RecipientsInput/RecipientsInput.tsx +++ b/packages/ringcentral-widgets/components/RecipientsInput/RecipientsInput.tsx @@ -168,6 +168,7 @@ class RecipientsInput extends Component< phoneNumber: this.state.value.replace(',', ''), }); } + this.setState({ value: '' }); } }; } diff --git a/packages/ringcentral-widgets/components/SearchPanel/SearchPanel.tsx b/packages/ringcentral-widgets/components/SearchPanel/SearchPanel.tsx index 4c95a8606d..41e056dd39 100644 --- a/packages/ringcentral-widgets/components/SearchPanel/SearchPanel.tsx +++ b/packages/ringcentral-widgets/components/SearchPanel/SearchPanel.tsx @@ -1,12 +1,12 @@ +import { RcIcon, RcTextField } from '@ringcentral/juno'; import searchSvg from '@ringcentral/juno/icon/Search'; -import React, { useState, FunctionComponent, useContext } from 'react'; -import { RcOutlineTextField } from '@ringcentral/juno'; import classNames from 'classnames'; -import { SearchResult, SearchResultProps } from './SearchResult'; -import { SelectListContext } from '../../contexts'; +import React, { FunctionComponent, useContext, useState } from 'react'; -import styles from './styles.scss'; +import { SelectListContext } from '../../contexts'; import i18n from './i18n'; +import { SearchResult, SearchResultProps } from './SearchResult'; +import styles from './styles.scss'; interface SearchPanelClasses { root?: string; @@ -46,11 +46,17 @@ export const SearchPanel: FunctionComponent = ({ {placeholder || i18n.getString('search', currentLocale)} )} - + ), + }} data-sign="searchBar" onChange={( event: React.ChangeEvent, diff --git a/packages/ringcentral-widgets/components/SelectListBasic/SelectListBasic.tsx b/packages/ringcentral-widgets/components/SelectListBasic/SelectListBasic.tsx index 9a6b8112a5..4834989793 100644 --- a/packages/ringcentral-widgets/components/SelectListBasic/SelectListBasic.tsx +++ b/packages/ringcentral-widgets/components/SelectListBasic/SelectListBasic.tsx @@ -1,4 +1,4 @@ -import { RcOutlineTextField } from '@ringcentral/juno'; +import { RcIcon, RcTextField } from '@ringcentral/juno'; import searchSvg from '@ringcentral/juno/icon/Search'; import classNames from 'classnames'; import formatMessage from 'format-message'; @@ -170,11 +170,20 @@ const SelectListBasic: FunctionComponent = ({ {placeholder} )} - + ), + }} data-sign="searchBar" onChange={(event: any) => { if (event.target) { diff --git a/packages/ringcentral-widgets/components/SettingsPanel/SettingsPanel.interface.ts b/packages/ringcentral-widgets/components/SettingsPanel/SettingsPanel.interface.ts index 5fc1a4c6d6..ca32bd56c5 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/SettingsPanel.interface.ts +++ b/packages/ringcentral-widgets/components/SettingsPanel/SettingsPanel.interface.ts @@ -52,6 +52,11 @@ export interface FeedbackProps { onFeedbackSettingsLinkClick?(): any; } +export interface ShareIdeaProps { + showShareIdea?: boolean; + onShareIdeaClick?(): any; +} + export interface QuickAccessLinkProps { showQuickAccess?: boolean; onQuickAccessLinkClick?(): any; diff --git a/packages/ringcentral-widgets/components/SettingsPanel/SettingsPanel.tsx b/packages/ringcentral-widgets/components/SettingsPanel/SettingsPanel.tsx index ee9b404760..b4a04c7bb9 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/SettingsPanel.tsx +++ b/packages/ringcentral-widgets/components/SettingsPanel/SettingsPanel.tsx @@ -27,6 +27,7 @@ import { FeedbackProps, QuickAccessLinkProps, UserGuideProps, + ShareIdeaProps, } from './SettingsPanel.interface'; export interface SettingsPanelProps @@ -47,6 +48,7 @@ export interface SettingsPanelProps QuickAccessLinkProps, UserGuideProps, PresenceSettingProps, + ShareIdeaProps, EulaRenderer { children?: ReactNode; currentLocale: string; @@ -68,6 +70,7 @@ const SettingsPanel: FunctionComponent = ({ onFeedbackSettingsLinkClick, onQuickAccessLinkClick, onUserGuideClick, + onShareIdeaClick, showCalling, showAutoLog, showAutoLogNotes, @@ -107,6 +110,7 @@ const SettingsPanel: FunctionComponent = ({ showFeedback, showQuickAccess, showUserGuide, + showShareIdea, additional, supportedLocales, savedLocale, @@ -227,6 +231,12 @@ const SettingsPanel: FunctionComponent = ({ currentLocale={currentLocale} onClick={onFeedbackSettingsLinkClick} /> + null, diff --git a/packages/ringcentral-widgets/components/SettingsPanel/i18n/de-DE.js b/packages/ringcentral-widgets/components/SettingsPanel/i18n/de-DE.js index 82d29e5a38..41f4be6538 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/i18n/de-DE.js +++ b/packages/ringcentral-widgets/components/SettingsPanel/i18n/de-DE.js @@ -18,7 +18,8 @@ export default { feedback: "Feedback", userGuide: "Neuigkeiten", quickAccess: "Einstellung für Schnellzugriff", - report: "Analysebericht" + report: "Analysebericht", + shareIdea: "Idee teilen" }; // @key: @#@"region"@#@ @source: @#@"Region"@#@ @@ -41,3 +42,4 @@ export default { // @key: @#@"userGuide"@#@ @source: @#@"What's New"@#@ // @key: @#@"quickAccess"@#@ @source: @#@"Quick Access Setting"@#@ // @key: @#@"report"@#@ @source: @#@"Analytics Report"@#@ +// @key: @#@"shareIdea"@#@ @source: @#@"Share idea"@#@ diff --git a/packages/ringcentral-widgets/components/SettingsPanel/i18n/en-AU.js b/packages/ringcentral-widgets/components/SettingsPanel/i18n/en-AU.js index 9b60e6e410..b16b10c2a1 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/i18n/en-AU.js +++ b/packages/ringcentral-widgets/components/SettingsPanel/i18n/en-AU.js @@ -18,7 +18,8 @@ export default { feedback: "Feedback", userGuide: "What's new", quickAccess: "Quick access setting", - report: "Analytics report" + report: "Analytics report", + shareIdea: "Share idea" }; // @key: @#@"region"@#@ @source: @#@"Region"@#@ @@ -41,3 +42,4 @@ export default { // @key: @#@"userGuide"@#@ @source: @#@"What's New"@#@ // @key: @#@"quickAccess"@#@ @source: @#@"Quick Access Setting"@#@ // @key: @#@"report"@#@ @source: @#@"Analytics Report"@#@ +// @key: @#@"shareIdea"@#@ @source: @#@"Share idea"@#@ diff --git a/packages/ringcentral-widgets/components/SettingsPanel/i18n/en-GB.js b/packages/ringcentral-widgets/components/SettingsPanel/i18n/en-GB.js index 9b60e6e410..b16b10c2a1 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/i18n/en-GB.js +++ b/packages/ringcentral-widgets/components/SettingsPanel/i18n/en-GB.js @@ -18,7 +18,8 @@ export default { feedback: "Feedback", userGuide: "What's new", quickAccess: "Quick access setting", - report: "Analytics report" + report: "Analytics report", + shareIdea: "Share idea" }; // @key: @#@"region"@#@ @source: @#@"Region"@#@ @@ -41,3 +42,4 @@ export default { // @key: @#@"userGuide"@#@ @source: @#@"What's New"@#@ // @key: @#@"quickAccess"@#@ @source: @#@"Quick Access Setting"@#@ // @key: @#@"report"@#@ @source: @#@"Analytics Report"@#@ +// @key: @#@"shareIdea"@#@ @source: @#@"Share idea"@#@ diff --git a/packages/ringcentral-widgets/components/SettingsPanel/i18n/en-US.js b/packages/ringcentral-widgets/components/SettingsPanel/i18n/en-US.js index e45efa1622..2c577a984f 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/i18n/en-US.js +++ b/packages/ringcentral-widgets/components/SettingsPanel/i18n/en-US.js @@ -17,6 +17,7 @@ export default { language: 'Language', feedback: 'Feedback', userGuide: "What's New", - quickAccess: "Quick Access Setting", - report: "Analytics Report", + quickAccess: 'Quick Access Setting', + report: 'Analytics Report', + shareIdea: 'Share idea', }; diff --git a/packages/ringcentral-widgets/components/SettingsPanel/i18n/es-419.js b/packages/ringcentral-widgets/components/SettingsPanel/i18n/es-419.js index 74abd626b5..c507b3d2c4 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/i18n/es-419.js +++ b/packages/ringcentral-widgets/components/SettingsPanel/i18n/es-419.js @@ -18,7 +18,8 @@ export default { feedback: "Comentarios", userGuide: "Novedades", quickAccess: "Configuración de acceso rápido", - report: "Informe de análisis" + report: "Informe de análisis", + shareIdea: "Compartir una idea" }; // @key: @#@"region"@#@ @source: @#@"Region"@#@ @@ -41,3 +42,4 @@ export default { // @key: @#@"userGuide"@#@ @source: @#@"What's New"@#@ // @key: @#@"quickAccess"@#@ @source: @#@"Quick Access Setting"@#@ // @key: @#@"report"@#@ @source: @#@"Analytics Report"@#@ +// @key: @#@"shareIdea"@#@ @source: @#@"Share idea"@#@ diff --git a/packages/ringcentral-widgets/components/SettingsPanel/i18n/es-ES.js b/packages/ringcentral-widgets/components/SettingsPanel/i18n/es-ES.js index 3d7f79cf97..782a38fb8c 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/i18n/es-ES.js +++ b/packages/ringcentral-widgets/components/SettingsPanel/i18n/es-ES.js @@ -18,7 +18,8 @@ export default { feedback: "Comentarios", userGuide: "Novedades", quickAccess: "Configuración de acceso rápido", - report: "Informe de análisis" + report: "Informe de análisis", + shareIdea: "Compartir idea" }; // @key: @#@"region"@#@ @source: @#@"Region"@#@ @@ -41,3 +42,4 @@ export default { // @key: @#@"userGuide"@#@ @source: @#@"What's New"@#@ // @key: @#@"quickAccess"@#@ @source: @#@"Quick Access Setting"@#@ // @key: @#@"report"@#@ @source: @#@"Analytics Report"@#@ +// @key: @#@"shareIdea"@#@ @source: @#@"Share idea"@#@ diff --git a/packages/ringcentral-widgets/components/SettingsPanel/i18n/fr-CA.js b/packages/ringcentral-widgets/components/SettingsPanel/i18n/fr-CA.js index 2e1dda5eda..a3edd01227 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/i18n/fr-CA.js +++ b/packages/ringcentral-widgets/components/SettingsPanel/i18n/fr-CA.js @@ -18,7 +18,8 @@ export default { feedback: "Commentaires", userGuide: "Nouveautés", quickAccess: "Paramètre d'accès rapide", - report: "Rapport d'analyse" + report: "Rapport d'analyse", + shareIdea: "Partager une idée" }; // @key: @#@"region"@#@ @source: @#@"Region"@#@ @@ -41,3 +42,4 @@ export default { // @key: @#@"userGuide"@#@ @source: @#@"What's New"@#@ // @key: @#@"quickAccess"@#@ @source: @#@"Quick Access Setting"@#@ // @key: @#@"report"@#@ @source: @#@"Analytics Report"@#@ +// @key: @#@"shareIdea"@#@ @source: @#@"Share idea"@#@ diff --git a/packages/ringcentral-widgets/components/SettingsPanel/i18n/fr-FR.js b/packages/ringcentral-widgets/components/SettingsPanel/i18n/fr-FR.js index b8a88ff94d..eafe050d39 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/i18n/fr-FR.js +++ b/packages/ringcentral-widgets/components/SettingsPanel/i18n/fr-FR.js @@ -18,7 +18,8 @@ export default { feedback: "Commentaires", userGuide: "Nouveautés", quickAccess: "Paramètre d'accès rapide", - report: "Rapport analytique" + report: "Rapport analytique", + shareIdea: "Partager une idée" }; // @key: @#@"region"@#@ @source: @#@"Region"@#@ @@ -41,3 +42,4 @@ export default { // @key: @#@"userGuide"@#@ @source: @#@"What's New"@#@ // @key: @#@"quickAccess"@#@ @source: @#@"Quick Access Setting"@#@ // @key: @#@"report"@#@ @source: @#@"Analytics Report"@#@ +// @key: @#@"shareIdea"@#@ @source: @#@"Share idea"@#@ diff --git a/packages/ringcentral-widgets/components/SettingsPanel/i18n/it-IT.js b/packages/ringcentral-widgets/components/SettingsPanel/i18n/it-IT.js index 8c902dc3ee..0ef1bbf810 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/i18n/it-IT.js +++ b/packages/ringcentral-widgets/components/SettingsPanel/i18n/it-IT.js @@ -18,7 +18,8 @@ export default { feedback: "Feedback", userGuide: "Novità", quickAccess: "Impostazione accesso rapido", - report: "Rapporto Analisi" + report: "Rapporto Analisi", + shareIdea: "Condividi idea" }; // @key: @#@"region"@#@ @source: @#@"Region"@#@ @@ -41,3 +42,4 @@ export default { // @key: @#@"userGuide"@#@ @source: @#@"What's New"@#@ // @key: @#@"quickAccess"@#@ @source: @#@"Quick Access Setting"@#@ // @key: @#@"report"@#@ @source: @#@"Analytics Report"@#@ +// @key: @#@"shareIdea"@#@ @source: @#@"Share idea"@#@ diff --git a/packages/ringcentral-widgets/components/SettingsPanel/i18n/ja-JP.js b/packages/ringcentral-widgets/components/SettingsPanel/i18n/ja-JP.js index 9fe2608006..81097c05ca 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/i18n/ja-JP.js +++ b/packages/ringcentral-widgets/components/SettingsPanel/i18n/ja-JP.js @@ -18,7 +18,8 @@ export default { feedback: "フィードバック", userGuide: "新着情報", quickAccess: "クイックアクセスの設定", - report: "分析レポート" + report: "分析レポート", + shareIdea: "アイデアの共有" }; // @key: @#@"region"@#@ @source: @#@"Region"@#@ @@ -41,3 +42,4 @@ export default { // @key: @#@"userGuide"@#@ @source: @#@"What's New"@#@ // @key: @#@"quickAccess"@#@ @source: @#@"Quick Access Setting"@#@ // @key: @#@"report"@#@ @source: @#@"Analytics Report"@#@ +// @key: @#@"shareIdea"@#@ @source: @#@"Share idea"@#@ diff --git a/packages/ringcentral-widgets/components/SettingsPanel/i18n/ko-KR.js b/packages/ringcentral-widgets/components/SettingsPanel/i18n/ko-KR.js index 5502771cc8..34146334b8 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/i18n/ko-KR.js +++ b/packages/ringcentral-widgets/components/SettingsPanel/i18n/ko-KR.js @@ -18,7 +18,8 @@ export default { feedback: "피드백", userGuide: "새로운 기능", quickAccess: "빠른 액세스 설정", - report: "분석 보고서" + report: "분석 보고서", + shareIdea: "아이디어 공유" }; // @key: @#@"region"@#@ @source: @#@"Region"@#@ @@ -41,3 +42,4 @@ export default { // @key: @#@"userGuide"@#@ @source: @#@"What's New"@#@ // @key: @#@"quickAccess"@#@ @source: @#@"Quick Access Setting"@#@ // @key: @#@"report"@#@ @source: @#@"Analytics Report"@#@ +// @key: @#@"shareIdea"@#@ @source: @#@"Share idea"@#@ diff --git a/packages/ringcentral-widgets/components/SettingsPanel/i18n/nl-NL.js b/packages/ringcentral-widgets/components/SettingsPanel/i18n/nl-NL.js index 97fc5f8212..8fabe72ab8 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/i18n/nl-NL.js +++ b/packages/ringcentral-widgets/components/SettingsPanel/i18n/nl-NL.js @@ -18,7 +18,8 @@ export default { feedback: "Feedback", userGuide: "Nieuw", quickAccess: "Instelling snelle toegang", - report: "Analyserapport" + report: "Analyserapport", + shareIdea: "Deel idee" }; // @key: @#@"region"@#@ @source: @#@"Region"@#@ @@ -41,3 +42,4 @@ export default { // @key: @#@"userGuide"@#@ @source: @#@"What's New"@#@ // @key: @#@"quickAccess"@#@ @source: @#@"Quick Access Setting"@#@ // @key: @#@"report"@#@ @source: @#@"Analytics Report"@#@ +// @key: @#@"shareIdea"@#@ @source: @#@"Share idea"@#@ diff --git a/packages/ringcentral-widgets/components/SettingsPanel/i18n/pt-BR.js b/packages/ringcentral-widgets/components/SettingsPanel/i18n/pt-BR.js index bfb0b75c09..841db63c23 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/i18n/pt-BR.js +++ b/packages/ringcentral-widgets/components/SettingsPanel/i18n/pt-BR.js @@ -18,7 +18,8 @@ export default { feedback: "Comentários", userGuide: "Novidades", quickAccess: "Configuração de acesso rápido", - report: "Relatório de análise" + report: "Relatório de análise", + shareIdea: "Compartilhar ideia" }; // @key: @#@"region"@#@ @source: @#@"Region"@#@ @@ -41,3 +42,4 @@ export default { // @key: @#@"userGuide"@#@ @source: @#@"What's New"@#@ // @key: @#@"quickAccess"@#@ @source: @#@"Quick Access Setting"@#@ // @key: @#@"report"@#@ @source: @#@"Analytics Report"@#@ +// @key: @#@"shareIdea"@#@ @source: @#@"Share idea"@#@ diff --git a/packages/ringcentral-widgets/components/SettingsPanel/i18n/pt-PT.js b/packages/ringcentral-widgets/components/SettingsPanel/i18n/pt-PT.js index 3e4e9fc9b7..fd4e25f2d8 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/i18n/pt-PT.js +++ b/packages/ringcentral-widgets/components/SettingsPanel/i18n/pt-PT.js @@ -18,7 +18,8 @@ export default { feedback: "Feedback", userGuide: "Novidades", quickAccess: "Definição de acesso rápido", - report: "Relatório de análise" + report: "Relatório de análise", + shareIdea: "Partilhar ideia" }; // @key: @#@"region"@#@ @source: @#@"Region"@#@ @@ -41,3 +42,4 @@ export default { // @key: @#@"userGuide"@#@ @source: @#@"What's New"@#@ // @key: @#@"quickAccess"@#@ @source: @#@"Quick Access Setting"@#@ // @key: @#@"report"@#@ @source: @#@"Analytics Report"@#@ +// @key: @#@"shareIdea"@#@ @source: @#@"Share idea"@#@ diff --git a/packages/ringcentral-widgets/components/SettingsPanel/i18n/zh-CN.js b/packages/ringcentral-widgets/components/SettingsPanel/i18n/zh-CN.js index 36c77c2e0a..028332d5a0 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/i18n/zh-CN.js +++ b/packages/ringcentral-widgets/components/SettingsPanel/i18n/zh-CN.js @@ -18,7 +18,8 @@ export default { feedback: "反馈", userGuide: "新功能", quickAccess: "快速访问设置", - report: "分析报告" + report: "分析报告", + shareIdea: "分享想法" }; // @key: @#@"region"@#@ @source: @#@"Region"@#@ @@ -41,3 +42,4 @@ export default { // @key: @#@"userGuide"@#@ @source: @#@"What's New"@#@ // @key: @#@"quickAccess"@#@ @source: @#@"Quick Access Setting"@#@ // @key: @#@"report"@#@ @source: @#@"Analytics Report"@#@ +// @key: @#@"shareIdea"@#@ @source: @#@"Share idea"@#@ diff --git a/packages/ringcentral-widgets/components/SettingsPanel/i18n/zh-HK.js b/packages/ringcentral-widgets/components/SettingsPanel/i18n/zh-HK.js index 8e30a47594..96e5ce0b8c 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/i18n/zh-HK.js +++ b/packages/ringcentral-widgets/components/SettingsPanel/i18n/zh-HK.js @@ -18,7 +18,8 @@ export default { feedback: "意見回饋", userGuide: "新功能", quickAccess: "快速存取設定", - report: "分析報告" + report: "分析報告", + shareIdea: "分享意見" }; // @key: @#@"region"@#@ @source: @#@"Region"@#@ @@ -41,3 +42,4 @@ export default { // @key: @#@"userGuide"@#@ @source: @#@"What's New"@#@ // @key: @#@"quickAccess"@#@ @source: @#@"Quick Access Setting"@#@ // @key: @#@"report"@#@ @source: @#@"Analytics Report"@#@ +// @key: @#@"shareIdea"@#@ @source: @#@"Share idea"@#@ diff --git a/packages/ringcentral-widgets/components/SettingsPanel/i18n/zh-TW.js b/packages/ringcentral-widgets/components/SettingsPanel/i18n/zh-TW.js index 8e30a47594..96e5ce0b8c 100644 --- a/packages/ringcentral-widgets/components/SettingsPanel/i18n/zh-TW.js +++ b/packages/ringcentral-widgets/components/SettingsPanel/i18n/zh-TW.js @@ -18,7 +18,8 @@ export default { feedback: "意見回饋", userGuide: "新功能", quickAccess: "快速存取設定", - report: "分析報告" + report: "分析報告", + shareIdea: "分享意見" }; // @key: @#@"region"@#@ @source: @#@"Region"@#@ @@ -41,3 +42,4 @@ export default { // @key: @#@"userGuide"@#@ @source: @#@"What's New"@#@ // @key: @#@"quickAccess"@#@ @source: @#@"Quick Access Setting"@#@ // @key: @#@"report"@#@ @source: @#@"Analytics Report"@#@ +// @key: @#@"shareIdea"@#@ @source: @#@"Share idea"@#@ diff --git a/packages/ringcentral-widgets/components/SpinnerOverlay/SpinnerOverlay.tsx b/packages/ringcentral-widgets/components/SpinnerOverlay/SpinnerOverlay.tsx index e41e6cdc31..2bd3d89fee 100644 --- a/packages/ringcentral-widgets/components/SpinnerOverlay/SpinnerOverlay.tsx +++ b/packages/ringcentral-widgets/components/SpinnerOverlay/SpinnerOverlay.tsx @@ -1,7 +1,6 @@ import classNames from 'classnames'; import React, { ComponentType, FunctionComponent } from 'react'; - -import Spinner from '../Spinner'; +import { RcCircularProgress } from '@ringcentral/juno'; import styles from './styles.scss'; export interface SpinnerOverlayProps { @@ -13,6 +12,9 @@ export interface SpinnerOverlayProps { container?: string; }; } + +const JunoSpinnerWrapper = () => ; + export const SpinnerOverlay: FunctionComponent = ({ className, custom: SpinnerComponent, @@ -35,6 +37,6 @@ export default SpinnerOverlay; SpinnerOverlay.defaultProps = { className: undefined, - custom: Spinner, + custom: JunoSpinnerWrapper, classes: {}, }; diff --git a/packages/ringcentral-widgets/components/SpinnerOverlay/styles.scss b/packages/ringcentral-widgets/components/SpinnerOverlay/styles.scss index bd62e6f917..a9db1b7aea 100644 --- a/packages/ringcentral-widgets/components/SpinnerOverlay/styles.scss +++ b/packages/ringcentral-widgets/components/SpinnerOverlay/styles.scss @@ -1,4 +1,5 @@ @import '../../lib/commonStyles/full-size'; +@import '../../lib/commonStyles/variable'; .root { @include full-size; @@ -19,7 +20,8 @@ .mask { @include full-size; - background-color: rgba(0, 0, 0, 0.7); + background: rc-palette(background, default); + opacity: rc-opacity('48'); } @media only screen and (max-width: 50px) { @@ -27,4 +29,4 @@ width: 30px; height: 30px; } -} \ No newline at end of file +} diff --git a/packages/ringcentral-widgets/components/TabNavigationButton/index.tsx b/packages/ringcentral-widgets/components/TabNavigationButton/index.tsx index dbfbc017ef..cfd996041c 100644 --- a/packages/ringcentral-widgets/components/TabNavigationButton/index.tsx +++ b/packages/ringcentral-widgets/components/TabNavigationButton/index.tsx @@ -17,6 +17,7 @@ export interface NavigationButtonProps { activeClassName: string; inActiveClassName: string; className?: string; + id?: string; } const NavigationButton: FunctionComponent = ({ @@ -32,6 +33,7 @@ const NavigationButton: FunctionComponent = ({ className, activeClassName, inActiveClassName, + id, }) => { let notice = null; if (noticeCounts && noticeCounts > 0) { @@ -50,6 +52,7 @@ const NavigationButton: FunctionComponent = ({ width, height, }} + id={id} >
    diff --git a/packages/ringcentral-widgets/components/TransferPanel/i18n/en-US.js b/packages/ringcentral-widgets/components/TransferPanel/i18n/en-US.js index 9921023d02..7bd9bf3916 100644 --- a/packages/ringcentral-widgets/components/TransferPanel/i18n/en-US.js +++ b/packages/ringcentral-widgets/components/TransferPanel/i18n/en-US.js @@ -2,5 +2,6 @@ export default { to: 'To:', transferTo: 'Transfer to', blindTransfer: 'Transfer', + warmTransfer: 'Ask first', enterNameOrNumber: 'Enter Number', }; diff --git a/packages/ringcentral-widgets/components/TransferPanel/index.js b/packages/ringcentral-widgets/components/TransferPanel/index.js index a4aaf11be6..82d142de5b 100644 --- a/packages/ringcentral-widgets/components/TransferPanel/index.js +++ b/packages/ringcentral-widgets/components/TransferPanel/index.js @@ -1,9 +1,14 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; +import classnames from 'classnames'; + +import WarmTransferIcon from '@ringcentral/juno/icon/Askfirst'; + import DialPad from '../DialPad'; import RecipientsInput from '../RecipientsInput'; import BackHeader from '../BackHeader'; import CircleButton from '../CircleButton'; +import ActiveCallButton from '../ActiveCallButton'; import TransferIcon from '../../assets/images/Transfer.svg'; import styles from './styles.scss'; import i18n from './i18n'; @@ -26,6 +31,7 @@ export default class TransferPanel extends PureComponent { sessionId: PropTypes.string.isRequired, session: PropTypes.object, controlBusy: PropTypes.bool, + enableWarmTransfer: PropTypes.bool, }; static defaultProps = { @@ -38,6 +44,7 @@ export default class TransferPanel extends PureComponent { session: null, searchContactList: [], controlBusy: false, + enableWarmTransfer: false, }; constructor(props) { @@ -87,6 +94,10 @@ export default class TransferPanel extends PureComponent { this.props.onTransfer(this._getTransferNumber(), this.props.sessionId); }; + onWarmTransfer = () => { + this.props.onWarmTransfer(this._getTransferNumber(), this.props.sessionId) + } + onToNumberChange = (toNumber) => { this.setState({ isLastInputFromDialpad: false, @@ -125,11 +136,52 @@ export default class TransferPanel extends PureComponent { recipientsContactInfoRenderer, recipientsContactPhoneRenderer, autoFocus, + enableWarmTransfer, } = this.props; if (!session) { return null; } const isOnTransfer = !!session.isOnTransfer; + let transferButton; + let warmTransferButton; + if (enableWarmTransfer) { + transferButton = ( +
    + +
    + ); + warmTransferButton = ( +
    + +
    + ); + } else { + transferButton = ( +
    + +
    + ); + } return (
    @@ -162,15 +214,8 @@ export default class TransferPanel extends PureComponent { onButtonOutput={this.onButtonOutput} />
    -
    - -
    + {warmTransferButton} + {transferButton}
    diff --git a/packages/ringcentral-widgets/components/TransferPanel/styles.scss b/packages/ringcentral-widgets/components/TransferPanel/styles.scss index 4689e0cc20..a8de886f5e 100644 --- a/packages/ringcentral-widgets/components/TransferPanel/styles.scss +++ b/packages/ringcentral-widgets/components/TransferPanel/styles.scss @@ -44,6 +44,11 @@ $input-height: 43px; display: inline-block; } +.buttonGroupItem { + margin: 0 20px; + width: 20%; +} + .disabled { g { cursor: not-allowed; diff --git a/packages/ringcentral-widgets/components/VideoPanel/VideoConfig.tsx b/packages/ringcentral-widgets/components/VideoPanel/VideoConfig.tsx index fe3fef5ea6..5b8999613f 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/VideoConfig.tsx +++ b/packages/ringcentral-widgets/components/VideoPanel/VideoConfig.tsx @@ -1,38 +1,36 @@ import { RcAlert, - RcBoxSelect, RcCheckbox, RcDatePicker, RcDatePickerProps, - RcLineSelect, RcMenuItem, + RcSelect, RcTextField, - RcOutlineTextField, RcTimePicker, RcTimePickerProps, } from '@ringcentral/juno'; -import { find, reduce } from 'ramda'; import classnames from 'classnames'; +import { find, reduce } from 'ramda'; import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { + updateFullTime, + updateFullYear, +} from 'ringcentral-integration/helpers/meetingHelper'; import { RcVMeetingModel } from 'ringcentral-integration/interfaces/Rcv.model'; import { - RcvDelegator, ASSISTED_USERS_MYSELF, RCV_WAITING_ROOM_MODE, + RcvDelegator, RcvWaitingRoomModeProps, } from 'ringcentral-integration/modules/RcVideo'; -import { - updateFullYear, - updateFullTime, -} from 'ringcentral-integration/helpers/meetingHelper'; import { formatMeetingId } from '../../lib/MeetingCalendarHelper'; import { useDebounce } from '../../react-hooks'; +import { SpinnerOverlay } from '../SpinnerOverlay'; import i18n from './i18n'; import { SettingGroup } from './SettingGroup'; import styles from './styles.scss'; import { VideoSecuritySettingsItem } from './VideoSecuritySettingItem'; -import { SpinnerOverlay } from '../SpinnerOverlay'; export const MINUTE_SCALE: number = 4; export const HOUR_SCALE: number = 13; @@ -277,8 +275,8 @@ export const VideoConfig: React.FunctionComponent = ( {showDuration ? (
    - { @@ -304,10 +302,11 @@ export const VideoConfig: React.FunctionComponent = ( {item !== null ? item.text : 'defaultValue'} ))} - +
    - = ( {item !== null ? item.text : 'defaultValue'} ))} - +
    ) : null} @@ -345,7 +344,8 @@ export const VideoConfig: React.FunctionComponent = ( summary={i18n.getString('scheduleFor', currentLocale)} >
    - = ( ); })} - +
    ) : null} @@ -490,10 +490,12 @@ export const VideoConfig: React.FunctionComponent = ( [styles.subPrefixPadding]: labelPlacement === 'end', })} > - = ( [styles.subPrefixPadding]: labelPlacement === 'end', })} > - = ( > {i18n.getString('waitingRoomAll', currentLocale)} - +
    ) : null} @@ -655,9 +658,10 @@ export const VideoConfig: React.FunctionComponent = ( [styles.subPrefixPadding]: labelPlacement === 'end', })} > - = ( {i18n.getString('signedInCoWorkers', currentLocale)} - +
    ) : null} @@ -710,50 +714,6 @@ export const VideoConfig: React.FunctionComponent = ( ); }; -const InnerTopic: React.FunctionComponent<{ - name: string; - currentLocale: string; - setTopicRef: (ref: any) => void; - updateMeetingTopic: (name: string) => void; -}> = ({ name, currentLocale, setTopicRef, updateMeetingTopic }) => { - const [topic, setTopic] = useState(name); - const topicRef = useRef(); - useEffect(() => { - setTopic(name); - setTopicRef(topicRef); - }, [name, setTopicRef]); - return ( - { - setTopic(e.target.value); - }} - onBlur={() => { - updateMeetingTopic(topic); - }} - classes={{ - root: styles.input, - }} - /> - ); -}; - -export const Topic = React.memo( - InnerTopic, - (prevProps, nextProps) => - prevProps.name === nextProps.name && - prevProps.currentLocale === nextProps.currentLocale, -); - VideoConfig.defaultProps = { recipientsSection: undefined, showTopic: true, diff --git a/packages/ringcentral-widgets/components/VideoPanel/i18n/de-DE.js b/packages/ringcentral-widgets/components/VideoPanel/i18n/de-DE.js index 1274d01433..112662b08a 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/i18n/de-DE.js +++ b/packages/ringcentral-widgets/components/VideoPanel/i18n/de-DE.js @@ -13,6 +13,7 @@ export default { waitingRoomNotCoworker: "Jeder außerhalb meines Unternehmens", waitingRoomGuest: "Jeder, der nicht angemeldet ist", waitingRoomAll: "Alle", + enterPassword: "Kennwort eingeben", onlyJoinAfterMe: "Teilnehmer können erst nach mir teilnehmen", onlyJoinAfterHost: "Teilnehmer können erst nach dem Gastgeber teilnehmen", muteAudio: "Ton für Teilnehmer ausschalten", @@ -29,7 +30,7 @@ export default { signedInUsers: "Angemeldete Benutzer", signedInCoWorkers: "Angemeldete Kollegen", limitScreenSharing: "Nur Gastgeber & Moderatoren können den Bildschirm freigeben", - lockTooltip: "Diese Einstellung wird vom Admin Ihres Unternehmens verwaltet", + lockTooltip: "Ihr Unternehmens-Admin verwaltet diese Einstellung", pmiSettingAlert: "Diese Einstellungen gelten für alle Meetings, die mit PMI erstellt werden" }; @@ -46,6 +47,7 @@ export default { // @key: @#@"waitingRoomNotCoworker"@#@ @source: @#@"Anyone outside my company"@#@ // @key: @#@"waitingRoomGuest"@#@ @source: @#@"Anyone not signed in"@#@ // @key: @#@"waitingRoomAll"@#@ @source: @#@"Everyone"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"onlyJoinAfterMe"@#@ @source: @#@"Participants can only join after me"@#@ // @key: @#@"onlyJoinAfterHost"@#@ @source: @#@"Participants can only join after host"@#@ // @key: @#@"muteAudio"@#@ @source: @#@"Mute audio for participants"@#@ diff --git a/packages/ringcentral-widgets/components/VideoPanel/i18n/en-AU.js b/packages/ringcentral-widgets/components/VideoPanel/i18n/en-AU.js index 33c6dca5a0..67761b4b26 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/i18n/en-AU.js +++ b/packages/ringcentral-widgets/components/VideoPanel/i18n/en-AU.js @@ -13,6 +13,7 @@ export default { waitingRoomNotCoworker: "Anyone outside my company", waitingRoomGuest: "Anyone not signed in", waitingRoomAll: "Everyone", + enterPassword: "Enter Password", onlyJoinAfterMe: "Participants can only join after me", onlyJoinAfterHost: "Participants can only join after host", muteAudio: "Mute audio for participants", @@ -46,6 +47,7 @@ export default { // @key: @#@"waitingRoomNotCoworker"@#@ @source: @#@"Anyone outside my company"@#@ // @key: @#@"waitingRoomGuest"@#@ @source: @#@"Anyone not signed in"@#@ // @key: @#@"waitingRoomAll"@#@ @source: @#@"Everyone"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"onlyJoinAfterMe"@#@ @source: @#@"Participants can only join after me"@#@ // @key: @#@"onlyJoinAfterHost"@#@ @source: @#@"Participants can only join after host"@#@ // @key: @#@"muteAudio"@#@ @source: @#@"Mute audio for participants"@#@ diff --git a/packages/ringcentral-widgets/components/VideoPanel/i18n/en-GB.js b/packages/ringcentral-widgets/components/VideoPanel/i18n/en-GB.js index 33c6dca5a0..67761b4b26 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/i18n/en-GB.js +++ b/packages/ringcentral-widgets/components/VideoPanel/i18n/en-GB.js @@ -13,6 +13,7 @@ export default { waitingRoomNotCoworker: "Anyone outside my company", waitingRoomGuest: "Anyone not signed in", waitingRoomAll: "Everyone", + enterPassword: "Enter Password", onlyJoinAfterMe: "Participants can only join after me", onlyJoinAfterHost: "Participants can only join after host", muteAudio: "Mute audio for participants", @@ -46,6 +47,7 @@ export default { // @key: @#@"waitingRoomNotCoworker"@#@ @source: @#@"Anyone outside my company"@#@ // @key: @#@"waitingRoomGuest"@#@ @source: @#@"Anyone not signed in"@#@ // @key: @#@"waitingRoomAll"@#@ @source: @#@"Everyone"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"onlyJoinAfterMe"@#@ @source: @#@"Participants can only join after me"@#@ // @key: @#@"onlyJoinAfterHost"@#@ @source: @#@"Participants can only join after host"@#@ // @key: @#@"muteAudio"@#@ @source: @#@"Mute audio for participants"@#@ diff --git a/packages/ringcentral-widgets/components/VideoPanel/i18n/en-US.js b/packages/ringcentral-widgets/components/VideoPanel/i18n/en-US.js index 05f24cd543..052bb9f59b 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/i18n/en-US.js +++ b/packages/ringcentral-widgets/components/VideoPanel/i18n/en-US.js @@ -14,6 +14,7 @@ export default { waitingRoomNotCoworker: 'Anyone outside my company', waitingRoomGuest: 'Anyone not signed in', waitingRoomAll: 'Everyone', + enterPassword: 'Enter Password', onlyJoinAfterMe: 'Participants can only join after me', onlyJoinAfterHost: 'Participants can only join after host', muteAudio: 'Mute audio for participants', diff --git a/packages/ringcentral-widgets/components/VideoPanel/i18n/es-419.js b/packages/ringcentral-widgets/components/VideoPanel/i18n/es-419.js index bf117294f0..30cbc5c298 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/i18n/es-419.js +++ b/packages/ringcentral-widgets/components/VideoPanel/i18n/es-419.js @@ -13,6 +13,7 @@ export default { waitingRoomNotCoworker: "Cualquiera fuera de mi empresa", waitingRoomGuest: "Cualquiera que no haya iniciado sesión", waitingRoomAll: "Todos", + enterPassword: "Ingrese la contraseña", onlyJoinAfterMe: "Los participantes solo pueden unirse después de mí", onlyJoinAfterHost: "Los participantes solo pueden unirse después del anfitrión", muteAudio: "Apagar el micrófono de los participantes", @@ -46,6 +47,7 @@ export default { // @key: @#@"waitingRoomNotCoworker"@#@ @source: @#@"Anyone outside my company"@#@ // @key: @#@"waitingRoomGuest"@#@ @source: @#@"Anyone not signed in"@#@ // @key: @#@"waitingRoomAll"@#@ @source: @#@"Everyone"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"onlyJoinAfterMe"@#@ @source: @#@"Participants can only join after me"@#@ // @key: @#@"onlyJoinAfterHost"@#@ @source: @#@"Participants can only join after host"@#@ // @key: @#@"muteAudio"@#@ @source: @#@"Mute audio for participants"@#@ diff --git a/packages/ringcentral-widgets/components/VideoPanel/i18n/es-ES.js b/packages/ringcentral-widgets/components/VideoPanel/i18n/es-ES.js index c40e9bc701..05c1da9206 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/i18n/es-ES.js +++ b/packages/ringcentral-widgets/components/VideoPanel/i18n/es-ES.js @@ -13,6 +13,7 @@ export default { waitingRoomNotCoworker: "Cualquier usuario de fuera de mi empresa", waitingRoomGuest: "Cualquier usuario que no haya iniciado sesión", waitingRoomAll: "Todos", + enterPassword: "Introducir contraseña", onlyJoinAfterMe: "Los participantes solo pueden unirse después de mí", onlyJoinAfterHost: "Los participantes solo pueden unirse después del organizador", muteAudio: "Silenciar el audio para los participantes", @@ -46,6 +47,7 @@ export default { // @key: @#@"waitingRoomNotCoworker"@#@ @source: @#@"Anyone outside my company"@#@ // @key: @#@"waitingRoomGuest"@#@ @source: @#@"Anyone not signed in"@#@ // @key: @#@"waitingRoomAll"@#@ @source: @#@"Everyone"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"onlyJoinAfterMe"@#@ @source: @#@"Participants can only join after me"@#@ // @key: @#@"onlyJoinAfterHost"@#@ @source: @#@"Participants can only join after host"@#@ // @key: @#@"muteAudio"@#@ @source: @#@"Mute audio for participants"@#@ diff --git a/packages/ringcentral-widgets/components/VideoPanel/i18n/fr-CA.js b/packages/ringcentral-widgets/components/VideoPanel/i18n/fr-CA.js index bb14c7c594..78950706ec 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/i18n/fr-CA.js +++ b/packages/ringcentral-widgets/components/VideoPanel/i18n/fr-CA.js @@ -13,6 +13,7 @@ export default { waitingRoomNotCoworker: "Personnes externes", waitingRoomGuest: "Personnes non connectées", waitingRoomAll: "Tout le monde", + enterPassword: "Saisissez le mot de passe", onlyJoinAfterMe: "Les participants ne peuvent se joindre au meeting qu'après moi", onlyJoinAfterHost: "Les participants ne peuvent se joindre au meeting qu'après l'hôte", muteAudio: "Activer le mode discrétion pour les participants", @@ -46,6 +47,7 @@ export default { // @key: @#@"waitingRoomNotCoworker"@#@ @source: @#@"Anyone outside my company"@#@ // @key: @#@"waitingRoomGuest"@#@ @source: @#@"Anyone not signed in"@#@ // @key: @#@"waitingRoomAll"@#@ @source: @#@"Everyone"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"onlyJoinAfterMe"@#@ @source: @#@"Participants can only join after me"@#@ // @key: @#@"onlyJoinAfterHost"@#@ @source: @#@"Participants can only join after host"@#@ // @key: @#@"muteAudio"@#@ @source: @#@"Mute audio for participants"@#@ diff --git a/packages/ringcentral-widgets/components/VideoPanel/i18n/fr-FR.js b/packages/ringcentral-widgets/components/VideoPanel/i18n/fr-FR.js index 5a252bf6ac..486685433d 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/i18n/fr-FR.js +++ b/packages/ringcentral-widgets/components/VideoPanel/i18n/fr-FR.js @@ -13,6 +13,7 @@ export default { waitingRoomNotCoworker: "Util. extérieure à mon entreprise", waitingRoomGuest: "Util. non connectée", waitingRoomAll: "Tout le monde", + enterPassword: "Entrer le mot de passe", onlyJoinAfterMe: "Les participants ne peuvent rejoindre la réunion qu'après moi", onlyJoinAfterHost: "Les participants ne peuvent rejoindre la réunion qu'après l'hôte", muteAudio: "Activer le mode silencieux pour les participants", @@ -46,6 +47,7 @@ export default { // @key: @#@"waitingRoomNotCoworker"@#@ @source: @#@"Anyone outside my company"@#@ // @key: @#@"waitingRoomGuest"@#@ @source: @#@"Anyone not signed in"@#@ // @key: @#@"waitingRoomAll"@#@ @source: @#@"Everyone"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"onlyJoinAfterMe"@#@ @source: @#@"Participants can only join after me"@#@ // @key: @#@"onlyJoinAfterHost"@#@ @source: @#@"Participants can only join after host"@#@ // @key: @#@"muteAudio"@#@ @source: @#@"Mute audio for participants"@#@ diff --git a/packages/ringcentral-widgets/components/VideoPanel/i18n/it-IT.js b/packages/ringcentral-widgets/components/VideoPanel/i18n/it-IT.js index 02c95d7a67..73d70f3e50 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/i18n/it-IT.js +++ b/packages/ringcentral-widgets/components/VideoPanel/i18n/it-IT.js @@ -13,6 +13,7 @@ export default { waitingRoomNotCoworker: "Chiunque al di fuori della mia azienda", waitingRoomGuest: "Chi non si è registrato", waitingRoomAll: "Tutti", + enterPassword: "Inserisci password", onlyJoinAfterMe: "I partecipanti possono accedere solo dopo di me", onlyJoinAfterHost: "I partecipanti possono accedere solo dopo l'host", muteAudio: "Disatt. audio partecipanti", @@ -29,7 +30,7 @@ export default { signedInUsers: "Utenti connessi", signedInCoWorkers: "Collaboratori connessi", limitScreenSharing: "Solo gli host e i moderatori possono condividere lo schermo", - lockTooltip: "Questa impostazione è gestita dall'amministratore della tua azienda", + lockTooltip: "L'impostazione è gestita dall'amministratore dell'azienda", pmiSettingAlert: "Queste impostazioni si applicheranno a tutte le riunioni create con il PMI" }; @@ -46,6 +47,7 @@ export default { // @key: @#@"waitingRoomNotCoworker"@#@ @source: @#@"Anyone outside my company"@#@ // @key: @#@"waitingRoomGuest"@#@ @source: @#@"Anyone not signed in"@#@ // @key: @#@"waitingRoomAll"@#@ @source: @#@"Everyone"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"onlyJoinAfterMe"@#@ @source: @#@"Participants can only join after me"@#@ // @key: @#@"onlyJoinAfterHost"@#@ @source: @#@"Participants can only join after host"@#@ // @key: @#@"muteAudio"@#@ @source: @#@"Mute audio for participants"@#@ diff --git a/packages/ringcentral-widgets/components/VideoPanel/i18n/ja-JP.js b/packages/ringcentral-widgets/components/VideoPanel/i18n/ja-JP.js index 4dd6ac2688..e2407a5947 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/i18n/ja-JP.js +++ b/packages/ringcentral-widgets/components/VideoPanel/i18n/ja-JP.js @@ -13,6 +13,7 @@ export default { waitingRoomNotCoworker: "すべての社外ユーザー", waitingRoomGuest: "すべての未サインインユーザー", waitingRoomAll: "全員", + enterPassword: "パスワードを入力", onlyJoinAfterMe: "自分の後にのみ参加者が参加可能にする", onlyJoinAfterHost: "ホストの後にのみ参加者が参加可能にする", muteAudio: "参加者のオーディオをミュートする", @@ -46,6 +47,7 @@ export default { // @key: @#@"waitingRoomNotCoworker"@#@ @source: @#@"Anyone outside my company"@#@ // @key: @#@"waitingRoomGuest"@#@ @source: @#@"Anyone not signed in"@#@ // @key: @#@"waitingRoomAll"@#@ @source: @#@"Everyone"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"onlyJoinAfterMe"@#@ @source: @#@"Participants can only join after me"@#@ // @key: @#@"onlyJoinAfterHost"@#@ @source: @#@"Participants can only join after host"@#@ // @key: @#@"muteAudio"@#@ @source: @#@"Mute audio for participants"@#@ diff --git a/packages/ringcentral-widgets/components/VideoPanel/i18n/ko-KR.js b/packages/ringcentral-widgets/components/VideoPanel/i18n/ko-KR.js index f2bb2bba0e..5e6333c870 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/i18n/ko-KR.js +++ b/packages/ringcentral-widgets/components/VideoPanel/i18n/ko-KR.js @@ -8,11 +8,12 @@ export default { meetingSettings: "모임 설정", [ASSISTED_USERS_MYSELF]: "나", joinBeforeHost: "참가자가 호스트보다 먼저 참가하도록 허용", - enableWaitingRoom: "대기실 소용", - waitingRoom: "다음 기간에 대기실 사용", + enableWaitingRoom: "대기실 사용", + waitingRoom: "다음 사람을 위해 대기실 허용", waitingRoomNotCoworker: "회사 외부의 사람", waitingRoomGuest: "로그인하지 않은 사람", waitingRoomAll: "모두", + enterPassword: "비밀번호 입력", onlyJoinAfterMe: "참가자는 내가 참가한 이후에만 참가할 수 있습니다.", onlyJoinAfterHost: "참가자는 호스트가 참가한 이후에만 참가할 수 있습니다.", muteAudio: "참가자의 오디오 음소거", @@ -46,6 +47,7 @@ export default { // @key: @#@"waitingRoomNotCoworker"@#@ @source: @#@"Anyone outside my company"@#@ // @key: @#@"waitingRoomGuest"@#@ @source: @#@"Anyone not signed in"@#@ // @key: @#@"waitingRoomAll"@#@ @source: @#@"Everyone"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"onlyJoinAfterMe"@#@ @source: @#@"Participants can only join after me"@#@ // @key: @#@"onlyJoinAfterHost"@#@ @source: @#@"Participants can only join after host"@#@ // @key: @#@"muteAudio"@#@ @source: @#@"Mute audio for participants"@#@ diff --git a/packages/ringcentral-widgets/components/VideoPanel/i18n/nl-NL.js b/packages/ringcentral-widgets/components/VideoPanel/i18n/nl-NL.js index 68b8dbc89b..b0a4863b5f 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/i18n/nl-NL.js +++ b/packages/ringcentral-widgets/components/VideoPanel/i18n/nl-NL.js @@ -13,6 +13,7 @@ export default { waitingRoomNotCoworker: "Iedereen buiten mijn bedrijf", waitingRoomGuest: "Iedereen die niet is aangemeld", waitingRoomAll: "Iedereen", + enterPassword: "Wachtwoord invoeren", onlyJoinAfterMe: "Deelnemers kunnen pas na mij deelnemen", onlyJoinAfterHost: "Deelnemers kunnen pas na de host deelnemen", muteAudio: "Audio dempen voor deelnemers", @@ -46,6 +47,7 @@ export default { // @key: @#@"waitingRoomNotCoworker"@#@ @source: @#@"Anyone outside my company"@#@ // @key: @#@"waitingRoomGuest"@#@ @source: @#@"Anyone not signed in"@#@ // @key: @#@"waitingRoomAll"@#@ @source: @#@"Everyone"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"onlyJoinAfterMe"@#@ @source: @#@"Participants can only join after me"@#@ // @key: @#@"onlyJoinAfterHost"@#@ @source: @#@"Participants can only join after host"@#@ // @key: @#@"muteAudio"@#@ @source: @#@"Mute audio for participants"@#@ diff --git a/packages/ringcentral-widgets/components/VideoPanel/i18n/pt-BR.js b/packages/ringcentral-widgets/components/VideoPanel/i18n/pt-BR.js index 821f5069e3..dc27335472 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/i18n/pt-BR.js +++ b/packages/ringcentral-widgets/components/VideoPanel/i18n/pt-BR.js @@ -13,6 +13,7 @@ export default { waitingRoomNotCoworker: "Pessoas fora da empresa", waitingRoomGuest: "Pessoas não conectadas", waitingRoomAll: "Todos", + enterPassword: "Insira a senha", onlyJoinAfterMe: "Os participantes só podem entrar depois de mim", onlyJoinAfterHost: "Os participantes só podem entrar depois do host", muteAudio: "Desativar o áudio dos participantes", @@ -29,7 +30,7 @@ export default { signedInUsers: "Usuários conectados", signedInCoWorkers: "Colegas de trabalho conectados", limitScreenSharing: "Apenas o host e os moderadores podem compartilhar tela", - lockTooltip: "Esta configuração é gerenciada pelo administrador da sua empresa", + lockTooltip: "Configuração gerenciada pelo administrador da empresa.", pmiSettingAlert: "Essas configurações serão aplicadas a todas as reuniões criadas com o PMI." }; @@ -46,6 +47,7 @@ export default { // @key: @#@"waitingRoomNotCoworker"@#@ @source: @#@"Anyone outside my company"@#@ // @key: @#@"waitingRoomGuest"@#@ @source: @#@"Anyone not signed in"@#@ // @key: @#@"waitingRoomAll"@#@ @source: @#@"Everyone"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"onlyJoinAfterMe"@#@ @source: @#@"Participants can only join after me"@#@ // @key: @#@"onlyJoinAfterHost"@#@ @source: @#@"Participants can only join after host"@#@ // @key: @#@"muteAudio"@#@ @source: @#@"Mute audio for participants"@#@ diff --git a/packages/ringcentral-widgets/components/VideoPanel/i18n/pt-PT.js b/packages/ringcentral-widgets/components/VideoPanel/i18n/pt-PT.js index 1560eabdfd..156fd369b0 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/i18n/pt-PT.js +++ b/packages/ringcentral-widgets/components/VideoPanel/i18n/pt-PT.js @@ -13,6 +13,7 @@ export default { waitingRoomNotCoworker: "Qq pessoa ext. à empresa", waitingRoomGuest: "Qq pessoa s/ ses. iniciada", waitingRoomAll: "Todos", + enterPassword: "Introduzir palavra-passe", onlyJoinAfterMe: "Os participantes apenas podem entrar depois de mim", onlyJoinAfterHost: "Os participantes só podem entrar depois do anfitrião", muteAudio: "Desativar som dos participantes", @@ -46,6 +47,7 @@ export default { // @key: @#@"waitingRoomNotCoworker"@#@ @source: @#@"Anyone outside my company"@#@ // @key: @#@"waitingRoomGuest"@#@ @source: @#@"Anyone not signed in"@#@ // @key: @#@"waitingRoomAll"@#@ @source: @#@"Everyone"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"onlyJoinAfterMe"@#@ @source: @#@"Participants can only join after me"@#@ // @key: @#@"onlyJoinAfterHost"@#@ @source: @#@"Participants can only join after host"@#@ // @key: @#@"muteAudio"@#@ @source: @#@"Mute audio for participants"@#@ diff --git a/packages/ringcentral-widgets/components/VideoPanel/i18n/zh-CN.js b/packages/ringcentral-widgets/components/VideoPanel/i18n/zh-CN.js index 84043feae2..a8e93833b3 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/i18n/zh-CN.js +++ b/packages/ringcentral-widgets/components/VideoPanel/i18n/zh-CN.js @@ -13,6 +13,7 @@ export default { waitingRoomNotCoworker: "我公司以外的任何人员", waitingRoomGuest: "任何未登录的人员", waitingRoomAll: "所有人", + enterPassword: "输入密码", onlyJoinAfterMe: "参与者只能在我之后加入", onlyJoinAfterHost: "参与者只能在主持人之后加入", muteAudio: "参与者音频静音", @@ -46,6 +47,7 @@ export default { // @key: @#@"waitingRoomNotCoworker"@#@ @source: @#@"Anyone outside my company"@#@ // @key: @#@"waitingRoomGuest"@#@ @source: @#@"Anyone not signed in"@#@ // @key: @#@"waitingRoomAll"@#@ @source: @#@"Everyone"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"onlyJoinAfterMe"@#@ @source: @#@"Participants can only join after me"@#@ // @key: @#@"onlyJoinAfterHost"@#@ @source: @#@"Participants can only join after host"@#@ // @key: @#@"muteAudio"@#@ @source: @#@"Mute audio for participants"@#@ diff --git a/packages/ringcentral-widgets/components/VideoPanel/i18n/zh-HK.js b/packages/ringcentral-widgets/components/VideoPanel/i18n/zh-HK.js index f77aa4f62c..2ba2cdf5cd 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/i18n/zh-HK.js +++ b/packages/ringcentral-widgets/components/VideoPanel/i18n/zh-HK.js @@ -13,6 +13,7 @@ export default { waitingRoomNotCoworker: "公司以外任何人", waitingRoomGuest: "未登入的任何人", waitingRoomAll: "任何人", + enterPassword: "輸入密碼", onlyJoinAfterMe: "參與者只能在我之後加入", onlyJoinAfterHost: "參與者只能在主持人之後加入", muteAudio: "靜音參與者的音訊", @@ -46,6 +47,7 @@ export default { // @key: @#@"waitingRoomNotCoworker"@#@ @source: @#@"Anyone outside my company"@#@ // @key: @#@"waitingRoomGuest"@#@ @source: @#@"Anyone not signed in"@#@ // @key: @#@"waitingRoomAll"@#@ @source: @#@"Everyone"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"onlyJoinAfterMe"@#@ @source: @#@"Participants can only join after me"@#@ // @key: @#@"onlyJoinAfterHost"@#@ @source: @#@"Participants can only join after host"@#@ // @key: @#@"muteAudio"@#@ @source: @#@"Mute audio for participants"@#@ diff --git a/packages/ringcentral-widgets/components/VideoPanel/i18n/zh-TW.js b/packages/ringcentral-widgets/components/VideoPanel/i18n/zh-TW.js index f77aa4f62c..2ba2cdf5cd 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/i18n/zh-TW.js +++ b/packages/ringcentral-widgets/components/VideoPanel/i18n/zh-TW.js @@ -13,6 +13,7 @@ export default { waitingRoomNotCoworker: "公司以外任何人", waitingRoomGuest: "未登入的任何人", waitingRoomAll: "任何人", + enterPassword: "輸入密碼", onlyJoinAfterMe: "參與者只能在我之後加入", onlyJoinAfterHost: "參與者只能在主持人之後加入", muteAudio: "靜音參與者的音訊", @@ -46,6 +47,7 @@ export default { // @key: @#@"waitingRoomNotCoworker"@#@ @source: @#@"Anyone outside my company"@#@ // @key: @#@"waitingRoomGuest"@#@ @source: @#@"Anyone not signed in"@#@ // @key: @#@"waitingRoomAll"@#@ @source: @#@"Everyone"@#@ +// @key: @#@"enterPassword"@#@ @source: @#@"Enter Password"@#@ // @key: @#@"onlyJoinAfterMe"@#@ @source: @#@"Participants can only join after me"@#@ // @key: @#@"onlyJoinAfterHost"@#@ @source: @#@"Participants can only join after host"@#@ // @key: @#@"muteAudio"@#@ @source: @#@"Mute audio for participants"@#@ diff --git a/packages/ringcentral-widgets/components/VideoPanel/styles.scss b/packages/ringcentral-widgets/components/VideoPanel/styles.scss index e5433ba068..ddf0381e0a 100644 --- a/packages/ringcentral-widgets/components/VideoPanel/styles.scss +++ b/packages/ringcentral-widgets/components/VideoPanel/styles.scss @@ -119,10 +119,6 @@ label.labelPlacementEndRoot { } input { - // TODO: will remove it after Juno updated - &[class*="Mui-disabled"] { - -webkit-text-fill-color: rc-palette(text, 'disabled'); - } @extend .normalFontSize; } } @@ -139,10 +135,12 @@ label.labelPlacementEndRoot { .boxSelect { max-width: 100% !important; - - & > div > div { - display: block !important; - @extend .normalFontSize; + & > div { + margin-top: 0 !important; + & > div { + display: block !important; + @extend .normalFontSize; + } } } @@ -186,7 +184,7 @@ $win-scroll-bar-width: 17px; } .pmiAlertContainer { - margin: 0 $content-padding; + margin: 4px $content-padding 8px; padding: 8px !important; } diff --git a/packages/ringcentral-widgets/components/WebRTCNotificationSection/WebRTCNotificationSection.tsx b/packages/ringcentral-widgets/components/WebRTCNotificationSection/WebRTCNotificationSection.tsx index 122345b60c..69f30c99b8 100644 --- a/packages/ringcentral-widgets/components/WebRTCNotificationSection/WebRTCNotificationSection.tsx +++ b/packages/ringcentral-widgets/components/WebRTCNotificationSection/WebRTCNotificationSection.tsx @@ -107,7 +107,7 @@ export const WebRTCNotificationSection: FunctionComponent this.props.onBeforeMerge(this.props.session.id); this.gotoParticipantsCtrl = () => this.props.gotoParticipantsCtrl(this.props.session.id); + this.onCompleteTransfer = () => + this.props.onCompleteTransfer(this.props.session.id); } static isLastCallEnded({ lastCallInfo }) { @@ -260,6 +262,8 @@ class CallCtrlContainer extends Component { startTime={session.startTime} isOnMute={session.isOnMute} isOnHold={session.isOnHold} + isOnTransfer={session.isOnTransfer} + isOnWaitingTransfer={!!session.warmTransferSessionId} recordStatus={session.recordStatus} showBackButton={this.props.showBackButton} backButtonLabel={backButtonLabel} @@ -277,6 +281,7 @@ class CallCtrlContainer extends Component { onBeforeMerge={this.onBeforeMerge} onFlip={this.props.onFlip} onTransfer={this.props.onTransfer} + onCompleteTransfer={this.onCompleteTransfer} onPark={this.onPark} disableFlip={this.props.disableFlip} showPark={this.props.showPark} @@ -327,6 +332,7 @@ CallCtrlContainer.propTypes = { to: PropTypes.string, from: PropTypes.string, contactMatch: PropTypes.object, + warmTransferSessionId: PropTypes.string, }).isRequired, currentLocale: PropTypes.string.isRequired, onMute: PropTypes.func.isRequired, @@ -377,6 +383,7 @@ CallCtrlContainer.propTypes = { afterOnMerge: PropTypes.func, disableFlip: PropTypes.bool, showCallQueueName: PropTypes.bool, + onCompleteTransfer: PropTypes.func, }; CallCtrlContainer.defaultProps = { @@ -409,6 +416,7 @@ CallCtrlContainer.defaultProps = { afterOnMerge: () => null, disableFlip: false, showCallQueueName: false, + onCompleteTransfer: () => null, }; export default CallCtrlContainer; diff --git a/packages/ringcentral-widgets/enums/callCtrlLayouts.ts b/packages/ringcentral-widgets/enums/callCtrlLayouts.ts index 8c361ee285..9be6fde03c 100644 --- a/packages/ringcentral-widgets/enums/callCtrlLayouts.ts +++ b/packages/ringcentral-widgets/enums/callCtrlLayouts.ts @@ -1,6 +1,6 @@ import { ObjectMap } from '@ringcentral-integration/core/lib/ObjectMap'; export default ObjectMap.prefixKeys( - ['normalCtrl', 'mergeCtrl', 'conferenceCtrl'], + ['normalCtrl', 'mergeCtrl', 'conferenceCtrl', 'completeTransferCtrl'], 'callCtrlLayouts', ); diff --git a/packages/ringcentral-widgets/gulpfile.js b/packages/ringcentral-widgets/gulpfile.js index 7cb4f4add7..e1a89ca77b 100644 --- a/packages/ringcentral-widgets/gulpfile.js +++ b/packages/ringcentral-widgets/gulpfile.js @@ -54,9 +54,9 @@ function preBuild() { return gulp .src([ './**/*.js', - './**/*.jsx', './**/*.ts', './**/*.tsx', + './**/*.jsx', '!./**/*.test.js', '!./test{/**,}', '!./coverage{/**,}', diff --git a/packages/ringcentral-widgets/lib/MeetingCalendarHelper/config.ts b/packages/ringcentral-widgets/lib/MeetingCalendarHelper/config.ts index 6e4b80d384..b55ad30ffc 100644 --- a/packages/ringcentral-widgets/lib/MeetingCalendarHelper/config.ts +++ b/packages/ringcentral-widgets/lib/MeetingCalendarHelper/config.ts @@ -1,15 +1,17 @@ -export const MEETING_URI_REGEXP: { +const MEETING_URI_REGEXP: { EMAIL: RegExp; RCM: RegExp; RCV: RegExp; } = { EMAIL: /w?(\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*)$/, - RCM: /(http|https):\/\/((((meetings|rcm\.rcdev)\.(ringcentral|btcloudphone\.bt|businessconnect\.telus))|(meetings-officeathand\.att))\.com|((\w+\.)*(meetzoom|zoom)\.us))(\/\w+)?(\/(\d+))(\?pwd=\w+)?/i, + RCM: /(http|https):\/\/((((meetings|rcm\.rcdev)\.(ringcentral|btcloudphone\.bt|businessconnect\.telus))|(meetings-officeathand\.att)|(rcm.rcdev.ringcentral))\.com|((\w+\.)*(meetzoom|zoom)\.us))(\/\w+)?(\/(\d+))(\?pwd=\w+)?/i, RCV: /(http|https):\/\/(((v\.ringcentral)|(meetings\.officeathand\.att)|((glpci1xmn|itlcixmn|xmnup)-rxe-1-v(-(avaya|atos))?\.lab\.nordigy)|(amrupams-shr-1-v\.lab\.nordigy)|(vi11-1-v\.lab\.nordigy)|(vi11-1-v-att\.lab\.nordigy))\.(com|ru))(\/{1,2}\w+)*(\/{1,2}(\d+))(\?pw=\w{32})?/i, }; -export const rcvAttTeleconference: string = +const rcvAttTeleconference: string = 'https://meetings.officeathand.att.com/teleconference'; -export const rcvTeleconference: string = - 'https://v.ringcentral.com/teleconference/'; +const rcvTeleconference: string = 'https://v.ringcentral.com/teleconference/'; + +// gsuite is using export at bottom +export { MEETING_URI_REGEXP, rcvAttTeleconference, rcvTeleconference }; diff --git a/packages/ringcentral-widgets/lib/MeetingCalendarHelper/formatMeetingId.ts b/packages/ringcentral-widgets/lib/MeetingCalendarHelper/formatMeetingId.ts index 3009c0ecc1..16e430f10b 100644 --- a/packages/ringcentral-widgets/lib/MeetingCalendarHelper/formatMeetingId.ts +++ b/packages/ringcentral-widgets/lib/MeetingCalendarHelper/formatMeetingId.ts @@ -5,7 +5,7 @@ function f1(str: string): Array { return [str.slice(0, 4), str.slice(4)]; } -export function formatMeetingId(str: string, delimeter: string = ' '): string { +function formatMeetingId(str: string, delimiter: string = ' '): string { if (!str) { return ''; } @@ -16,5 +16,8 @@ export function formatMeetingId(str: string, delimeter: string = ' '): string { if (nextSlices.length === 1) { return `${current}${nextSlices}`; } - return `${current}${delimeter}${formatMeetingId(nextSlices, delimeter)}`; + return `${current}${delimiter}${formatMeetingId(nextSlices, delimiter)}`; } + +// gsuite is using export at bottom +export { formatMeetingId }; diff --git a/packages/ringcentral-widgets/lib/MeetingCalendarHelper/i18n/pt-BR.js b/packages/ringcentral-widgets/lib/MeetingCalendarHelper/i18n/pt-BR.js index e29646003e..a1a6b00ec8 100644 --- a/packages/ringcentral-widgets/lib/MeetingCalendarHelper/i18n/pt-BR.js +++ b/packages/ringcentral-widgets/lib/MeetingCalendarHelper/i18n/pt-BR.js @@ -1,8 +1,8 @@ export default { inviteMeetingContent: "{accountName} está convidando você para uma reunião do {brandName}.\n\nEntre em um PC, Mac, iOS ou Android: {joinUri}{passwordTpl}\n\nOu pelo toque do iPhone:\n\t{mobileDialingNumberTpl}\n\nOu pelo telefone:\n\tDisque: {phoneDialingNumberTpl}\n\tID da reunião: {meetingId}\n\tNúmeros internacionais disponíveis: {teleconference} ", rcvInviteMeetingContent: "{accountName} convidou você para uma reunião {brandName}.\n\nEntre usando este link:\n\t{joinUri}{passwordTpl}", - rcvRCBrandInviteMeetingContent: "{accountName} Convidou você para uma reunião da {productName}.\n\nEntre usando este link:\n\t{joinUri}{passwordTpl}", - rcvInviteMeetingContentDial: "\n\nUm toque para acessar o áudio apenas de um smartphone:\n\t{smartphones}\n\nOu ligue para:\n\tLigue: {dialNumber}\n\tCódigo de acesso/ID da reunião: {pinNumber} ", + rcvRCBrandInviteMeetingContent: "{accountName} convidou você para uma reunião da {productName}.\n\nEntre usando este link:\n\t{joinUri}{passwordTpl}", + rcvInviteMeetingContentDial: "\n\nUm toque para participar apenas com áudio por um smartphone:\n\t{smartphones}\n\nOu ligue para:\n\tLigue: {dialNumber}\n\tCódigo de acesso/ID da reunião: {pinNumber} ", rcvInviteMeetingContentCountryDial: "\n\nUm toque para acessar o áudio apenas de um smartphone:\n\t{smartphones}\n\nOu ligue para:\n\t{dialNumber}\n\tCódigo de acesso/ID da reunião: {pinNumber} ", rcvTeleconference: "\n\nNúmeros internacionais disponíveis: {teleconference} ", doNotModify: "===== Não modifique este texto =====", diff --git a/packages/ringcentral-widgets/lib/MeetingCalendarHelper/index.interface.ts b/packages/ringcentral-widgets/lib/MeetingCalendarHelper/index.interface.ts index 04e50f283a..ad0d799209 100644 --- a/packages/ringcentral-widgets/lib/MeetingCalendarHelper/index.interface.ts +++ b/packages/ringcentral-widgets/lib/MeetingCalendarHelper/index.interface.ts @@ -7,7 +7,7 @@ interface RcmMeeting { password: string; topic: string; meetingType: MeetingTypeV; - schedule: { + schedule?: { startTime: Date; durationInMinutes: number; timeZone: { diff --git a/packages/ringcentral-widgets/lib/MeetingCalendarHelper/index.ts b/packages/ringcentral-widgets/lib/MeetingCalendarHelper/index.ts index 06674ff5cd..02507bd8d8 100644 --- a/packages/ringcentral-widgets/lib/MeetingCalendarHelper/index.ts +++ b/packages/ringcentral-widgets/lib/MeetingCalendarHelper/index.ts @@ -84,7 +84,7 @@ function getPasswordTpl( * Then replace it into http://www.example.com * @param input */ -export function replaceTextLinksToAnchors(input: string): string { +function replaceTextLinksToAnchors(input: string): string { /** * [^<>\]]+ means should match any characters except < or > or ] * (?!\s*<\/a>) means url should not be followed by either "" or " " @@ -101,11 +101,11 @@ export function replaceTextLinksToAnchors(input: string): string { }); } -export const htmlNewLine: string = '
    '; -export const htmlIndentation: string = ' '; -export const htmlTabIndentation: string = htmlIndentation.repeat(4); +const htmlNewLine: string = '
    '; +const htmlIndentation: string = ' '; +const htmlTabIndentation: string = htmlIndentation.repeat(4); -export function formatTextToHtml( +function formatTextToHtml( plantText: string, options: FormatToHtmlOptions = {}, ): string { @@ -195,7 +195,7 @@ function getBaseRcmTpl( }; } -export function getRcmEventTpl( +function getRcmEventTpl( mainInfo: RcmMainParams, brand: CommonBrand, currentLocale: string, @@ -204,7 +204,7 @@ export function getRcmEventTpl( return tplResult.formattedMsg; } -export function getRcmHtmlEventTpl( +function getRcmHtmlEventTpl( mainInfo: RcmMainParams, brand: CommonBrand, currentLocale: string, @@ -292,7 +292,7 @@ function getBaseRcvTpl( }; } -export function getRcvEventTpl( +function getRcvEventTpl( mainInfo: RcvMainParams, brand: CommonBrand, currentLocale: string, @@ -301,7 +301,7 @@ export function getRcvEventTpl( return tplResult.formattedMsg; } -export function getRcvHtmlEventTpl( +function getRcvHtmlEventTpl( mainInfo: RcvMainParams, brand: CommonBrand, currentLocale: string, @@ -312,7 +312,7 @@ export function getRcvHtmlEventTpl( }); } -export function getMeetingId(meetingUri: string): string { +function getMeetingId(meetingUri: string): string { if (meetingUri) { const regs = [MEETING_URI_REGEXP.RCM, MEETING_URI_REGEXP.RCV]; for (let i = 0; i < regs.length; i += 1) { @@ -333,4 +333,37 @@ export function getMeetingId(meetingUri: string): string { return null; } -export { formatMeetingId }; +function stripMeetingLinks(text: string): string { + let result = text; + [MEETING_URI_REGEXP.RCM, MEETING_URI_REGEXP.RCV].forEach((reg) => { + while (reg.test(result)) { + result = result.replace(reg, ''); + } + }); + return result; +} + +function meetingLinkContains( + text?: string, +): { hasRCM: boolean; hasRCV: boolean } { + return { + hasRCM: MEETING_URI_REGEXP.RCM.test(text ?? ''), + hasRCV: MEETING_URI_REGEXP.RCV.test(text ?? ''), + }; +} + +export { + formatMeetingId, + stripMeetingLinks, + meetingLinkContains, + replaceTextLinksToAnchors, + htmlNewLine, + htmlIndentation, + htmlTabIndentation, + formatTextToHtml, + getRcmEventTpl, + getRcmHtmlEventTpl, + getRcvEventTpl, + getRcvHtmlEventTpl, + getMeetingId, +}; diff --git a/packages/ringcentral-widgets/lib/countryNames/i18n/fr-FR.js b/packages/ringcentral-widgets/lib/countryNames/i18n/fr-FR.js index 5d3d892d82..854657dd88 100644 --- a/packages/ringcentral-widgets/lib/countryNames/i18n/fr-FR.js +++ b/packages/ringcentral-widgets/lib/countryNames/i18n/fr-FR.js @@ -64,7 +64,7 @@ export default { TW: "Taïwan", UA: "Ukraine", US: "États-Unis", - VN: "Vietnam", + VN: "Viêt Nam", ZA: "Afrique du Sud" }; diff --git a/packages/ringcentral-widgets/lib/isElectron.ts b/packages/ringcentral-widgets/lib/isElectron.ts new file mode 100644 index 0000000000..bc7dab9c80 --- /dev/null +++ b/packages/ringcentral-widgets/lib/isElectron.ts @@ -0,0 +1,3 @@ +export const isElectron = () => { + return navigator.userAgent.toLowerCase().indexOf(' electron/') > -1; +}; diff --git a/packages/ringcentral-widgets/lib/popWindow/index.js b/packages/ringcentral-widgets/lib/popWindow/index.ts similarity index 69% rename from packages/ringcentral-widgets/lib/popWindow/index.js rename to packages/ringcentral-widgets/lib/popWindow/index.ts index e5ad8c1690..f97d183263 100644 --- a/packages/ringcentral-widgets/lib/popWindow/index.js +++ b/packages/ringcentral-widgets/lib/popWindow/index.ts @@ -1,9 +1,18 @@ -export default function popWindow(url, id, w, h) { +export default function popWindow( + url: string, + id: string, + w: number, + h: number, +) { // Fixes dual-screen position Most browsers Firefox const dualScreenLeft = - window.screenLeft !== undefined ? window.screenLeft : window.screen.left; + window.screenLeft !== undefined + ? window.screenLeft + : (window.screen as any).left; const dualScreenTop = - window.screenTop !== undefined ? window.screenTop : window.screen.top; + window.screenTop !== undefined + ? window.screenTop + : (window.screen as any).top; const width = window.screen.width || window.outerWidth; const height = window.screen.height || window.innerHeight; diff --git a/packages/ringcentral-widgets/modules/Beforeunload/Beforeunload.interface.ts b/packages/ringcentral-widgets/modules/Beforeunload/Beforeunload.interface.ts index 79ac5cdc5e..a257b9ebee 100644 --- a/packages/ringcentral-widgets/modules/Beforeunload/Beforeunload.interface.ts +++ b/packages/ringcentral-widgets/modules/Beforeunload/Beforeunload.interface.ts @@ -1,5 +1,5 @@ export interface BeforeunloadOptions { - orginWindow?: Window; + originWindow?: Window; } export interface Deps { diff --git a/packages/ringcentral-widgets/modules/Beforeunload/Beforeunload.ts b/packages/ringcentral-widgets/modules/Beforeunload/Beforeunload.ts index 9e3920d1c7..c708867349 100644 --- a/packages/ringcentral-widgets/modules/Beforeunload/Beforeunload.ts +++ b/packages/ringcentral-widgets/modules/Beforeunload/Beforeunload.ts @@ -10,7 +10,7 @@ type BeforeunloadFn = () => boolean; name: 'Beforeunload', deps: [{ dep: 'BeforeunloadOptions', optional: true }], }) -export class Beforeunload extends RcModuleV2 { +export class Beforeunload extends RcModuleV2 { _window: Window; private get list() { @@ -47,7 +47,7 @@ export class Beforeunload extends RcModuleV2 { super({ deps: {}, }); - this._window = this._deps.beforeunloadOptions?.orginWindow ?? window; + this._window = this._deps.beforeunloadOptions?.originWindow ?? window; } /** diff --git a/packages/ringcentral-widgets/modules/CallCtrlUI/index.ts b/packages/ringcentral-widgets/modules/CallCtrlUI/index.ts index 48c11f266b..ef01817a42 100644 --- a/packages/ringcentral-widgets/modules/CallCtrlUI/index.ts +++ b/packages/ringcentral-widgets/modules/CallCtrlUI/index.ts @@ -4,9 +4,30 @@ import callDirections from 'ringcentral-integration/enums/callDirections'; import callingModes from 'ringcentral-integration/modules/CallingSettings/callingModes'; import sessionStatus from 'ringcentral-integration/modules/Webphone/sessionStatus'; import formatNumber from 'ringcentral-integration/lib/formatNumber'; +import calleeTypes from 'ringcentral-integration/enums/calleeTypes'; + import callCtrlLayouts from '../../enums/callCtrlLayouts'; import RcUIModule from '../../lib/RcUIModule'; +function getLastCallInfoFromWebphoneSession(webphoneSession) { + const sessionNumber = + webphoneSession.direction === callDirections.outbound + ? webphoneSession.to + : webphoneSession.from; + const sessionStatus = webphoneSession.callStatus; + const matchedContact = webphoneSession.contactMatch; + const calleeType = matchedContact + ? calleeTypes.contacts + : calleeTypes.unknown; + return { + calleeType, + avatarUrl: matchedContact && matchedContact.profileImageUrl, + name: matchedContact && matchedContact.name, + status: sessionStatus, + phoneNumber: sessionNumber, + }; +} + @Module({ name: 'CallCtrlUI', deps: [ @@ -98,7 +119,7 @@ export default class CallCtrlUI extends RcUIModule { let isMerging = false; let conferenceCallParties; let conferenceCallId = null; - const lastCallInfo = + let lastCallInfo = this._conferenceCall && this._conferenceCall.lastCallInfo; let isConferenceCallOverload = false; const conferenceCallEquipped = !!( @@ -136,6 +157,12 @@ export default class CallCtrlUI extends RcUIModule { children = null; } } + if (currentSession.warmTransferSessionId) { + const warmTransferSession = this._webphone.sessions.find( + (session) => session.id === currentSession.warmTransferSessionId, + ); + lastCallInfo = getLastCallInfoFromWebphoneSession(warmTransferSession); + } const disableLinks = !!( this._connectivityManager.isOfflineMode || @@ -182,7 +209,9 @@ export default class CallCtrlUI extends RcUIModule { session, }) => { let layout = callCtrlLayouts.normalCtrl; - + if (session.warmTransferSessionId) { + return callCtrlLayouts.completeTransferCtrl; + } if (!conferenceCallEquipped) { return layout; } @@ -252,6 +281,9 @@ export default class CallCtrlUI extends RcUIModule { onTransfer: (sessionId) => { this._routerInteraction.push(`/transfer/${sessionId}/webphone`); }, + onCompleteTransfer: (sessionId) => { + this._webphone.completeWarmTransfer(sessionId); + }, onPark: (sessionId) => this._webphone.park(sessionId), searchContact: (searchString) => this._contactSearch.debouncedSearch({ searchString }), diff --git a/packages/ringcentral-widgets/modules/CallLogUI/CallLogUI.tsx b/packages/ringcentral-widgets/modules/CallLogUI/CallLogUI.tsx index 95ca3c25d5..8b196604e8 100644 --- a/packages/ringcentral-widgets/modules/CallLogUI/CallLogUI.tsx +++ b/packages/ringcentral-widgets/modules/CallLogUI/CallLogUI.tsx @@ -11,6 +11,7 @@ import { } from './CallLogUI.interface'; import CallLogCallCtrlContainer from '../../containers/CallLogCallCtrlContainer'; +import i18n from './i18n'; @Module({ name: 'CallLogUI', @@ -101,8 +102,11 @@ class CallLogUI phoneNumber, areaCode: regionSettings.areaCode, countryCode: regionSettings.countryCode, - }) || 'Unknown', - goBack: () => callLogSection.closeLogSection(), + }) || i18n.getString('unKnown', locale.currentLocale), + goBack: () => { + callLogSection.closeLogSection(); + callLogSection.closeLogNotification(); + }, renderCallLogCallControl: ( currentTelephonySessionId, isWide, diff --git a/packages/ringcentral-widgets/modules/CallLogUI/i18n/en-US.ts b/packages/ringcentral-widgets/modules/CallLogUI/i18n/en-US.ts new file mode 100644 index 0000000000..cb6c0547e7 --- /dev/null +++ b/packages/ringcentral-widgets/modules/CallLogUI/i18n/en-US.ts @@ -0,0 +1,3 @@ +export default { + unKnown: 'Unknown', +}; diff --git a/packages/ringcentral-widgets/modules/CallLogUI/i18n/index.ts b/packages/ringcentral-widgets/modules/CallLogUI/i18n/index.ts new file mode 100644 index 0000000000..ca92214549 --- /dev/null +++ b/packages/ringcentral-widgets/modules/CallLogUI/i18n/index.ts @@ -0,0 +1,5 @@ +import I18n from '@ringcentral-integration/i18n'; + +import loadLocale from './loadLocale'; + +export default new I18n(loadLocale); diff --git a/packages/ringcentral-widgets/modules/CallLogUI/i18n/loadLocale.ts b/packages/ringcentral-widgets/modules/CallLogUI/i18n/loadLocale.ts new file mode 100644 index 0000000000..12b11cfa2e --- /dev/null +++ b/packages/ringcentral-widgets/modules/CallLogUI/i18n/loadLocale.ts @@ -0,0 +1 @@ +/* loadLocale */ diff --git a/packages/ringcentral-widgets/modules/DialerUI/index.js b/packages/ringcentral-widgets/modules/DialerUI/index.js index 8bd63093c3..cbeb39bd39 100644 --- a/packages/ringcentral-widgets/modules/DialerUI/index.js +++ b/packages/ringcentral-widgets/modules/DialerUI/index.js @@ -141,6 +141,11 @@ export default class DialerUI extends RcUIModule { recipient, }); await this.triggerHook({ phoneNumber, recipient, fromNumber }); + const continueCall = this.callVerify + ? await this.callVerify({ phoneNumber, recipient }) + : true; + if (!continueCall) return; + try { await this._call.call({ phoneNumber: this.toNumberField, diff --git a/packages/ringcentral-widgets/modules/GenericMeetingUI/index.ts b/packages/ringcentral-widgets/modules/GenericMeetingUI/index.ts index b9e680c231..9495c6c73b 100644 --- a/packages/ringcentral-widgets/modules/GenericMeetingUI/index.ts +++ b/packages/ringcentral-widgets/modules/GenericMeetingUI/index.ts @@ -120,6 +120,7 @@ export default class GenericMeetingUI extends RcUIModule { ), showSpinnerInConfigPanel: this._genericMeeting.isUpdating, hasSettingsChanged: this._genericMeeting.hasSettingsChanged, + defaultTopic: this._genericMeeting.defaultTopic, }; } diff --git a/packages/ringcentral-widgets/modules/ModalUIV2/ModalUI.interface.ts b/packages/ringcentral-widgets/modules/ModalUIV2/ModalUI.interface.ts index 1c81f78fcc..2e49196d53 100644 --- a/packages/ringcentral-widgets/modules/ModalUIV2/ModalUI.interface.ts +++ b/packages/ringcentral-widgets/modules/ModalUIV2/ModalUI.interface.ts @@ -1,4 +1,3 @@ -import { ObjectMap } from '@ringcentral-integration/core/lib/ObjectMap'; import { RcButtonProps } from '@ringcentral/juno'; import { Locale } from 'ringcentral-integration/modules/LocaleV2'; import { ModalProps } from '../../components/ModalV2/interface'; @@ -22,6 +21,7 @@ export type ModalOptions = Pick< | 'size' | 'fillContent' | 'loading' + | 'showLoadingOverlay' | 'okText' | 'okVariant' | 'okType' @@ -42,6 +42,7 @@ export type ModalOptions = Pick< footerProps?: Record; dialogOptions?: SimpleDialogProps; variant?: 'alert' | 'confirm' | 'info'; + useLoadingOverlay?: boolean; }; export type ConfirmModalOptions = Omit; diff --git a/packages/ringcentral-widgets/modules/ModalUIV2/ModalUI.tsx b/packages/ringcentral-widgets/modules/ModalUIV2/ModalUI.tsx index 7e7c7933c2..93ba9c1ed0 100644 --- a/packages/ringcentral-widgets/modules/ModalUIV2/ModalUI.tsx +++ b/packages/ringcentral-widgets/modules/ModalUIV2/ModalUI.tsx @@ -35,6 +35,7 @@ import { InfoModalOptions, ModalOptions, } from './ModalUI.interface'; + export const defaultOKRendererID = 'ModalUI.defaultOKRendererID'; export const defaultCancelRendererID = 'ModalUI.defaultCancelRendererID'; export const infoTitleRendererID = 'ModalUI.infoTitleRendererID'; @@ -109,10 +110,22 @@ export class ModalUI extends RcUIModuleV2 { return this._handlerRegister.get(id)?.get(handlerID)?.(...args); } + @action + private _setLoading(id: string, loading: boolean) { + const idx = findIndex((item) => item.id === id, this._modals); + if (this._modals[idx].useLoadingOverlay) { + this._modals[idx].showLoadingOverlay = loading; + } else { + this._modals[idx].loading = loading; + } + } + @proxify private async _onOK(id: string, onOK?: string) { + this._setLoading(id, true); const handler = this._handlerRegister.get(id).get(onOK); if (handler && (await handler()) === false) { + this._setLoading(id, false); return; } this._promises.get(id).resolve(true); diff --git a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/de-DE.ts b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/de-DE.ts index 2254db64da..942cec2ecd 100644 --- a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/de-DE.ts +++ b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/de-DE.ts @@ -1,5 +1,9 @@ export default { - close: "Schließen" + cancel: "Abbrechen", + close: "Schließen", + ok: "OK" }; +// @key: @#@"cancel"@#@ @source: @#@"Cancel"@#@ // @key: @#@"close"@#@ @source: @#@"Close"@#@ +// @key: @#@"ok"@#@ @source: @#@"OK"@#@ diff --git a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/en-AU.ts b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/en-AU.ts index 98b17cf855..46b92a2116 100644 --- a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/en-AU.ts +++ b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/en-AU.ts @@ -1,5 +1,9 @@ export default { - close: "Close" + cancel: "Cancel", + close: "Close", + ok: "OK" }; +// @key: @#@"cancel"@#@ @source: @#@"Cancel"@#@ // @key: @#@"close"@#@ @source: @#@"Close"@#@ +// @key: @#@"ok"@#@ @source: @#@"OK"@#@ diff --git a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/en-GB.ts b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/en-GB.ts index 98b17cf855..46b92a2116 100644 --- a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/en-GB.ts +++ b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/en-GB.ts @@ -1,5 +1,9 @@ export default { - close: "Close" + cancel: "Cancel", + close: "Close", + ok: "OK" }; +// @key: @#@"cancel"@#@ @source: @#@"Cancel"@#@ // @key: @#@"close"@#@ @source: @#@"Close"@#@ +// @key: @#@"ok"@#@ @source: @#@"OK"@#@ diff --git a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/es-419.ts b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/es-419.ts index a45d9a7884..4037c3a9b4 100644 --- a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/es-419.ts +++ b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/es-419.ts @@ -1,5 +1,9 @@ export default { - close: "Cerrar" + cancel: "Cancelar", + close: "Cerrar", + ok: "Aceptar" }; +// @key: @#@"cancel"@#@ @source: @#@"Cancel"@#@ // @key: @#@"close"@#@ @source: @#@"Close"@#@ +// @key: @#@"ok"@#@ @source: @#@"OK"@#@ diff --git a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/es-ES.ts b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/es-ES.ts index a45d9a7884..4037c3a9b4 100644 --- a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/es-ES.ts +++ b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/es-ES.ts @@ -1,5 +1,9 @@ export default { - close: "Cerrar" + cancel: "Cancelar", + close: "Cerrar", + ok: "Aceptar" }; +// @key: @#@"cancel"@#@ @source: @#@"Cancel"@#@ // @key: @#@"close"@#@ @source: @#@"Close"@#@ +// @key: @#@"ok"@#@ @source: @#@"OK"@#@ diff --git a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/fr-CA.ts b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/fr-CA.ts index ebc7d234a4..b5a671b194 100644 --- a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/fr-CA.ts +++ b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/fr-CA.ts @@ -1,5 +1,9 @@ export default { - close: "Fermer" + cancel: "Annuler", + close: "Fermer", + ok: "OK" }; +// @key: @#@"cancel"@#@ @source: @#@"Cancel"@#@ // @key: @#@"close"@#@ @source: @#@"Close"@#@ +// @key: @#@"ok"@#@ @source: @#@"OK"@#@ diff --git a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/fr-FR.ts b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/fr-FR.ts index ebc7d234a4..b5a671b194 100644 --- a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/fr-FR.ts +++ b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/fr-FR.ts @@ -1,5 +1,9 @@ export default { - close: "Fermer" + cancel: "Annuler", + close: "Fermer", + ok: "OK" }; +// @key: @#@"cancel"@#@ @source: @#@"Cancel"@#@ // @key: @#@"close"@#@ @source: @#@"Close"@#@ +// @key: @#@"ok"@#@ @source: @#@"OK"@#@ diff --git a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/it-IT.ts b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/it-IT.ts index 021083e7cd..d8caf50762 100644 --- a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/it-IT.ts +++ b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/it-IT.ts @@ -1,5 +1,9 @@ export default { - close: "Chiudi" + cancel: "Annulla", + close: "Chiudi", + ok: "OK" }; +// @key: @#@"cancel"@#@ @source: @#@"Cancel"@#@ // @key: @#@"close"@#@ @source: @#@"Close"@#@ +// @key: @#@"ok"@#@ @source: @#@"OK"@#@ diff --git a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/ja-JP.ts b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/ja-JP.ts index 0b1d6dd325..44e45eb436 100644 --- a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/ja-JP.ts +++ b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/ja-JP.ts @@ -1,5 +1,9 @@ export default { - close: "閉じる" + cancel: "キャンセル", + close: "閉じる", + ok: "OK" }; +// @key: @#@"cancel"@#@ @source: @#@"Cancel"@#@ // @key: @#@"close"@#@ @source: @#@"Close"@#@ +// @key: @#@"ok"@#@ @source: @#@"OK"@#@ diff --git a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/ko-KR.ts b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/ko-KR.ts index 67207d152e..89a4dcaf68 100644 --- a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/ko-KR.ts +++ b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/ko-KR.ts @@ -1,5 +1,9 @@ export default { - close: "닫기" + cancel: "취소", + close: "닫기", + ok: "확인" }; +// @key: @#@"cancel"@#@ @source: @#@"Cancel"@#@ // @key: @#@"close"@#@ @source: @#@"Close"@#@ +// @key: @#@"ok"@#@ @source: @#@"OK"@#@ diff --git a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/nl-NL.ts b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/nl-NL.ts index 8b7ef992c5..0cdf14c784 100644 --- a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/nl-NL.ts +++ b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/nl-NL.ts @@ -1,5 +1,9 @@ export default { - close: "Sluiten" + cancel: "Annuleren", + close: "Sluiten", + ok: "OK" }; +// @key: @#@"cancel"@#@ @source: @#@"Cancel"@#@ // @key: @#@"close"@#@ @source: @#@"Close"@#@ +// @key: @#@"ok"@#@ @source: @#@"OK"@#@ diff --git a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/pt-BR.ts b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/pt-BR.ts index f85b39edd0..18b27370b1 100644 --- a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/pt-BR.ts +++ b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/pt-BR.ts @@ -1,5 +1,9 @@ export default { - close: "Fechar" + cancel: "Cancelar", + close: "Fechar", + ok: "Ok" }; +// @key: @#@"cancel"@#@ @source: @#@"Cancel"@#@ // @key: @#@"close"@#@ @source: @#@"Close"@#@ +// @key: @#@"ok"@#@ @source: @#@"OK"@#@ diff --git a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/pt-PT.ts b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/pt-PT.ts index f85b39edd0..b38a5fc776 100644 --- a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/pt-PT.ts +++ b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/pt-PT.ts @@ -1,5 +1,9 @@ export default { - close: "Fechar" + cancel: "Cancelar", + close: "Fechar", + ok: "OK" }; +// @key: @#@"cancel"@#@ @source: @#@"Cancel"@#@ // @key: @#@"close"@#@ @source: @#@"Close"@#@ +// @key: @#@"ok"@#@ @source: @#@"OK"@#@ diff --git a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/zh-CN.ts b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/zh-CN.ts index 5d3e9ee316..827ad2b12d 100644 --- a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/zh-CN.ts +++ b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/zh-CN.ts @@ -1,5 +1,9 @@ export default { - close: "关闭" + cancel: "取消", + close: "关闭", + ok: "确定" }; +// @key: @#@"cancel"@#@ @source: @#@"Cancel"@#@ // @key: @#@"close"@#@ @source: @#@"Close"@#@ +// @key: @#@"ok"@#@ @source: @#@"OK"@#@ diff --git a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/zh-HK.ts b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/zh-HK.ts index 218cba3382..68ed25e01f 100644 --- a/packages/ringcentral-widgets/modules/ModalUIV2/i18n/zh-HK.ts +++ b/packages/ringcentral-widgets/modules/ModalUIV2/i18n/zh-HK.ts @@ -1,5 +1,9 @@ export default { - close: "關閉" + cancel: "取消", + close: "關閉", + ok: "確定" }; +// @key: @#@"cancel"@#@ @source: @#@"Cancel"@#@ // @key: @#@"close"@#@ @source: @#@"Close"@#@ +// @key: @#@"ok"@#@ @source: @#@"OK"@#@ diff --git a/packages/ringcentral-widgets/modules/OAuth/index.js b/packages/ringcentral-widgets/modules/OAuth/index.js index d31ad378ff..a915182664 100644 --- a/packages/ringcentral-widgets/modules/OAuth/index.js +++ b/packages/ringcentral-widgets/modules/OAuth/index.js @@ -5,8 +5,8 @@ import ensureExist from 'ringcentral-integration/lib/ensureExist'; import * as uuid from 'uuid'; import popWindow from '../../lib/popWindow'; - import OAuthBase from '../../lib/OAuthBase'; +import { isElectron } from '../../lib/isElectron'; @Module({ name: 'OAuth', @@ -16,6 +16,7 @@ export default class OAuth extends OAuthBase { constructor({ loginPath = '/', redirectUri = './redirect.html', + restrictSameOriginRedirectUri = true, routerInteraction, ...options }) { @@ -30,6 +31,8 @@ export default class OAuth extends OAuthBase { this._loginPath = loginPath; this._loginWindow = null; this._redirectCheckTimeout = null; + this._isInElectron = isElectron(); + this._restrictSameOriginRedirectUri = restrictSameOriginRedirectUri; this._uuid = uuid.v4(); } @@ -147,12 +150,14 @@ export default class OAuth extends OAuthBase { this._redirectCheckTimeout = null; if ( !this._loginWindow || - !this._loginWindow.window || - this._loginWindow.closed + this._loginWindow.closed || + // for electron, the .window is always undefined + (!this._isInElectron && !this._loginWindow.window) ) { this._loginWindow = null; return; } + try { const callbackUri = this._loginWindow.location.href; if (callbackUri.indexOf(this.redirectUri) !== -1) { @@ -170,7 +175,9 @@ export default class OAuth extends OAuthBase { } get isRedirectUriSameOrigin() { - return this.redirectUri.indexOf(window.origin) === 0; + return this.restrictSameOriginRedirectUri + ? this.redirectUri.indexOf(window.origin) === 0 + : true; } get authState() { diff --git a/packages/ringcentral-widgets/modules/OAuth/interface.ts b/packages/ringcentral-widgets/modules/OAuth/interface.ts new file mode 100644 index 0000000000..40361fac30 --- /dev/null +++ b/packages/ringcentral-widgets/modules/OAuth/interface.ts @@ -0,0 +1,7 @@ +// TODO +// Once transform OAuth module to TS, export this from index.ts? +export interface OAuthOptions { + redirectUri?: string; + loginPath?: string; + restrictSameOriginRedirectUri?: boolean; +} diff --git a/packages/ringcentral-widgets/modules/TransferUI/index.ts b/packages/ringcentral-widgets/modules/TransferUI/index.ts index a14c699642..275122c148 100644 --- a/packages/ringcentral-widgets/modules/TransferUI/index.ts +++ b/packages/ringcentral-widgets/modules/TransferUI/index.ts @@ -34,7 +34,10 @@ export default class TransferUI extends RcUIModule { this._routerInteraction = routerInteraction; } - getUIProps({ params: { sessionId, type = 'active' } }) { + getUIProps({ + params: { sessionId, type = 'active' }, + enableWarmTransfer = false, + }) { let session = null; if (type === 'active' && this._activeCallControl) { session = this._activeCallControl.activeSession; @@ -52,6 +55,7 @@ export default class TransferUI extends RcUIModule { session, controlBusy: (this._activeCallControl && this._activeCallControl.busy) || false, + enableWarmTransfer: enableWarmTransfer && !!this._webphone, }; } @@ -63,18 +67,23 @@ export default class TransferUI extends RcUIModule { phoneTypeRenderer, }) { return { - setActiveSessionId: (sessionId) => { + setActiveSessionId: (sessionId: string) => { if (type === 'active' && this._activeCallControl) { this._activeCallControl.setActiveSessionId(sessionId); } }, - onTransfer: (transferNumber, sessionId) => { + onTransfer: (transferNumber: string, sessionId: string) => { if (type === 'active' && this._activeCallControl) { this._activeCallControl.transfer(transferNumber, sessionId); } else if (type === 'webphone' && this._webphone) { this._webphone.transfer(transferNumber, sessionId); } }, + onWarmTransfer: (transferNumber: string, sessionId: string) => { + if (this._webphone) { + this._webphone.startWarmTransfer(transferNumber, sessionId); + } + }, onBack: () => { this._routerInteraction.goBack(); }, diff --git a/packages/ringcentral-widgets/package.json b/packages/ringcentral-widgets/package.json index 8acb30e292..2a8cbc8ea3 100644 --- a/packages/ringcentral-widgets/package.json +++ b/packages/ringcentral-widgets/package.json @@ -50,7 +50,7 @@ "@ringcentral-integration/locale-loader": "*", "@ringcentral-integration/locale-settings": "*", "@ringcentral-integration/phone-number": "*", - "@ringcentral/juno": "^1.3.2-beta.3102-608720c9", + "@ringcentral/juno": "^1.4.1", "@ringcentral/sdk": "^4.3.2", "@ringcentral/subscriptions": "^4.3.2", "@testing-library/jest-dom": "^5.11.2", @@ -58,6 +58,7 @@ "autoprefixer": "^9.8.4", "babel-istanbul": "^0.12.1", "babel-loader": "^8.1.0", + "crius-test": "^1.1.3", "execa": "^1.0.0", "format-message": "^6.2.3", "fs-extra": "^9.0.1", @@ -88,7 +89,7 @@ "@ringcentral-integration/core": "^0.12.0", "@ringcentral-integration/i18n": "^2.0.1", "@ringcentral-integration/phone-number": "^1.0.4", - "@ringcentral/juno": "^1.3.2-beta.3102-608720c9", + "@ringcentral/juno": "^1.4.1", "@ringcentral/sdk": "^4.3.2", "@ringcentral/subscriptions": "^4.3.2", "format-message": "^6.2.3", diff --git a/packages/ringcentral-widgets/test/components/CallHistoryPanel/CallHistoryPanelList.ut.tsx b/packages/ringcentral-widgets/test/components/CallHistoryPanel/CallHistoryPanelList.ut.tsx new file mode 100644 index 0000000000..f4ea9c014e --- /dev/null +++ b/packages/ringcentral-widgets/test/components/CallHistoryPanel/CallHistoryPanelList.ut.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { StepFunction } from 'crius-test'; +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; + +import { CallHistoryPanel } from '../../../components/CallHistoryPanel'; + +interface CallHistoryPanelProps { + calls: any[]; +} + +export const UTListNoData: StepFunction = ({ + calls, +}) => { + const { container } = render( + , + ); + const emptyNode = container.querySelector('.empty'); + expect(emptyNode.innerHTML).toEqual('No call records'); +}; diff --git a/packages/test-utils/README.md b/packages/test-utils/README.md index cbece9645e..d9ff6fbc1a 100644 --- a/packages/test-utils/README.md +++ b/packages/test-utils/README.md @@ -23,3 +23,17 @@ Run the following command to start `jest.retryTimes(3)` by default on CI: ```sh cross-env NODE_ENV=ci yarn jest ``` + +## APIs + +- `mount(Component, props)` + +```tsx +import { mount } from '@ringcentral-integration/test-utils/lib/render'; +import { Foobar } from '../components/Foobar'; + +test('', () => { + // `const app = render();` is equivalent to: + const app = mount(Foobar, { version: '' }); +}); +``` diff --git a/packages/test-utils/lib/render.tsx b/packages/test-utils/lib/render.tsx new file mode 100644 index 0000000000..c543700984 --- /dev/null +++ b/packages/test-utils/lib/render.tsx @@ -0,0 +1,35 @@ +import React, { ComponentType } from 'react'; +import { RcThemeProvider } from '@ringcentral/juno'; +import { render } from '@testing-library/react'; + +/** + * mount ui module with props automatically + * @param page any module page + * @param sourceProps props you want to set on page + */ +export const pageMount = (page: any, sourceProps: any) => { + const obj = page(sourceProps); + const { Component, props } = obj; + return render( + + + , + ); +}; + +/** + * Render React component with `@testing-library/react`. + * + * @param {ComponentType} Component React component + * @param {object} props the component's props + */ +export const mount: ( + Component: ComponentType, + props: T, +) => ReturnType = (Component, props) => { + return render( + + + , + ); +}; diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 611e2aea4a..3c324d7b93 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -22,6 +22,8 @@ "license": "MIT", "dependencies": { "@ringcentral-integration/babel-settings": "*", + "@ringcentral/juno": "^1.4.1", + "@testing-library/react": "^11.2.3", "babel-jest": "^26.3.0", "babel-preset-crius": "^1.0.0-alpha.2", "cross-env": "^5.1.3", @@ -32,6 +34,7 @@ "jest": "^26.3.0", "jest-circus": "^26.3.0", "jest-junit": "^6.3.0", + "react": "^16.14.0", "ringcentral-crius": "*" } } diff --git a/yarn.lock b/yarn.lock index 3ad525d506..71ae382b49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -182,10 +182,10 @@ dependencies: "@babel/types" "^7.10.4" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" - integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz#806526ce125aed03373bc416a828321e3a6a33af" + integrity sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ== "@babel/helper-regex@^7.10.4": version "7.10.5" @@ -534,12 +534,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-typescript@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.1.tgz#460ba9d77077653803c3dd2e673f76d66b4029e5" - integrity sha512-UZNEcCY+4Dp9yYRCAHrHDU+9ZXLYaY9MgBXSRLkB9WjYFRR6quJBumfVrEkUxrePPBwFcpWfNKXqVRQQtm7mMA== +"@babel/plugin-syntax-typescript@^7.12.1", "@babel/plugin-syntax-typescript@^7.3.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.13.tgz#9dff111ca64154cef0f4dc52cf843d9f12ce4474" + integrity sha512-cHP3u1JiUiG2LFDKbXnwVad81GvfyIOmCD6HIEId6ojrY0Drfy2q1jw7BwN7dE84+kTnBjLkXoL3IEy/3JPu2w== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.12.13" "@babel/plugin-transform-arrow-functions@^7.12.1": version "7.12.1" @@ -971,14 +971,7 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.1.tgz#b4116a6b6711d010b2dad3b7b6e43bf1b9954740" - integrity sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA== - dependencies: - regenerator-runtime "^0.13.4" - -"@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.12.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== @@ -1385,10 +1378,10 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" -"@jest/types@^26.6.1": - version "26.6.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.1.tgz#2638890e8031c0bc8b4681e0357ed986e2f866c5" - integrity sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg== +"@jest/types@^26.6.1", "@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" @@ -1396,36 +1389,36 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@material-ui/core@4.10.2": - version "4.10.2" - resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.10.2.tgz#0ef78572132fcef1a25f6969bce0d34652d42e31" - integrity sha512-Uf4iDLi9sW6HKbVQDyDZDr1nMR4RUAE7w/RIIJZGNVZResC0xwmpLRZMtaUdSO43N0R0yJehfxTi4Z461Cd49A== +"@material-ui/core@^4.10.2": + version "4.11.3" + resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.11.3.tgz#f22e41775b0bd075e36a7a093d43951bf7f63850" + integrity sha512-Adt40rGW6Uds+cAyk3pVgcErpzU/qxc7KBR94jFHBYretU4AtWZltYcNsbeMn9tXL86jjVL1kuGcIHsgLgFGRw== dependencies: "@babel/runtime" "^7.4.4" - "@material-ui/styles" "^4.10.0" - "@material-ui/system" "^4.9.14" + "@material-ui/styles" "^4.11.3" + "@material-ui/system" "^4.11.3" "@material-ui/types" "^5.1.0" - "@material-ui/utils" "^4.10.2" + "@material-ui/utils" "^4.11.2" "@types/react-transition-group" "^4.2.0" clsx "^1.0.4" hoist-non-react-statics "^3.3.2" popper.js "1.16.1-lts" prop-types "^15.7.2" - react-is "^16.8.0" + react-is "^16.8.0 || ^17.0.0" react-transition-group "^4.4.0" -"@material-ui/lab@4.0.0-alpha.56": - version "4.0.0-alpha.56" - resolved "https://registry.yarnpkg.com/@material-ui/lab/-/lab-4.0.0-alpha.56.tgz#ff63080949b55b40625e056bbda05e130d216d34" - integrity sha512-xPlkK+z/6y/24ka4gVJgwPfoCF4RCh8dXb1BNE7MtF9bXEBLN/lBxNTK8VAa0qm3V2oinA6xtUIdcRh0aeRtVw== +"@material-ui/lab@^4.0.0-alpha.56": + version "4.0.0-alpha.57" + resolved "https://registry.yarnpkg.com/@material-ui/lab/-/lab-4.0.0-alpha.57.tgz#e8961bcf6449e8a8dabe84f2700daacfcafbf83a" + integrity sha512-qo/IuIQOmEKtzmRD2E4Aa6DB4A87kmY6h0uYhjUmrrgmEAgbbw9etXpWPVXuRK6AGIQCjFzV6WO2i21m1R4FCw== dependencies: "@babel/runtime" "^7.4.4" - "@material-ui/utils" "^4.10.2" + "@material-ui/utils" "^4.11.2" clsx "^1.0.4" prop-types "^15.7.2" - react-is "^16.8.0" + react-is "^16.8.0 || ^17.0.0" -"@material-ui/pickers@3.2.10": +"@material-ui/pickers@^3.2.10": version "3.2.10" resolved "https://registry.yarnpkg.com/@material-ui/pickers/-/pickers-3.2.10.tgz#19df024895876eb0ec7cd239bbaea595f703f0ae" integrity sha512-B8G6Obn5S3RCl7hwahkQj9sKUapwXWFjiaz/Bsw1fhYFdNMnDUolRiWQSoKPb1/oKe37Dtfszoywi1Ynbo3y8w== @@ -1437,35 +1430,35 @@ react-transition-group "^4.0.0" rifm "^0.7.0" -"@material-ui/styles@4.10.0", "@material-ui/styles@^4.10.0": - version "4.10.0" - resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.10.0.tgz#2406dc23aa358217aa8cc772e6237bd7f0544071" - integrity sha512-XPwiVTpd3rlnbfrgtEJ1eJJdFCXZkHxy8TrdieaTvwxNYj42VnnCyFzxYeNW9Lhj4V1oD8YtQ6S5Gie7bZDf7Q== +"@material-ui/styles@^4.10.0", "@material-ui/styles@^4.11.3": + version "4.11.3" + resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.3.tgz#1b8d97775a4a643b53478c895e3f2a464e8916f2" + integrity sha512-HzVzCG+PpgUGMUYEJ2rTEmQYeonGh41BYfILNFb/1ueqma+p1meSdu4RX6NjxYBMhf7k+jgfHFTTz+L1SXL/Zg== dependencies: "@babel/runtime" "^7.4.4" "@emotion/hash" "^0.8.0" "@material-ui/types" "^5.1.0" - "@material-ui/utils" "^4.9.6" + "@material-ui/utils" "^4.11.2" clsx "^1.0.4" csstype "^2.5.2" hoist-non-react-statics "^3.3.2" - jss "^10.0.3" - jss-plugin-camel-case "^10.0.3" - jss-plugin-default-unit "^10.0.3" - jss-plugin-global "^10.0.3" - jss-plugin-nested "^10.0.3" - jss-plugin-props-sort "^10.0.3" - jss-plugin-rule-value-function "^10.0.3" - jss-plugin-vendor-prefixer "^10.0.3" + jss "^10.5.1" + jss-plugin-camel-case "^10.5.1" + jss-plugin-default-unit "^10.5.1" + jss-plugin-global "^10.5.1" + jss-plugin-nested "^10.5.1" + jss-plugin-props-sort "^10.5.1" + jss-plugin-rule-value-function "^10.5.1" + jss-plugin-vendor-prefixer "^10.5.1" prop-types "^15.7.2" -"@material-ui/system@^4.9.14": - version "4.9.14" - resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.9.14.tgz#4b00c48b569340cefb2036d0596b93ac6c587a5f" - integrity sha512-oQbaqfSnNlEkXEziDcJDDIy8pbvwUmZXWNqlmIwDqr/ZdCK8FuV3f4nxikUh7hvClKV2gnQ9djh5CZFTHkZj3w== +"@material-ui/system@^4.11.3": + version "4.11.3" + resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.11.3.tgz#466bc14c9986798fd325665927c963eb47cc4143" + integrity sha512-SY7otguNGol41Mu2Sg6KbBP1ZRFIbFLHGK81y4KYbsV2yIcaEPOmsCK6zwWlp+2yTV3J/VwT6oSBARtGIVdXPw== dependencies: "@babel/runtime" "^7.4.4" - "@material-ui/utils" "^4.9.6" + "@material-ui/utils" "^4.11.2" csstype "^2.5.2" prop-types "^15.7.2" @@ -1474,14 +1467,14 @@ resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-5.1.0.tgz#efa1c7a0b0eaa4c7c87ac0390445f0f88b0d88f2" integrity sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A== -"@material-ui/utils@^4.10.2", "@material-ui/utils@^4.9.6": - version "4.10.2" - resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.10.2.tgz#3fd5470ca61b7341f1e0468ac8f29a70bf6df321" - integrity sha512-eg29v74P7W5r6a4tWWDAAfZldXIzfyO1am2fIsC39hdUUHm/33k6pGOKPbgDjg/U/4ifmgAePy/1OjkKN6rFRw== +"@material-ui/utils@^4.11.2": + version "4.11.2" + resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.11.2.tgz#f1aefa7e7dff2ebcb97d31de51aecab1bb57540a" + integrity sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA== dependencies: "@babel/runtime" "^7.4.4" prop-types "^15.7.2" - react-is "^16.8.0" + react-is "^16.8.0 || ^17.0.0" "@nodelib/fs.scandir@2.1.3": version "2.1.3" @@ -1542,24 +1535,24 @@ isomorphic-ws "^4.0.1" ws "^7.4.2" -"@ringcentral/juno@^1.3.2-beta.3102-608720c9": - version "1.3.2-beta.3102-608720c9" - resolved "https://registry.yarnpkg.com/@ringcentral/juno/-/juno-1.3.2-beta.3102-608720c9.tgz#dbe03c8ba9ba1a49e8d5d80f154fa3a0a01d8c0a" - integrity sha512-lBGV4rvVTtT10aIebQ5cS2MC0wcH/4bB9lWoQjxa2kPx06YkiA17g5kLuTrryonbqpbwTl7SdnVYCsHzntbYWg== +"@ringcentral/juno@^1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@ringcentral/juno/-/juno-1.4.1.tgz#cf7fe17419d7028080f6b66249c5e6b13b864b25" + integrity sha512-x1l0nwlAAQ5bfCmIWpHuyjvIVt1Fd+uClT/z5pRR0tsRloonUX8OmQ79o2AGJJWWT9y/Q1BLmzP1Wji3MKfVyQ== dependencies: "@date-io/moment" "^1.3.11" "@emotion/core" "^10.0.27" - "@material-ui/core" "4.10.2" - "@material-ui/lab" "4.0.0-alpha.56" - "@material-ui/pickers" "3.2.10" - "@material-ui/styles" "4.10.0" + "@material-ui/core" "^4.10.2" + "@material-ui/lab" "^4.0.0-alpha.56" + "@material-ui/pickers" "^3.2.10" + "@material-ui/styles" "^4.10.0" "@types/animejs" "^3.1.0" "@types/lodash" "^4.14.120" - "@types/react-beautiful-dnd" "13.0.0" + "@types/react-beautiful-dnd" "^13.0.0" "@types/shelljs" "^0.8.5" - "@types/smoothscroll-polyfill" "0.3.1" + "@types/smoothscroll-polyfill" "^0.3.1" "@types/styled-components" "4.0.3" - "@types/tinycolor2" "1.4.1" + "@types/tinycolor2" "^1.4.1" animejs "^3.1.0" clsx "^1.0.4" deepmerge "^4.0.0" @@ -1567,7 +1560,7 @@ focus-visible "^5.1.0" lodash "^4.17.15" re-resizable "^6.1.1" - react-beautiful-dnd "13.0.0" + react-beautiful-dnd "^13.0.0" react-draggable "^4.4.3" react-resize-detector "^4.2.1" react-sortable-hoc "^1.11.0" @@ -1577,7 +1570,6 @@ smoothscroll-polyfill "^0.4.4" tinycolor2 "^1.4.1" typeface-lato "^0.0.75" - uuid "^8.3.0" "@ringcentral/sdk@^4.3.2": version "4.3.2" @@ -1661,16 +1653,19 @@ dependencies: "@sinonjs/commons" "^1.7.0" -"@testing-library/dom@^7.17.1": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.22.1.tgz#b66861fb7751287bda63a55f5c72ca808c63043c" - integrity sha512-bEszhvj9LcspaRz56mqGV7uc+vJTAYKCKPJcGb5X6U1qBysgTAgCexQXvKZ3BBjWu5S/TANP2NniOVsMWqKXcw== +"@testing-library/dom@^7.17.1", "@testing-library/dom@^7.28.1": + version "7.29.6" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.29.6.tgz#eb37844fb431186db7960a7ff6749ea65a19617c" + integrity sha512-vzTsAXa439ptdvav/4lsKRcGpAQX7b6wBIqia7+iNzqGJ5zjswApxA6jDAsexrc6ue9krWcbh8o+LYkBXW+GCQ== dependencies: - "@babel/runtime" "^7.10.3" + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" "@types/aria-query" "^4.2.0" aria-query "^4.2.2" - dom-accessibility-api "^0.5.0" - pretty-format "^25.5.0" + chalk "^4.1.0" + dom-accessibility-api "^0.5.4" + lz-string "^1.4.4" + pretty-format "^26.6.2" "@testing-library/jest-dom@^5.11.2": version "5.11.3" @@ -1696,6 +1691,14 @@ "@babel/runtime" "^7.10.3" "@testing-library/dom" "^7.17.1" +"@testing-library/react@^11.2.3": + version "11.2.5" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.5.tgz#ae1c36a66c7790ddb6662c416c27863d87818eb9" + integrity sha512-yEx7oIa/UWLe2F2dqK0FtMF9sJWNXD+2PPtp39BvE0Kh9MJ9Kl0HrZAgEuhUJR+Lx8Di6Xz+rKwSdEPY2UV8ZQ== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^7.28.1" + "@types/animejs@^3.1.0": version "3.1.2" resolved "https://registry.yarnpkg.com/@types/animejs/-/animejs-3.1.2.tgz#93289a902b0b9bfac3020d29413139dcca6acdbc" @@ -1854,7 +1857,7 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.5.tgz#434711bdd49eb5ee69d90c1d67c354a9a8ecb18b" integrity sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ== -"@types/react-beautiful-dnd@13.0.0": +"@types/react-beautiful-dnd@^13.0.0": version "13.0.0" resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#e60d3d965312fcf1516894af92dc3e9249587db4" integrity sha512-by80tJ8aTTDXT256Gl+RfLRtFjYbUWOnZuEigJgNsJrSEGxvFe5eY6k3g4VIvf0M/6+xoLgfYWoWonlOo6Wqdg== @@ -1884,7 +1887,7 @@ "@types/glob" "*" "@types/node" "*" -"@types/smoothscroll-polyfill@0.3.1": +"@types/smoothscroll-polyfill@^0.3.1": version "0.3.1" resolved "https://registry.yarnpkg.com/@types/smoothscroll-polyfill/-/smoothscroll-polyfill-0.3.1.tgz#77fb3a6e116bdab4a5959122e3b8e201224dcd49" integrity sha512-+KkHw4y+EyeCtVXET7woHUhIbfWFCflc0E0mZnSV+ZdjPQeHt/9KPEuT7gSW/kFQ8O3EG30PLO++YhChDt8+Ag== @@ -1916,10 +1919,10 @@ dependencies: "@types/jest" "*" -"@types/tinycolor2@1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.1.tgz#2f5670c9d1d6e558897a810ed284b44918fc1253" - integrity sha512-25L/RL5tqZkquKXVHM1fM2bd23qjfbcPpAZ2N/H05Y45g3UEi+Hw8CbDV28shKY8gH1SHiLpZSxPI1lacqdpGg== +"@types/tinycolor2@^1.4.1": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.2.tgz#721ca5c5d1a2988b4a886e35c2ffc5735b6afbdf" + integrity sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw== "@types/ws@^7.4.0": version "7.4.0" @@ -2963,6 +2966,14 @@ babel-messages@^6.23.0: dependencies: babel-runtime "^6.22.0" +babel-plugin-const-enum@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-plugin-const-enum/-/babel-plugin-const-enum-1.0.1.tgz#0d742faf9731be4f213c4d01d61fc4e93c44d159" + integrity sha512-6oGu63g1FS9psUPQyLCJM08ty6kGihGKTbzWGbAKHfUuCzCh7y9twh516cR6v0lM4d4NOoR+DgLb7uKVytyp6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-typescript" "^7.3.3" + babel-plugin-dynamic-import-node@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" @@ -3878,7 +3889,7 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== @@ -4777,7 +4788,7 @@ css-tree@1.0.0-alpha.39: mdn-data "2.0.6" source-map "^0.6.1" -css-vendor@^2.0.7: +css-vendor@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d" integrity sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ== @@ -4848,11 +4859,16 @@ cssstyle@^2.2.0: dependencies: cssom "~0.3.6" -csstype@^2.2.0, csstype@^2.5.2, csstype@^2.5.7, csstype@^2.6.5, csstype@^2.6.7: +csstype@^2.2.0, csstype@^2.5.2, csstype@^2.5.7, csstype@^2.6.7: version "2.6.13" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f" integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A== +csstype@^3.0.2: + version "3.0.7" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.7.tgz#2a5fb75e1015e84dd15692f71e89a1450290950b" + integrity sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g== + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -4925,6 +4941,13 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +data-transport@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/data-transport/-/data-transport-1.2.0.tgz#c512111f3925336f9637dcd38570c62302e164b6" + integrity sha512-+yhgqBtSmOj6K0kf31GHu8X0CbNzYYCtfjrjPdTtrmrbzF4W5gQimael8pjQbr9F5PFIdq428JNmVIwRkGugXg== + dependencies: + uuid "^8.3.0" + data-uri-to-buffer@1: version "1.2.0" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz#77163ea9c20d8641b4707e8f18abdf9a78f34835" @@ -4981,7 +5004,7 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@3.X, debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5: +debug@3.X, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -5310,10 +5333,10 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.0.tgz#fddffd04e178796e241436c3f21be2f89c91afac" - integrity sha512-eCVf9n4Ni5UQAFc2+fqfMPHdtiX7DA0rLakXgNBZfXNJzEbNo3MQIYd+zdYpFBqAaGYVrkd8leNSLGPrG4ODmA== +dom-accessibility-api@^0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166" + integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ== dom-align@^1.7.0: version "1.12.0" @@ -6666,14 +6689,7 @@ focus-visible@^5.1.0: resolved "https://registry.yarnpkg.com/focus-visible/-/focus-visible-5.2.0.tgz#3a9e41fccf587bd25dcc2ef045508284f0a4d6b3" integrity sha512-Rwix9pBtC1Nuy5wysTmKy+UjbDJpIfg8eHjw0rjZ1mX4GNLz1Bmd16uDpI3Gk1i70Fgcs8Csg2lPm8HULFg9DQ== -follow-redirects@^1.0.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.11.0.tgz#afa14f08ba12a52963140fe43212658897bc0ecb" - integrity sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA== - dependencies: - debug "^3.0.0" - -follow-redirects@^1.10.0: +follow-redirects@^1.0.0, follow-redirects@^1.10.0: version "1.13.1" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg== @@ -7888,6 +7904,13 @@ in-publish@^2.0.0: resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.1.tgz#948b1a535c8030561cea522f73f78f4be357e00c" integrity sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ== +indefinite-observable@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/indefinite-observable/-/indefinite-observable-2.0.1.tgz#574af29bfbc17eb5947793797bddc94c9d859400" + integrity sha512-G8vgmork+6H9S8lUAg1gtXEj2JxIQTo0g2PbFiYOdjkziSI0F7UYBiVwhZRuixhBCNGczAls34+5HJPyZysvxQ== + dependencies: + symbol-observable "1.2.0" + indent-string@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" @@ -9251,72 +9274,74 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jss-plugin-camel-case@^10.0.3: - version "10.1.1" - resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.1.1.tgz#8e73ecc4f1d0f8dfe4dd31f6f9f2782588970e78" - integrity sha512-MDIaw8FeD5uFz1seQBKz4pnvDLnj5vIKV5hXSVdMaAVq13xR6SVTVWkIV/keyTs5txxTvzGJ9hXoxgd1WTUlBw== +jss-plugin-camel-case@^10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.5.1.tgz#427b24a9951b4c2eaa7e3d5267acd2e00b0934f9" + integrity sha512-9+oymA7wPtswm+zxVti1qiowC5q7bRdCJNORtns2JUj/QHp2QPXYwSNRD8+D2Cy3/CEMtdJzlNnt5aXmpS6NAg== dependencies: "@babel/runtime" "^7.3.1" hyphenate-style-name "^1.0.3" - jss "10.1.1" + jss "10.5.1" -jss-plugin-default-unit@^10.0.3: - version "10.1.1" - resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.1.1.tgz#2df86016dfe73085eead843f5794e3890e9c5c47" - integrity sha512-UkeVCA/b3QEA4k0nIKS4uWXDCNmV73WLHdh2oDGZZc3GsQtlOCuiH3EkB/qI60v2MiCq356/SYWsDXt21yjwdg== +jss-plugin-default-unit@^10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.5.1.tgz#2be385d71d50aee2ee81c2a9ac70e00592ed861b" + integrity sha512-D48hJBc9Tj3PusvlillHW8Fz0y/QqA7MNmTYDQaSB/7mTrCZjt7AVRROExoOHEtd2qIYKOYJW3Jc2agnvsXRlQ== dependencies: "@babel/runtime" "^7.3.1" - jss "10.1.1" + jss "10.5.1" -jss-plugin-global@^10.0.3: - version "10.1.1" - resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.1.1.tgz#36b0d6d9facb74dfd99590643708a89260747d14" - integrity sha512-VBG3wRyi3Z8S4kMhm8rZV6caYBegsk+QnQZSVmrWw6GVOT/Z4FA7eyMu5SdkorDlG/HVpHh91oFN56O4R9m2VA== +jss-plugin-global@^10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.5.1.tgz#0e1793dea86c298360a7e2004721351653c7e764" + integrity sha512-jX4XpNgoaB8yPWw/gA1aPXJEoX0LNpvsROPvxlnYe+SE0JOhuvF7mA6dCkgpXBxfTWKJsno7cDSCgzHTocRjCQ== dependencies: "@babel/runtime" "^7.3.1" - jss "10.1.1" + jss "10.5.1" -jss-plugin-nested@^10.0.3: - version "10.1.1" - resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.1.1.tgz#5c3de2b8bda344de1ebcef3a4fd30870a29a8a8c" - integrity sha512-ozEu7ZBSVrMYxSDplPX3H82XHNQk2DQEJ9TEyo7OVTPJ1hEieqjDFiOQOxXEj9z3PMqkylnUbvWIZRDKCFYw5Q== +jss-plugin-nested@^10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.5.1.tgz#8753a80ad31190fb6ac6fdd39f57352dcf1295bb" + integrity sha512-xXkWKOCljuwHNjSYcXrCxBnjd8eJp90KVFW1rlhvKKRXnEKVD6vdKXYezk2a89uKAHckSvBvBoDGsfZrldWqqQ== dependencies: "@babel/runtime" "^7.3.1" - jss "10.1.1" + jss "10.5.1" tiny-warning "^1.0.2" -jss-plugin-props-sort@^10.0.3: - version "10.1.1" - resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.1.1.tgz#34bddcbfaf9430ec8ccdf92729f03bb10caf1785" - integrity sha512-g/joK3eTDZB4pkqpZB38257yD4LXB0X15jxtZAGbUzcKAVUHPl9Jb47Y7lYmiGsShiV4YmQRqG1p2DHMYoK91g== +jss-plugin-props-sort@^10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.5.1.tgz#ab1c167fd2d4506fb6a1c1d66c5f3ef545ff1cd8" + integrity sha512-t+2vcevNmMg4U/jAuxlfjKt46D/jHzCPEjsjLRj/J56CvP7Iy03scsUP58Iw8mVnaV36xAUZH2CmAmAdo8994g== dependencies: "@babel/runtime" "^7.3.1" - jss "10.1.1" + jss "10.5.1" -jss-plugin-rule-value-function@^10.0.3: - version "10.1.1" - resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.1.1.tgz#be00dac6fc394aaddbcef5860b9eca6224d96382" - integrity sha512-ClV1lvJ3laU9la1CUzaDugEcwnpjPTuJ0yGy2YtcU+gG/w9HMInD5vEv7xKAz53Bk4WiJm5uLOElSEshHyhKNw== +jss-plugin-rule-value-function@^10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.5.1.tgz#37f4030523fb3032c8801fab48c36c373004de7e" + integrity sha512-3gjrSxsy4ka/lGQsTDY8oYYtkt2esBvQiceGBB4PykXxHoGRz14tbCK31Zc6DHEnIeqsjMUGbq+wEly5UViStQ== dependencies: "@babel/runtime" "^7.3.1" - jss "10.1.1" + jss "10.5.1" + tiny-warning "^1.0.2" -jss-plugin-vendor-prefixer@^10.0.3: - version "10.1.1" - resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.1.1.tgz#8348b20749f790beebab3b6a8f7075b07c2cfcfd" - integrity sha512-09MZpQ6onQrhaVSF6GHC4iYifQ7+4YC/tAP6D4ZWeZotvCMq1mHLqNKRIaqQ2lkgANjlEot2JnVi1ktu4+L4pw== +jss-plugin-vendor-prefixer@^10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.5.1.tgz#45a183a3a0eb097bdfab0986b858d99920c0bbd8" + integrity sha512-cLkH6RaPZWHa1TqSfd2vszNNgxT1W0omlSjAd6hCFHp3KIocSrW21gaHjlMU26JpTHwkc+tJTCQOmE/O1A4FKQ== dependencies: "@babel/runtime" "^7.3.1" - css-vendor "^2.0.7" - jss "10.1.1" + css-vendor "^2.0.8" + jss "10.5.1" -jss@10.1.1, jss@^10.0.3: - version "10.1.1" - resolved "https://registry.yarnpkg.com/jss/-/jss-10.1.1.tgz#450b27d53761af3e500b43130a54cdbe157ea332" - integrity sha512-Xz3qgRUFlxbWk1czCZibUJqhVPObrZHxY3FPsjCXhDld4NOj1BgM14Ir5hVm+Qr6OLqVljjGvoMcCdXNOAbdkQ== +jss@10.5.1, jss@^10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/jss/-/jss-10.5.1.tgz#93e6b2428c840408372d8b548c3f3c60fa601c40" + integrity sha512-hbbO3+FOTqVdd7ZUoTiwpHzKXIo5vGpMNbuXH1a0wubRSWLWSBvwvaq4CiHH/U42CmjOnp6lVNNs/l+Z7ZdDmg== dependencies: "@babel/runtime" "^7.3.1" - csstype "^2.6.5" + csstype "^3.0.2" + indefinite-observable "^2.0.1" is-in-browser "^1.1.3" tiny-warning "^1.0.2" @@ -9869,6 +9894,11 @@ lru-queue@0.1: dependencies: es5-ext "~0.10.2" +lz-string@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" + integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= + make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -11662,12 +11692,12 @@ pretty-format@^25.5.0: ansi-styles "^4.0.0" react-is "^16.12.0" -pretty-format@^26.0.0, pretty-format@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.1.tgz#af9a2f63493a856acddeeb11ba6bcf61989660a8" - integrity sha512-MeqqsP5PYcRBbGMvwzsyBdmAJ4EFX7pWFyl7x4+dMVg5pE0ZDdBIvEH2ergvIO+Gvwv1wh64YuOY9y5LuyY/GA== +pretty-format@^26.0.0, pretty-format@^26.6.1, pretty-format@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" + integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== dependencies: - "@jest/types" "^26.6.1" + "@jest/types" "^26.6.2" ansi-regex "^5.0.0" ansi-styles "^4.0.0" react-is "^17.0.1" @@ -12127,7 +12157,7 @@ react-base16-styling@^0.5.1: lodash.flow "^3.3.0" pure-color "^1.2.0" -react-beautiful-dnd@13.0.0: +react-beautiful-dnd@^13.0.0: version "13.0.0" resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#f70cc8ff82b84bc718f8af157c9f95757a6c3b40" integrity sha512-87It8sN0ineoC3nBW0SbQuTFXM6bUqM62uJGY4BtTf0yzPl8/3+bHMWkgIe0Z6m8e+gJgjWxefGRVfpE3VcdEg== @@ -12184,12 +12214,12 @@ react-emojione@^5.0.0: resolved "https://registry.yarnpkg.com/react-emojione/-/react-emojione-5.0.1.tgz#faa4ccdad4bf24e2f5a1a96d9b44c2e902d2bc79" integrity sha512-sjI6k8uQ14rWENYoAb+2BFQGLBt/cpLDJJNhnZvdFJytAJijhv+JmbmyyrfQPdyID0Cs4N8XFqnek0xq6POwGA== -react-is@^16.12.0, react-is@^16.13.0, react-is@^16.13.1, react-is@^16.3.2, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0: +react-is@^16.12.0, react-is@^16.13.0, react-is@^16.13.1, react-is@^16.3.2, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^17.0.1: +"react-is@^16.8.0 || ^17.0.0", react-is@^17.0.1: version "17.0.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== @@ -12975,10 +13005,10 @@ ringcentral-call-control@^0.2.5: resolved "https://registry.yarnpkg.com/ringcentral-call-control/-/ringcentral-call-control-0.2.5.tgz#afe851e44ca6c02c464d41a74d27d4994771cf02" integrity sha512-RlcKMGqSDA1dOeC+dIorUZIBuiGUr6I7GeRFRZlrLdw5NrdwqqaXfmI7C9rSwa9rVn916ArON76AStC6gijKJQ== -ringcentral-call@^0.2.11: - version "0.2.11" - resolved "https://registry.yarnpkg.com/ringcentral-call/-/ringcentral-call-0.2.11.tgz#0d677029e8d4435cfe5cc8b837a8c9b90333e27d" - integrity sha512-3hZkRlk3elMivrlElFT5bU+qIBRndz83OGtuk1UXO8Ligz8d2AH3dixDMk+oMvN+YvXgfm1oSfIfvHcXP0c/pA== +ringcentral-call@^0.2.12: + version "0.2.12" + resolved "https://registry.yarnpkg.com/ringcentral-call/-/ringcentral-call-0.2.12.tgz#3e7e33d81c0fa51cda6765ffe790f89a5676ce63" + integrity sha512-pHFLt9UEDJCo3JmnZ504rXiSlooFWy3pAZSMgDu2tWUJpv8ryT8Ra7q8LhBXyxLOXPTT1cn0/vEfFWqWz+aKAA== ringcentral-client@^1.0.0-beta.2: version "1.0.0-beta.2" @@ -14244,7 +14274,7 @@ svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" -symbol-observable@^1.2.0: +symbol-observable@1.2.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== @@ -15573,12 +15603,7 @@ ws@^6.2.1: dependencies: async-limiter "~1.0.0" -ws@^7.2.3: - version "7.4.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.0.tgz#a5dd76a24197940d4a8bb9e0e152bb4503764da7" - integrity sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ== - -ws@^7.4.2: +ws@^7.2.3, ws@^7.4.2: version "7.4.2" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.2.tgz#782100048e54eb36fe9843363ab1c68672b261dd" integrity sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==