Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

middleware.js 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. // @flow
  2. import RNCalendarEvents from 'react-native-calendar-events';
  3. import { APP_WILL_MOUNT } from '../app';
  4. import { ADD_KNOWN_DOMAINS } from '../base/known-domains';
  5. import { MiddlewareRegistry } from '../base/redux';
  6. import { APP_LINK_SCHEME, parseURIString } from '../base/util';
  7. import { APP_STATE_CHANGED } from '../mobile/background';
  8. import { setCalendarAuthorization, setCalendarEvents } from './actions';
  9. import { REFRESH_CALENDAR } from './actionTypes';
  10. import { CALENDAR_ENABLED } from './constants';
  11. const logger = require('jitsi-meet-logger').getLogger(__filename);
  12. /**
  13. * The number of days to fetch.
  14. */
  15. const FETCH_END_DAYS = 10;
  16. /**
  17. * The number of days to go back when fetching.
  18. */
  19. const FETCH_START_DAYS = -1;
  20. /**
  21. * The max number of events to fetch from the calendar.
  22. */
  23. const MAX_LIST_LENGTH = 10;
  24. CALENDAR_ENABLED
  25. && MiddlewareRegistry.register(store => next => action => {
  26. const result = next(action);
  27. switch (action.type) {
  28. case ADD_KNOWN_DOMAINS:
  29. case APP_WILL_MOUNT:
  30. _fetchCalendarEntries(store, false, false);
  31. break;
  32. case APP_STATE_CHANGED:
  33. _maybeClearAccessStatus(store, action);
  34. break;
  35. case REFRESH_CALENDAR:
  36. _fetchCalendarEntries(store, true, action.forcePermission);
  37. break;
  38. }
  39. return result;
  40. });
  41. /**
  42. * Ensures calendar access if possible and resolves the promise if it's granted.
  43. *
  44. * @param {boolean} promptForPermission - Flag to tell the app if it should
  45. * prompt for a calendar permission if it wasn't granted yet.
  46. * @param {Function} dispatch - The Redux dispatch function.
  47. * @private
  48. * @returns {Promise}
  49. */
  50. function _ensureCalendarAccess(promptForPermission, dispatch) {
  51. return new Promise((resolve, reject) => {
  52. RNCalendarEvents.authorizationStatus()
  53. .then(status => {
  54. if (status === 'authorized') {
  55. resolve(true);
  56. } else if (promptForPermission) {
  57. RNCalendarEvents.authorizeEventStore()
  58. .then(result => {
  59. dispatch(setCalendarAuthorization(result));
  60. resolve(result === 'authorized');
  61. })
  62. .catch(reject);
  63. } else {
  64. resolve(false);
  65. }
  66. })
  67. .catch(reject);
  68. });
  69. }
  70. /**
  71. * Reads the user's calendar and updates the stored entries if need be.
  72. *
  73. * @param {Object} store - The redux store.
  74. * @param {boolean} maybePromptForPermission - Flag to tell the app if it should
  75. * prompt for a calendar permission if it wasn't granted yet.
  76. * @param {boolean|undefined} forcePermission - Whether to force to re-ask for
  77. * the permission or not.
  78. * @private
  79. * @returns {void}
  80. */
  81. function _fetchCalendarEntries(
  82. store,
  83. maybePromptForPermission,
  84. forcePermission) {
  85. const { dispatch, getState } = store;
  86. const promptForPermission
  87. = (maybePromptForPermission
  88. && !getState()['features/calendar-sync'].authorization)
  89. || forcePermission;
  90. _ensureCalendarAccess(promptForPermission, dispatch)
  91. .then(accessGranted => {
  92. if (accessGranted) {
  93. const startDate = new Date();
  94. const endDate = new Date();
  95. startDate.setDate(startDate.getDate() + FETCH_START_DAYS);
  96. endDate.setDate(endDate.getDate() + FETCH_END_DAYS);
  97. RNCalendarEvents.fetchAllEvents(
  98. startDate.getTime(),
  99. endDate.getTime(),
  100. [])
  101. .then(_updateCalendarEntries.bind(store))
  102. .catch(error =>
  103. logger.error('Error fetching calendar.', error));
  104. } else {
  105. logger.warn('Calendar access not granted.');
  106. }
  107. })
  108. .catch(reason => {
  109. logger.error('Error accessing calendar.', reason);
  110. });
  111. }
  112. /**
  113. * Retrieves a Jitsi Meet URL from an event if present.
  114. *
  115. * @param {Object} event - The event to parse.
  116. * @param {Array<string>} knownDomains - The known domain names.
  117. * @private
  118. * @returns {string}
  119. */
  120. function _getURLFromEvent(event, knownDomains) {
  121. const linkTerminatorPattern = '[^\\s<>$]';
  122. const urlRegExp
  123. = new RegExp(
  124. `http(s)?://(${knownDomains.join('|')})/${linkTerminatorPattern}+`,
  125. 'gi');
  126. const schemeRegExp
  127. = new RegExp(`${APP_LINK_SCHEME}${linkTerminatorPattern}+`, 'gi');
  128. const fieldsToSearch = [
  129. event.title,
  130. event.url,
  131. event.location,
  132. event.notes,
  133. event.description
  134. ];
  135. for (const field of fieldsToSearch) {
  136. if (typeof field === 'string') {
  137. const matches = urlRegExp.exec(field) || schemeRegExp.exec(field);
  138. if (matches) {
  139. const url = parseURIString(matches[0]);
  140. if (url) {
  141. return url.toString();
  142. }
  143. }
  144. }
  145. }
  146. return null;
  147. }
  148. /**
  149. * Clears the calendar access status when the app comes back from the
  150. * background. This is needed as some users may never quit the app, but puts it
  151. * into the background and we need to try to request for a permission as often
  152. * as possible, but not annoyingly often.
  153. *
  154. * @param {Object} store - The redux store.
  155. * @param {Object} action - The Redux action.
  156. * @private
  157. * @returns {void}
  158. */
  159. function _maybeClearAccessStatus(store, { appState }) {
  160. appState === 'background'
  161. && store.dispatch(setCalendarAuthorization(undefined));
  162. }
  163. /**
  164. * Updates the calendar entries in Redux when new list is received.
  165. *
  166. * @param {Object} event - An event returned from the native calendar.
  167. * @param {Array<string>} knownDomains - The known domain list.
  168. * @private
  169. * @returns {CalendarEntry}
  170. */
  171. function _parseCalendarEntry(event, knownDomains) {
  172. if (event) {
  173. const url = _getURLFromEvent(event, knownDomains);
  174. if (url) {
  175. const startDate = Date.parse(event.startDate);
  176. const endDate = Date.parse(event.endDate);
  177. if (isNaN(startDate) || isNaN(endDate)) {
  178. logger.warn(
  179. 'Skipping invalid calendar event',
  180. event.title,
  181. event.startDate,
  182. event.endDate
  183. );
  184. } else {
  185. return {
  186. endDate,
  187. id: event.id,
  188. startDate,
  189. title: event.title,
  190. url
  191. };
  192. }
  193. }
  194. }
  195. return null;
  196. }
  197. /**
  198. * Updates the calendar entries in redux when new list is received.
  199. *
  200. * XXX The function's {@code this} is the redux store.
  201. *
  202. * @param {Array<CalendarEntry>} events - The new event list.
  203. * @private
  204. * @returns {void}
  205. */
  206. function _updateCalendarEntries(events) {
  207. if (events && events.length) {
  208. // eslint-disable-next-line no-invalid-this
  209. const { dispatch, getState } = this;
  210. const knownDomains = getState()['features/base/known-domains'];
  211. const eventList = [];
  212. const now = Date.now();
  213. for (const event of events) {
  214. const calendarEntry = _parseCalendarEntry(event, knownDomains);
  215. calendarEntry
  216. && calendarEntry.endDate > now
  217. && eventList.push(calendarEntry);
  218. }
  219. dispatch(
  220. setCalendarEvents(
  221. eventList
  222. .sort((a, b) => a.startDate - b.startDate)
  223. .slice(0, MAX_LIST_LENGTH)));
  224. }
  225. }