Skip to content

Commit

Permalink
CalDav Service: Add Synology, Google Calendar + Fix data event parsing (
Browse files Browse the repository at this point in the history
GladysAssistant#754)

* Fix data event parsing & add host

* Improve code coverage

* Improve code coverage

Co-authored-by: Pierre-Gilles Leymarie <pierregilles.leymarie@gmail.com>
  • Loading branch information
2 people authored and NickDub committed Aug 7, 2020
1 parent 907700d commit f87d1f9
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 32 deletions.
18 changes: 18 additions & 0 deletions front/src/config/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,24 @@
"password": "Password",
"passwordInfo": "<a href=https://support.apple.com/en-us/HT204397 target=_blank>Generate an app-specific password</a> and use this app-specific password instead of your account password."
},
"google": {
"name": "Google Calendar (deprecated)",
"url": "Google Calendar URL",
"urlInfo": "Let default URL, it will be set automatically.",
"username": "Email",
"usernameInfo": "Enter your Google email adress.",
"password": "Password",
"passwordInfo": "<a href=https://support.google.com/accounts/answer/185833?hl=en target=_blank>Generate an app-specific password</a> and use this app-specific password instead of your account password."
},
"synology": {
"name": "Synology Calendar",
"url": "Synology CalDAV URL",
"urlInfo": "In calendar application, click on 'CalDAV Account' and copy 'macOS/iOS' URL.",
"username": "Synology username",
"usernameInfo": "Enter your username.",
"password": "Password",
"passwordInfo": "Enter your password."
},
"other": {
"name": "Other",
"url": "CalDAV URL",
Expand Down
18 changes: 18 additions & 0 deletions front/src/config/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,24 @@
"password": "Mot de passe",
"passwordInfo": "<a href=https://support.apple.com/fr-fr/HT204397 target=_blank>Générez un mot de passe application</a> et utilisez le à la place du mot de passe de votre compte."
},
"google": {
"name": "Google Agenda (déprécié)",
"url": "URL Google Agenda",
"urlInfo": "Laissez l'URL par défaut, il sera paramétré automatiquement.",
"username": "Email",
"usernameInfo": "Entrez l'adresse email de votre compte Google.",
"password": "Mot de passe",
"passwordInfo": "<a href=https://support.google.com/accounts/answer/185833?hl=fr target=_blank>Générez un mot de passe application</a> et utilisez le à la place du mot de passe de votre compte."
},
"synology": {
"name": "Calendrier Synology",
"url": "URL CalDAV Synology",
"urlInfo": "Dans l'application Calendrier, cliquez sur 'Compte CalDAV' et copiez l'URL 'macOS/iOS'.",
"username": "Nom d'utilisateur Synology",
"usernameInfo": "Entrez votre nom d'utilisateur.",
"password": "Mot de passe",
"passwordInfo": "Entrez votre mot de passe."
},
"other": {
"name": "Autre",
"url": "URL CalDAV",
Expand Down
4 changes: 4 additions & 0 deletions front/src/routes/integration/all/caldav/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ const actions = store => ({
store.setState({
caldavUrl: 'https://caldav.icloud.com'
});
} else if (e.target.value === 'google') {
store.setState({
caldavUrl: 'https://www.google.com/calendar/dav'
});
}
},
updateCaldavUrl(state, e) {
Expand Down
47 changes: 27 additions & 20 deletions server/services/caldav/lib/calendar/calendar.requests.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
const url = require('url');
const logger = require('../../../../utils/logger');

/**
* @description Search elements as getElementsByTagName with regex instead of string.
* @param {Object} container - Element where execute research.
* @param {RegExp} regex - Regex to search.
* @returns {Array} Elements that match with regex.
* @example
* getElementsByTagRegex(document, /(cal:)?calendar-data/)
*/
function getElementsByTagRegex(container, regex) {
return Array.from(container.getElementsByTagName('*')).filter((elem) => regex.test(elem.tagName));
}

/**
* @description Get calendars from caldav server.
* @param {Object} xhr - Request with dav credentials.
Expand Down Expand Up @@ -105,36 +117,31 @@ async function requestEventsData(xhr, calendarUrl, eventsToUpdate, calDavHost) {
</c:comp-filter>
</c:filter>
</c:calendar-multiget>`,
transformRequest: (request) => request.setRequestHeader('Content-Type', 'application/xml;charset=utf-8'),
});

const eventsData = await xhr.send(req, calendarUrl);

const tags = {};
switch (calDavHost) {
case 'apple':
tags.response = 'response';
tags.calendarData = 'calendar-data';
tags.href = 'href';
break;
default:
tags.response = 'd:response';
tags.calendarData = 'cal:calendar-data';
tags.href = 'd:href';
break;
}
// Extract data from XML response
const xmlEvents = new this.xmlDom.DOMParser().parseFromString(eventsData.request.responseText);
const jsonEvents = Array.from(xmlEvents.getElementsByTagName(tags.response)).map((xmlEvent) => {
const event = this.ical.parseICS(xmlEvent.getElementsByTagName(tags.calendarData)[0].childNodes[0].data);
return {
href: xmlEvent.getElementsByTagName(tags.href)[0].childNodes[0].data,
...event[Object.keys(event)[0]],
};
const jsonEvents = getElementsByTagRegex(xmlEvents, /^[a-zA-Z]*:?response$/).map((xmlEvent) => {
const event = this.ical.parseICS(
getElementsByTagRegex(xmlEvent, /^[a-zA-Z]*:?calendar-data$/)[0].childNodes[0].data,
);
for (let j = 0; j < Object.keys(event).length; j += 1) {
if (event[Object.keys(event)[j]].type === 'VEVENT') {
return {
href: getElementsByTagRegex(xmlEvent, /^[a-zA-Z]*:?href$/)[0].childNodes[0].data,
...event[Object.keys(event)[j]],
};
}
}
return null;
});

// Filter only event objects
return jsonEvents.filter((jsonEvent) => {
return jsonEvent.type === 'VEVENT';
return jsonEvent !== null;
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ async function syncUserCalendars(userId) {
return this.gladys.calendar.updateEvent(gladysEvents[0].selector, formatedEvent);
}),
);

logger.info(`CalDAV : ${savedEvents.length} events updated.`);
logger.info(`CalDAV : ${savedEvents.length} events updated for calendar ${calendarToUpdate.name}.`);
},
{ concurrency: 1 },
);
Expand Down
46 changes: 36 additions & 10 deletions server/test/services/caldav/lib/calendar/requests.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,33 @@ describe('CalDAV requests', () => {
Request: sinon.stub().returns({ requestDate: 'request3' }),
},
ical: {
parseICS: sinon.stub().returns({
data: {
type: 'VEVENT',
uid: '49193db9-f666-4947-8ce6-3357ce3b7166',
summary: 'Evenement 1',
start: new Date('2018-06-08'),
end: new Date('2018-06-09'),
},
}),
parseICS: sinon
.stub()
.onFirstCall()
.returns({
data: {
type: 'VEVENT',
uid: '49193db9-f666-4947-8ce6-3357ce3b7166',
summary: 'Evenement 1',
start: new Date('2018-06-08'),
end: new Date('2018-06-09'),
},
})
.onSecondCall()
.returns({}),
},
xmlDom: {
DOMParser: sinon.stub().returns({
parseFromString: sinon.stub().returns({
getElementsByTagName: sinon.stub().returns([
{
tagName: 'response',
getElementsByTagName: sinon
.stub()
.onFirstCall()
.returns([
{
tagName: 'calendar-data',
childNodes: [
{
data: `
Expand Down Expand Up @@ -82,7 +89,22 @@ describe('CalDAV requests', () => {
},
])
.onSecondCall()
.returns([{ childNodes: [{ data: 'https://caldav.host.com/home/personal/event-1.ics' }] }]),
.returns([
{ tagName: 'href', childNodes: [{ data: 'https://caldav.host.com/home/personal/event-1.ics' }] },
]),
},
{
tagName: 'response',
getElementsByTagName: sinon.stub().returns([
{
tagName: 'calendar-data',
childNodes: [
{
data: '',
},
],
},
]),
},
]),
}),
Expand Down Expand Up @@ -246,6 +268,7 @@ describe('CalDAV requests', () => {
const xhr = {
send: sinon.stub(),
};
const setRequestHeader = sinon.stub();
xhr.send.resolves({ request: { responseText: '<xml></xml>' } });
const eventsData = await requests.requestEventsData(
xhr,
Expand All @@ -267,6 +290,9 @@ describe('CalDAV requests', () => {
'other',
);

requests.dav.Request.args[0][0].transformRequest({ setRequestHeader });
expect(setRequestHeader.args[0]).to.eql(['Content-Type', 'application/xml;charset=utf-8']);

expect(eventsData).to.eql([
{
href: 'https://caldav.host.com/home/personal/event-1.ics',
Expand Down

0 comments on commit f87d1f9

Please sign in to comment.