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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. // @flow
  2. import Logger from 'jitsi-meet-logger';
  3. import RNCalendarEvents from 'react-native-calendar-events';
  4. import { SET_ROOM } from '../base/conference';
  5. import { MiddlewareRegistry } from '../base/redux';
  6. import { parseURIString } from '../base/util';
  7. import { APP_WILL_MOUNT } from '../app';
  8. import { maybeAddNewKnownDomain, updateCalendarEntryList } from './actions';
  9. const FETCH_END_DAYS = 10;
  10. const FETCH_START_DAYS = -1;
  11. const MAX_LIST_LENGTH = 10;
  12. const logger = Logger.getLogger(__filename);
  13. MiddlewareRegistry.register(store => next => action => {
  14. const result = next(action);
  15. switch (action.type) {
  16. case APP_WILL_MOUNT:
  17. _ensureDefaultServer(store);
  18. _fetchCalendarEntries(store);
  19. break;
  20. case SET_ROOM:
  21. _parseAndAddDomain(store);
  22. }
  23. return result;
  24. });
  25. /**
  26. * Ensures calendar access if possible and resolves the promise if it's granted.
  27. *
  28. * @private
  29. * @returns {Promise}
  30. */
  31. function _ensureCalendarAccess() {
  32. return new Promise((resolve, reject) => {
  33. RNCalendarEvents.authorizationStatus()
  34. .then(status => {
  35. if (status === 'authorized') {
  36. resolve();
  37. } else if (status === 'undetermined') {
  38. RNCalendarEvents.authorizeEventStore()
  39. .then(result => {
  40. if (result === 'authorized') {
  41. resolve();
  42. } else {
  43. reject(result);
  44. }
  45. })
  46. .catch(error => {
  47. reject(error);
  48. });
  49. } else {
  50. reject(status);
  51. }
  52. })
  53. .catch(error => {
  54. reject(error);
  55. });
  56. });
  57. }
  58. /**
  59. * Ensures presence of the default server in the known domains list.
  60. *
  61. * @private
  62. * @param {Object} store - The redux store.
  63. * @returns {Promise}
  64. */
  65. function _ensureDefaultServer(store) {
  66. const state = store.getState();
  67. const defaultURL = parseURIString(
  68. state['features/app'].app._getDefaultURL()
  69. );
  70. store.dispatch(maybeAddNewKnownDomain(defaultURL.host));
  71. }
  72. /**
  73. * Reads the user's calendar and updates the stored entries if need be.
  74. *
  75. * @private
  76. * @param {Object} store - The redux store.
  77. * @returns {void}
  78. */
  79. function _fetchCalendarEntries(store) {
  80. _ensureCalendarAccess()
  81. .then(() => {
  82. const startDate = new Date();
  83. const endDate = new Date();
  84. startDate.setDate(startDate.getDate() + FETCH_START_DAYS);
  85. endDate.setDate(endDate.getDate() + FETCH_END_DAYS);
  86. RNCalendarEvents.fetchAllEvents(
  87. startDate.getTime(),
  88. endDate.getTime(),
  89. []
  90. )
  91. .then(events => {
  92. const { knownDomains } = store.getState()['features/calendar-sync'];
  93. const eventList = [];
  94. if (events && events.length) {
  95. for (const event of events) {
  96. const jitsiURL = _getURLFromEvent(event, knownDomains);
  97. const now = Date.now();
  98. if (jitsiURL) {
  99. const eventStartDate = Date.parse(event.startDate);
  100. const eventEndDate = Date.parse(event.endDate);
  101. if (isNaN(eventStartDate) || isNaN(eventEndDate)) {
  102. logger.warn(
  103. 'Skipping calendar event due to invalid dates',
  104. event.title,
  105. event.startDate,
  106. event.endDate
  107. );
  108. } else if (eventEndDate > now) {
  109. eventList.push({
  110. endDate: eventEndDate,
  111. id: event.id,
  112. startDate: eventStartDate,
  113. title: event.title,
  114. url: jitsiURL
  115. });
  116. }
  117. }
  118. }
  119. }
  120. store.dispatch(updateCalendarEntryList(eventList.sort((a, b) =>
  121. a.startDate - b.startDate
  122. ).slice(0, MAX_LIST_LENGTH)));
  123. })
  124. .catch(error => {
  125. logger.error('Error fetching calendar.', error);
  126. });
  127. })
  128. .catch(reason => {
  129. logger.error('Error accessing calendar.', reason);
  130. });
  131. }
  132. /**
  133. * Retreives a jitsi URL from an event if present.
  134. *
  135. * @private
  136. * @param {Object} event - The event to parse.
  137. * @param {Array<string>} knownDomains - The known domain names.
  138. * @returns {string}
  139. *
  140. */
  141. function _getURLFromEvent(event, knownDomains) {
  142. const urlRegExp
  143. = new RegExp(`http(s)?://(${knownDomains.join('|')})/[^\\s<>$]+`, 'gi');
  144. const fieldsToSearch = [
  145. event.title,
  146. event.url,
  147. event.location,
  148. event.notes,
  149. event.description
  150. ];
  151. let matchArray;
  152. for (const field of fieldsToSearch) {
  153. if (typeof field === 'string') {
  154. if (
  155. (matchArray = urlRegExp.exec(field)) !== null
  156. ) {
  157. return matchArray[0];
  158. }
  159. }
  160. }
  161. return null;
  162. }
  163. /**
  164. * Retreives the domain name of a room upon join and stores it
  165. * in the known domain list, if not present yet.
  166. *
  167. * @private
  168. * @param {Object} store - The redux store.
  169. * @returns {Promise}
  170. */
  171. function _parseAndAddDomain(store) {
  172. const { locationURL } = store.getState()['features/base/connection'];
  173. store.dispatch(maybeAddNewKnownDomain(locationURL.host));
  174. }