|
|
|
@ -2,13 +2,18 @@ |
|
|
|
|
import Logger from 'jitsi-meet-logger'; |
|
|
|
|
import RNCalendarEvents from 'react-native-calendar-events'; |
|
|
|
|
|
|
|
|
|
import { APP_WILL_MOUNT } from '../app'; |
|
|
|
|
import { SET_ROOM } from '../base/conference'; |
|
|
|
|
import { MiddlewareRegistry } from '../base/redux'; |
|
|
|
|
import { APP_LINK_SCHEME, parseURIString } from '../base/util'; |
|
|
|
|
import { APP_STATE_CHANGED } from '../mobile/background'; |
|
|
|
|
|
|
|
|
|
import { APP_WILL_MOUNT } from '../app'; |
|
|
|
|
|
|
|
|
|
import { maybeAddNewKnownDomain, updateCalendarEntryList } from './actions'; |
|
|
|
|
import { |
|
|
|
|
maybeAddNewKnownDomain, |
|
|
|
|
updateCalendarAccessStatus, |
|
|
|
|
updateCalendarEntryList |
|
|
|
|
} from './actions'; |
|
|
|
|
import { REFRESH_CALENDAR_ENTRY_LIST } from './actionTypes'; |
|
|
|
|
|
|
|
|
|
const FETCH_END_DAYS = 10; |
|
|
|
@ -20,12 +25,15 @@ MiddlewareRegistry.register(store => next => action => { |
|
|
|
|
const result = next(action); |
|
|
|
|
|
|
|
|
|
switch (action.type) { |
|
|
|
|
case APP_STATE_CHANGED: |
|
|
|
|
_maybeClearAccessStatus(store, action); |
|
|
|
|
break; |
|
|
|
|
case APP_WILL_MOUNT: |
|
|
|
|
_ensureDefaultServer(store); |
|
|
|
|
_fetchCalendarEntries(store, false); |
|
|
|
|
_fetchCalendarEntries(store, false, false); |
|
|
|
|
break; |
|
|
|
|
case REFRESH_CALENDAR_ENTRY_LIST: |
|
|
|
|
_fetchCalendarEntries(store, true); |
|
|
|
|
_fetchCalendarEntries(store, true, action.forcePermission); |
|
|
|
|
break; |
|
|
|
|
case SET_ROOM: |
|
|
|
|
_parseAndAddDomain(store); |
|
|
|
@ -34,34 +42,53 @@ MiddlewareRegistry.register(store => next => action => { |
|
|
|
|
return result; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Clears the calendar access status when the app comes back from |
|
|
|
|
* the background. This is needed as some users may never quit the |
|
|
|
|
* app, but puts it into the background and we need to try to request |
|
|
|
|
* for a permission as often as possible, but not annoyingly often. |
|
|
|
|
* |
|
|
|
|
* @private |
|
|
|
|
* @param {Object} store - The redux store. |
|
|
|
|
* @param {Object} action - The Redux action. |
|
|
|
|
* @returns {void} |
|
|
|
|
*/ |
|
|
|
|
function _maybeClearAccessStatus(store, action) { |
|
|
|
|
const { appState } = action; |
|
|
|
|
|
|
|
|
|
if (appState === 'background') { |
|
|
|
|
const { dispatch } = store; |
|
|
|
|
|
|
|
|
|
dispatch(updateCalendarAccessStatus(undefined)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Ensures calendar access if possible and resolves the promise if it's granted. |
|
|
|
|
* |
|
|
|
|
* @private |
|
|
|
|
* @param {boolean} promptForPermission - Flag to tell the app if it should |
|
|
|
|
* prompt for a calendar permission if it wasn't granted yet. |
|
|
|
|
* @param {Function} dispatch - The Redux dispatch function. |
|
|
|
|
* @returns {Promise} |
|
|
|
|
*/ |
|
|
|
|
function _ensureCalendarAccess(promptForPermission) { |
|
|
|
|
function _ensureCalendarAccess(promptForPermission, dispatch) { |
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
|
RNCalendarEvents.authorizationStatus() |
|
|
|
|
.then(status => { |
|
|
|
|
if (status === 'authorized') { |
|
|
|
|
resolve(); |
|
|
|
|
resolve(true); |
|
|
|
|
} else if (promptForPermission) { |
|
|
|
|
RNCalendarEvents.authorizeEventStore() |
|
|
|
|
.then(result => { |
|
|
|
|
if (result === 'authorized') { |
|
|
|
|
resolve(); |
|
|
|
|
} else { |
|
|
|
|
reject(result); |
|
|
|
|
} |
|
|
|
|
dispatch(updateCalendarAccessStatus(result)); |
|
|
|
|
resolve(result === 'authorized'); |
|
|
|
|
}) |
|
|
|
|
.catch(error => { |
|
|
|
|
reject(error); |
|
|
|
|
}); |
|
|
|
|
} else { |
|
|
|
|
reject(status); |
|
|
|
|
resolve(false); |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
.catch(error => { |
|
|
|
@ -91,64 +118,49 @@ function _ensureDefaultServer(store) { |
|
|
|
|
* |
|
|
|
|
* @private |
|
|
|
|
* @param {Object} store - The redux store. |
|
|
|
|
* @param {boolean} promptForPermission - Flag to tell the app if it should |
|
|
|
|
* @param {boolean} maybePromptForPermission - Flag to tell the app if it should |
|
|
|
|
* prompt for a calendar permission if it wasn't granted yet. |
|
|
|
|
* @param {boolean|undefined} forcePermission - Whether to force to re-ask |
|
|
|
|
* for the permission or not. |
|
|
|
|
* @returns {void} |
|
|
|
|
*/ |
|
|
|
|
function _fetchCalendarEntries(store, promptForPermission) { |
|
|
|
|
_ensureCalendarAccess(promptForPermission) |
|
|
|
|
.then(() => { |
|
|
|
|
const startDate = new Date(); |
|
|
|
|
const endDate = new Date(); |
|
|
|
|
|
|
|
|
|
startDate.setDate(startDate.getDate() + FETCH_START_DAYS); |
|
|
|
|
endDate.setDate(endDate.getDate() + FETCH_END_DAYS); |
|
|
|
|
|
|
|
|
|
RNCalendarEvents.fetchAllEvents( |
|
|
|
|
startDate.getTime(), |
|
|
|
|
endDate.getTime(), |
|
|
|
|
[] |
|
|
|
|
) |
|
|
|
|
.then(events => { |
|
|
|
|
const { knownDomains } = store.getState()['features/calendar-sync']; |
|
|
|
|
const eventList = []; |
|
|
|
|
|
|
|
|
|
if (events && events.length) { |
|
|
|
|
for (const event of events) { |
|
|
|
|
const jitsiURL = _getURLFromEvent(event, knownDomains); |
|
|
|
|
const now = Date.now(); |
|
|
|
|
|
|
|
|
|
if (jitsiURL) { |
|
|
|
|
const eventStartDate = Date.parse(event.startDate); |
|
|
|
|
const eventEndDate = Date.parse(event.endDate); |
|
|
|
|
|
|
|
|
|
if (isNaN(eventStartDate) || isNaN(eventEndDate)) { |
|
|
|
|
logger.warn( |
|
|
|
|
'Skipping calendar event due to invalid dates', |
|
|
|
|
event.title, |
|
|
|
|
event.startDate, |
|
|
|
|
event.endDate |
|
|
|
|
); |
|
|
|
|
} else if (eventEndDate > now) { |
|
|
|
|
eventList.push({ |
|
|
|
|
endDate: eventEndDate, |
|
|
|
|
id: event.id, |
|
|
|
|
startDate: eventStartDate, |
|
|
|
|
title: event.title, |
|
|
|
|
url: jitsiURL |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
function _fetchCalendarEntries( |
|
|
|
|
store, |
|
|
|
|
maybePromptForPermission, |
|
|
|
|
forcePermission |
|
|
|
|
) { |
|
|
|
|
const { dispatch } = store; |
|
|
|
|
const state = store.getState()['features/calendar-sync']; |
|
|
|
|
const { calendarAccessStatus } = state; |
|
|
|
|
const promptForPermission |
|
|
|
|
= (maybePromptForPermission && !calendarAccessStatus) |
|
|
|
|
|| forcePermission; |
|
|
|
|
|
|
|
|
|
_ensureCalendarAccess(promptForPermission, dispatch) |
|
|
|
|
.then(accessGranted => { |
|
|
|
|
if (accessGranted) { |
|
|
|
|
const startDate = new Date(); |
|
|
|
|
const endDate = new Date(); |
|
|
|
|
|
|
|
|
|
startDate.setDate(startDate.getDate() + FETCH_START_DAYS); |
|
|
|
|
endDate.setDate(endDate.getDate() + FETCH_END_DAYS); |
|
|
|
|
|
|
|
|
|
store.dispatch(updateCalendarEntryList(eventList.sort((a, b) => |
|
|
|
|
a.startDate - b.startDate |
|
|
|
|
).slice(0, MAX_LIST_LENGTH))); |
|
|
|
|
}) |
|
|
|
|
.catch(error => { |
|
|
|
|
logger.error('Error fetching calendar.', error); |
|
|
|
|
}); |
|
|
|
|
RNCalendarEvents.fetchAllEvents( |
|
|
|
|
startDate.getTime(), |
|
|
|
|
endDate.getTime(), |
|
|
|
|
[] |
|
|
|
|
) |
|
|
|
|
.then(events => { |
|
|
|
|
const { knownDomains } = state; |
|
|
|
|
|
|
|
|
|
_updateCalendarEntries(events, knownDomains, dispatch); |
|
|
|
|
}) |
|
|
|
|
.catch(error => { |
|
|
|
|
logger.error('Error fetching calendar.', error); |
|
|
|
|
}); |
|
|
|
|
} else { |
|
|
|
|
logger.warn('Calendar access not granted.'); |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
.catch(reason => { |
|
|
|
|
logger.error('Error accessing calendar.', reason); |
|
|
|
@ -209,3 +221,70 @@ function _parseAndAddDomain(store) { |
|
|
|
|
|
|
|
|
|
store.dispatch(maybeAddNewKnownDomain(locationURL.host)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Updates the calendar entries in Redux when new list is received. |
|
|
|
|
* |
|
|
|
|
* @private |
|
|
|
|
* @param {Object} event - An event returned from the native calendar. |
|
|
|
|
* @param {Array<string>} knownDomains - The known domain list. |
|
|
|
|
* @returns {CalendarEntry} |
|
|
|
|
*/ |
|
|
|
|
function _parseCalendarEntry(event, knownDomains) { |
|
|
|
|
if (event) { |
|
|
|
|
const jitsiURL = _getURLFromEvent(event, knownDomains); |
|
|
|
|
|
|
|
|
|
if (jitsiURL) { |
|
|
|
|
const eventStartDate = Date.parse(event.startDate); |
|
|
|
|
const eventEndDate = Date.parse(event.endDate); |
|
|
|
|
|
|
|
|
|
if (isNaN(eventStartDate) || isNaN(eventEndDate)) { |
|
|
|
|
logger.warn( |
|
|
|
|
'Skipping invalid calendar event', |
|
|
|
|
event.title, |
|
|
|
|
event.startDate, |
|
|
|
|
event.endDate |
|
|
|
|
); |
|
|
|
|
} else { |
|
|
|
|
return { |
|
|
|
|
endDate: eventEndDate, |
|
|
|
|
id: event.id, |
|
|
|
|
startDate: eventStartDate, |
|
|
|
|
title: event.title, |
|
|
|
|
url: jitsiURL |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Updates the calendar entries in Redux when new list is received. |
|
|
|
|
* |
|
|
|
|
* @private |
|
|
|
|
* @param {Array<CalendarEntry>} events - The new event list. |
|
|
|
|
* @param {Array<string>} knownDomains - The known domain list. |
|
|
|
|
* @param {Function} dispatch - The Redux dispatch function. |
|
|
|
|
* @returns {void} |
|
|
|
|
*/ |
|
|
|
|
function _updateCalendarEntries(events, knownDomains, dispatch) { |
|
|
|
|
if (events && events.length) { |
|
|
|
|
const eventList = []; |
|
|
|
|
|
|
|
|
|
for (const event of events) { |
|
|
|
|
const calendarEntry |
|
|
|
|
= _parseCalendarEntry(event, knownDomains); |
|
|
|
|
const now = Date.now(); |
|
|
|
|
|
|
|
|
|
if (calendarEntry && calendarEntry.endDate > now) { |
|
|
|
|
eventList.push(calendarEntry); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
dispatch(updateCalendarEntryList(eventList.sort((a, b) => |
|
|
|
|
a.startDate - b.startDate |
|
|
|
|
).slice(0, MAX_LIST_LENGTH))); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|