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.js 8.3KB

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