You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

actions.web.ts 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. // @ts-expect-error
  2. import { generateRoomWithoutSeparator } from '@jitsi/js-utils/random';
  3. import { createCalendarConnectedEvent } from '../analytics/AnalyticsEvents';
  4. import { sendAnalytics } from '../analytics/functions';
  5. import { IStore } from '../app/types';
  6. // eslint-disable-next-line lines-around-comment
  7. // @ts-ignore
  8. import { loadGoogleAPI } from '../google-api';
  9. import {
  10. CLEAR_CALENDAR_INTEGRATION,
  11. SET_CALENDAR_AUTH_STATE,
  12. SET_CALENDAR_ERROR,
  13. SET_CALENDAR_INTEGRATION,
  14. SET_CALENDAR_PROFILE_EMAIL,
  15. SET_LOADING_CALENDAR_EVENTS
  16. } from './actionTypes';
  17. import { refreshCalendar, setCalendarEvents } from './actions.web';
  18. import { _getCalendarIntegration, isCalendarEnabled } from './functions.web';
  19. import logger from './logger';
  20. export * from './actions.any';
  21. /**
  22. * Sets the initial state of calendar integration by loading third party APIs
  23. * and filling out any data that needs to be fetched.
  24. *
  25. * @returns {Function}
  26. */
  27. export function bootstrapCalendarIntegration() {
  28. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  29. const state = getState();
  30. if (!isCalendarEnabled(state)) {
  31. return Promise.reject();
  32. }
  33. const {
  34. googleApiApplicationClientID
  35. } = state['features/base/config'];
  36. const {
  37. integrationReady,
  38. integrationType
  39. } = state['features/calendar-sync'];
  40. return Promise.resolve()
  41. .then(() => {
  42. if (googleApiApplicationClientID) {
  43. return dispatch(loadGoogleAPI());
  44. }
  45. })
  46. .then(() => {
  47. if (!integrationType || integrationReady) {
  48. return;
  49. }
  50. const integrationToLoad
  51. = _getCalendarIntegration(integrationType);
  52. if (!integrationToLoad) {
  53. dispatch(clearCalendarIntegration());
  54. return;
  55. }
  56. return dispatch(integrationToLoad._isSignedIn())
  57. .then((signedIn: boolean) => {
  58. if (signedIn) {
  59. dispatch(setIntegrationReady(integrationType));
  60. dispatch(updateProfile(integrationType));
  61. } else {
  62. dispatch(clearCalendarIntegration());
  63. }
  64. });
  65. });
  66. };
  67. }
  68. /**
  69. * Resets the state of calendar integration so stored events and selected
  70. * calendar type are cleared.
  71. *
  72. * @returns {{
  73. * type: CLEAR_CALENDAR_INTEGRATION
  74. * }}
  75. */
  76. export function clearCalendarIntegration() {
  77. return {
  78. type: CLEAR_CALENDAR_INTEGRATION
  79. };
  80. }
  81. /**
  82. * Asks confirmation from the user to add a Jitsi link to the calendar event.
  83. *
  84. * NOTE: Currently there is no confirmation prompted on web, so this is just
  85. * a relaying method to avoid flow problems.
  86. *
  87. * @param {string} eventId - The event id.
  88. * @param {string} calendarId - The calendar id.
  89. * @returns {Function}
  90. */
  91. export function openUpdateCalendarEventDialog(
  92. eventId: string, calendarId: string) {
  93. return updateCalendarEvent(eventId, calendarId);
  94. }
  95. /**
  96. * Sends an action to update the current calendar api auth state in redux.
  97. * This is used only for microsoft implementation to store it auth state.
  98. *
  99. * @param {number} newState - The new state.
  100. * @returns {{
  101. * type: SET_CALENDAR_AUTH_STATE,
  102. * msAuthState: Object
  103. * }}
  104. */
  105. export function setCalendarAPIAuthState(newState?: Object) {
  106. return {
  107. type: SET_CALENDAR_AUTH_STATE,
  108. msAuthState: newState
  109. };
  110. }
  111. /**
  112. * Sends an action to update the calendar error state in redux.
  113. *
  114. * @param {Object} error - An object with error details.
  115. * @returns {{
  116. * type: SET_CALENDAR_ERROR,
  117. * error: Object
  118. * }}
  119. */
  120. export function setCalendarError(error?: Object) {
  121. return {
  122. type: SET_CALENDAR_ERROR,
  123. error
  124. };
  125. }
  126. /**
  127. * Sends an action to update the current calendar profile email state in redux.
  128. *
  129. * @param {number} newEmail - The new email.
  130. * @returns {{
  131. * type: SET_CALENDAR_PROFILE_EMAIL,
  132. * email: string
  133. * }}
  134. */
  135. export function setCalendarProfileEmail(newEmail?: string) {
  136. return {
  137. type: SET_CALENDAR_PROFILE_EMAIL,
  138. email: newEmail
  139. };
  140. }
  141. /**
  142. * Sends an to denote a request in is flight to get calendar events.
  143. *
  144. * @param {boolean} isLoadingEvents - Whether or not calendar events are being
  145. * fetched.
  146. * @returns {{
  147. * type: SET_LOADING_CALENDAR_EVENTS,
  148. * isLoadingEvents: boolean
  149. * }}
  150. */
  151. export function setLoadingCalendarEvents(isLoadingEvents: boolean) {
  152. return {
  153. type: SET_LOADING_CALENDAR_EVENTS,
  154. isLoadingEvents
  155. };
  156. }
  157. /**
  158. * Sets the calendar integration type to be used by web and signals that the
  159. * integration is ready to be used.
  160. *
  161. * @param {string|undefined} integrationType - The calendar type.
  162. * @returns {{
  163. * type: SET_CALENDAR_INTEGRATION,
  164. * integrationReady: boolean,
  165. * integrationType: string
  166. * }}
  167. */
  168. export function setIntegrationReady(integrationType: string) {
  169. return {
  170. type: SET_CALENDAR_INTEGRATION,
  171. integrationReady: true,
  172. integrationType
  173. };
  174. }
  175. /**
  176. * Signals signing in to the specified calendar integration.
  177. *
  178. * @param {string} calendarType - The calendar integration which should be
  179. * signed into.
  180. * @returns {Function}
  181. */
  182. export function signIn(calendarType: string) {
  183. return (dispatch: IStore['dispatch']) => {
  184. const integration = _getCalendarIntegration(calendarType);
  185. if (!integration) {
  186. return Promise.reject('No supported integration found');
  187. }
  188. return dispatch(integration.load())
  189. .then(() => dispatch(integration.signIn()))
  190. .then(() => dispatch(setIntegrationReady(calendarType)))
  191. .then(() => dispatch(updateProfile(calendarType)))
  192. .then(() => dispatch(refreshCalendar()))
  193. .then(() => sendAnalytics(createCalendarConnectedEvent()))
  194. .catch((error: any) => {
  195. logger.error(
  196. 'Error occurred while signing into calendar integration',
  197. error);
  198. return Promise.reject(error);
  199. });
  200. };
  201. }
  202. /**
  203. * Updates calendar event by generating new invite URL and editing the event
  204. * adding some descriptive text and location.
  205. *
  206. * @param {string} id - The event id.
  207. * @param {string} calendarId - The id of the calendar to use.
  208. * @returns {Function}
  209. */
  210. export function updateCalendarEvent(id: string, calendarId: string) {
  211. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  212. const { integrationType = '' } = getState()['features/calendar-sync'];
  213. const integration = _getCalendarIntegration(integrationType);
  214. if (!integration) {
  215. return Promise.reject('No integration found');
  216. }
  217. const { locationURL } = getState()['features/base/connection'];
  218. const newRoomName = generateRoomWithoutSeparator();
  219. let href = locationURL?.href ?? '';
  220. href.endsWith('/') || (href += '/');
  221. const roomURL = `${href}${newRoomName}`;
  222. return dispatch(integration.updateCalendarEvent(
  223. id, calendarId, roomURL))
  224. .then(() => {
  225. // make a copy of the array
  226. const events
  227. = getState()['features/calendar-sync'].events.slice(0);
  228. const eventIx = events.findIndex(
  229. e => e.id === id && e.calendarId === calendarId);
  230. // clone the event we will modify
  231. const newEvent = Object.assign({}, events[eventIx]);
  232. newEvent.url = roomURL;
  233. events[eventIx] = newEvent;
  234. return dispatch(setCalendarEvents(events));
  235. });
  236. };
  237. }
  238. /**
  239. * Signals to get current profile data linked to the current calendar
  240. * integration that is in use.
  241. *
  242. * @param {string} calendarType - The calendar integration to which the profile
  243. * should be updated.
  244. * @returns {Function}
  245. */
  246. export function updateProfile(calendarType: string) {
  247. return (dispatch: IStore['dispatch']) => {
  248. const integration = _getCalendarIntegration(calendarType);
  249. if (!integration) {
  250. return Promise.reject('No integration found');
  251. }
  252. return dispatch(integration.getCurrentEmail())
  253. .then((email: string) => {
  254. dispatch(setCalendarProfileEmail(email));
  255. });
  256. };
  257. }