summaryrefslogtreecommitdiffstats
path: root/js/dav/lib/request.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/dav/lib/request.js')
-rw-r--r--js/dav/lib/request.js233
1 files changed, 233 insertions, 0 deletions
diff --git a/js/dav/lib/request.js b/js/dav/lib/request.js
new file mode 100644
index 00000000..97de3c3e
--- /dev/null
+++ b/js/dav/lib/request.js
@@ -0,0 +1,233 @@
+import { multistatus } from './parser';
+import * as template from './template';
+
+/**
+ * Options:
+ *
+ * (String) depth - optional value for Depth header.
+ * (Array.<Object>) props - list of props to request.
+ */
+export function addressBookQuery(options) {
+ return collectionQuery(
+ template.addressBookQuery({ props: options.props || [] }),
+ { depth: options.depth }
+ );
+}
+
+/**
+ * Options:
+ *
+ * (String) data - put request body.
+ * (String) method - http method.
+ * (String) etag - cached calendar object etag.
+ */
+export function basic(options) {
+ function transformRequest(xhr) {
+ setRequestHeaders(xhr, options);
+ }
+
+ return new Request({
+ method: options.method,
+ requestData: options.data,
+ transformRequest: transformRequest
+ });
+}
+
+/**
+ * Options:
+ *
+ * (String) depth - optional value for Depth header.
+ * (Array.<Object>) filters - list of filters to send with request.
+ * (Array.<Object>) props - list of props to request.
+ * (String) timezone - VTIMEZONE calendar object.
+ */
+export function calendarQuery(options) {
+ return collectionQuery(
+ template.calendarQuery({
+ props: options.props || [],
+ filters: options.filters || [],
+ timezone: options.timezone
+ }),
+ {
+ depth: options.depth
+ }
+ );
+}
+
+export function collectionQuery(requestData, options) {
+ function transformRequest(xhr) {
+ setRequestHeaders(xhr, options);
+ }
+
+ function transformResponse(xhr) {
+ return multistatus(xhr.responseText).response.map(res => {
+ return { href: res.href, props: getProps(res.propstat) };
+ });
+ }
+
+ return new Request({
+ method: 'REPORT',
+ requestData: requestData,
+ transformRequest: transformRequest,
+ transformResponse: transformResponse
+ });
+}
+
+export function mkcol(options) {
+ let requestData = template.mkcol({ props: options.props });
+
+ function transformRequest(xhr) {
+ setRequestHeaders(xhr, options);
+ }
+
+ return new Request({
+ method: 'MKCOL',
+ requestData: requestData,
+ transformRequest: transformRequest
+ });
+}
+
+export function proppatch(options) {
+ let requestData = template.proppatch({ props: options.props });
+
+ function transformRequest(xhr) {
+ setRequestHeaders(xhr, options);
+ }
+
+ return new Request({
+ method: 'PROPPATCH',
+ requestData: requestData,
+ transformRequest: transformRequest
+ });
+}
+
+/**
+ * Options:
+ *
+ * (String) depth - optional value for Depth header.
+ * (Array.<Object>) props - list of props to request.
+ */
+export function propfind(options) {
+ let requestData = template.propfind({ props: options.props });
+
+ function transformRequest(xhr) {
+ setRequestHeaders(xhr, options);
+ }
+
+ function transformResponse(xhr) {
+ let responses = multistatus(xhr.responseText).response.map(res => {
+ return { href: res.href, props: getProps(res.propstat) };
+ });
+
+ if (!options.mergeResponses) {
+ return responses;
+ }
+
+ // Merge the props.
+ let merged = mergeProps(responses.map(res => res.props));
+ let hrefs = responses.map(res => res.href);
+ return { props: merged, hrefs: hrefs };
+ }
+
+ return new Request({
+ method: 'PROPFIND',
+ requestData: requestData,
+ transformRequest: transformRequest,
+ transformResponse: transformResponse
+ });
+}
+
+/**
+ * Options:
+ *
+ * (String) depth - option value for Depth header.
+ * (Array.<Object>) props - list of props to request.
+ * (Number) syncLevel - indicates scope of the sync report request.
+ * (String) syncToken - synchronization token provided by the server.
+ */
+export function syncCollection(options) {
+ let requestData = template.syncCollection({
+ props: options.props,
+ syncLevel: options.syncLevel,
+ syncToken: options.syncToken
+ });
+
+ function transformRequest(xhr) {
+ setRequestHeaders(xhr, options);
+ }
+
+ function transformResponse(xhr) {
+ let object = multistatus(xhr.responseText);
+ let responses = object.response.map(res => {
+ return { href: res.href, props: getProps(res.propstat) };
+ });
+
+ return { responses: responses, syncToken: object.syncToken };
+ }
+
+ return new Request({
+ method: 'REPORT',
+ requestData: requestData,
+ transformRequest: transformRequest,
+ transformResponse: transformResponse
+ });
+}
+
+export class Request {
+ constructor(options={}) {
+ Object.assign(this, {
+ method: null,
+ requestData: null,
+ transformRequest: null,
+ transformResponse: null,
+ onerror: null
+ }, options);
+ }
+}
+
+function getProp(propstat) {
+ if (/404/g.test(propstat.status)) {
+ return null;
+ }
+ if (/5\d{2}/g.test(propstat.status) ||
+ /4\d{2}/g.test(propstat.status)) {
+ throw new Error('Bad status on propstat: ' + propstat.status);
+ }
+
+ return ('prop' in propstat) ? propstat.prop : null;
+}
+
+export function mergeProps(props) {
+ return props.reduce((a, b) => Object.assign(a, b), {});
+}
+
+/**
+ * Map propstats to props.
+ */
+export function getProps(propstats) {
+ return mergeProps(
+ propstats
+ .map(getProp)
+ .filter(prop => prop && typeof prop === 'object')
+ );
+}
+
+export function setRequestHeaders(request, options) {
+ request.setRequestHeader('Content-Type', 'application/xml;charset=utf-8');
+
+ if ('depth' in options) {
+ request.setRequestHeader('Depth', options.depth);
+ }
+
+ if ('etag' in options) {
+ request.setRequestHeader('If-Match', options.etag);
+ }
+
+ if ('destination' in options) {
+ request.setRequestHeader('Destination', options.destination);
+ }
+
+ if ('overwrite' in options) {
+ request.setRequestHeader('Overwrite', options.overwrite);
+ }
+}