Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

middleware.js 8.6KB

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