Skip to content

Commit

Permalink
feat(calendar): add CalDavClient wrapper for getting personal calenda…
Browse files Browse the repository at this point in the history
…rs and default calendar

Signed-off-by: Maksim Sukharev <[email protected]>
  • Loading branch information
Antreesy committed Jan 7, 2025
1 parent f6ed335 commit 27f6859
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 1 deletion.
103 changes: 103 additions & 0 deletions src/services/CalDavClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { getRequestToken } from '@nextcloud/auth'
import DavClient from '@nextcloud/cdav-library'
import { generateRemoteUrl } from '@nextcloud/router'

import type {
DavCalendar,
DavCalendarHome,
DavPrincipal,
} from '../types/index.ts'

/**
* Copied from https://github.com/nextcloud/calendar/blob/main/src/services/caldavService.js
* Modified for TS usage
*/
const clients: Record<string, DavClient> = {}

const getClientKey = (headers: object) => JSON.stringify(headers)

const getClient = (headers: object = {}) => {
const clientKey = getClientKey(headers)
if (clients[clientKey]) {
return clients[clientKey]
}

clients[clientKey] = new DavClient({
rootUrl: generateRemoteUrl('dav'),
}, () => {
const mergedHeaders: Record<string, string> = {
'X-Requested-With': 'XMLHttpRequest',
requesttoken: getRequestToken() as string,
'X-NC-CalDAV-Webcal-Caching': 'On',
...headers,
}
const xhr = new XMLHttpRequest()
const oldOpen = xhr.open

// override open() method to add headers
xhr.open = function() {
// @ts-expect-error: Vue: Argument of type IArguments is not assignable to parameter of type
// eslint-disable-next-line prefer-rest-params
const result = oldOpen.apply(this, arguments)
for (const name in mergedHeaders) {
xhr.setRequestHeader(name, mergedHeaders[name])
}

return result
}

// @ts-expect-error: Vue: Cannot find name OC
OC.registerXHRForErrorProcessing(xhr) // eslint-disable-line no-undef
return xhr
})

return clients[clientKey]
}

/**
* Initializes the client for use in the user-view
* If already connected, returns existing (see upstream)
*/
const initializeCalDavClient = async () => {
await getClient().connect({ enableCalDAV: true })
}

/**
* Returns the current User Principal
*/
const getDavCurrentUserPrincipal = (): DavPrincipal => {
return getClient().currentUserPrincipal
}

/**
* Returns calendar home
* @param headers optional request headers
*/
const getDavCalendarHome = (headers?: object): DavCalendarHome => getClient(headers).calendarHomes[0]

/**
* Get personal calendars for a user
*/
const getPersonalCalendars = async function(): Promise<DavCalendar[]> {
return getDavCalendarHome().findAllCalendars()
}

const convertUrlToUri = (url: string): string => {
return url.replace(/\/$/gi, '').split('/').pop() || url
}

const getDefaultCalendarUri = () => {
return convertUrlToUri(getDavCurrentUserPrincipal().scheduleDefaultCalendarUrl)
}

export {
initializeCalDavClient,
getPersonalCalendars,
getDefaultCalendarUri,
convertUrlToUri,
}
40 changes: 39 additions & 1 deletion src/stores/groupware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,49 @@ import Vue from 'vue'
import type { AxiosError } from '@nextcloud/axios'
import { generateUrl, getBaseUrl } from '@nextcloud/router'

import {
initializeCalDavClient,
getPersonalCalendars,
getDefaultCalendarUri,
convertUrlToUri,
} from '../services/CalDavClient.ts'
import {
getUpcomingEvents,
getUserAbsence,
} from '../services/groupwareService.ts'
import type {
DavCalendar,
OutOfOfficeResult,
UpcomingEvent,
} from '../types/index.ts'

type State = {
absence: Record<string, OutOfOfficeResult>
calendars: Record<string, DavCalendar & { uri: string }>,
defaultCalendarUri: string | null,
upcomingEvents: Record<string, UpcomingEvent[]>
}

export const useGroupwareStore = defineStore('groupware', {
state: (): State => ({
absence: {},
calendars: {},
defaultCalendarUri: null,
upcomingEvents: {},
}),

getters: {
getAllEvents: (state) => (token: string) => {
return state.upcomingEvents[token]
return state.upcomingEvents[token] ?? []
},
getNextEvent: (state) => (token: string) => {
return state.upcomingEvents[token]?.[0]
},
writeableCalendars: (state) => {
return Object.values(state.calendars).filter(calendar => {
return calendar.isWriteable() && calendar.components.includes('VEVENT')
})
},
},

actions: {
Expand Down Expand Up @@ -73,6 +89,28 @@ export const useGroupwareStore = defineStore('groupware', {
}
},

async getDefaultCalendarUri() {
try {
await initializeCalDavClient()
Vue.set(this, 'defaultCalendarUri', getDefaultCalendarUri())
} catch (error) {
console.error(error)
}
},

async getPersonalCalendars() {
try {
await initializeCalDavClient()
const calendars = await getPersonalCalendars()
calendars.forEach(calendar => {
const calendarWithUri = Object.assign(calendar, { uri: convertUrlToUri(calendar.url) })
Vue.set(this.calendars, calendarWithUri.uri, calendarWithUri)
})
} catch (error) {
console.error(error)
}
},

/**
* Drop an absence status from the store
* @param token The conversation token
Expand Down
37 changes: 37 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,43 @@ export type TaskProcessingResponse = ApiResponseUnwrapped<{
}>

// Groupware
export type DavPrincipal = {
calendarHomes: string[],
calendarUserType: string,
displayname: string,
email: string,
language: string,
principalScheme: string,
principalUrl: string,
scheduleDefaultCalendarUrl: string,
scheduleInbox: string,
scheduleOutbox: string,
url: string,
userId: string,
[key: string]: unknown,
}
export type DavCalendar = {
displayname: string,
color?: string,
components: string[],
allowedSharingModes: string[],
currentUserPrivilegeSet: string[],
enabled?: boolean,
order: number,
owner: string,
resourcetype: string[],
timezone?: string,
transparency: string,
url: string,
[key: string]: unknown,
isWriteable: () => boolean,
}
export type DavCalendarHome = {
displayname: string,
url: string,
findAllCalendars: () => Promise<DavCalendar[]>,
}

// Upcoming events response
// From https://github.com/nextcloud/server/blob/master/apps/dav/lib/CalDAV/UpcomingEvent.php
export type UpcomingEvent = {
Expand Down

0 comments on commit 27f6859

Please sign in to comment.