Merge branch '20469-contents-select' refs #20469
[arvados-workbench2.git] / src / common / webdav.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { customEncodeURI } from "./url";
6
7 export class WebDAV {
8
9     private defaults: WebDAVDefaults = {
10         baseURL: '',
11         headers: {
12             'Cache-Control': 'no-cache'
13         },
14     };
15
16     constructor(config?: Partial<WebDAVDefaults>, private createRequest = () => new XMLHttpRequest()) {
17         if (config) {
18             this.defaults = {
19                 ...this.defaults,
20                 ...config,
21                 headers: {
22                     ...this.defaults.headers,
23                     ...config.headers
24                 },
25             };
26         }
27     }
28
29     getBaseUrl = (): string => this.defaults.baseURL;
30     setAuthorization = (token?) => this.defaults.headers.Authorization = token;
31
32     propfind = (url: string, config: WebDAVRequestConfig = {}) =>
33         this.request({
34             ...config, url,
35             method: 'PROPFIND'
36         })
37
38     put = (url: string, data?: any, config: WebDAVRequestConfig = {}) =>
39         this.request({
40             ...config, url,
41             method: 'PUT',
42             data
43         })
44
45     get = (url: string, config: WebDAVRequestConfig = {}) =>
46         this.request({
47             ...config, url,
48             method: 'GET'
49         })
50
51     upload = (url: string, files: File[], config: WebDAVRequestConfig = {}) => {
52         return Promise.all(
53             files.map(file => this.request({
54                 ...config, url,
55                 method: 'PUT',
56                 data: file
57             }))
58         );
59     }
60
61     copy = (url: string, destination: string, config: WebDAVRequestConfig = {}) =>
62         this.request({
63             ...config, url,
64             method: 'COPY',
65             headers: {
66                 ...config.headers,
67                 Destination: this.defaults.baseURL
68                     ? this.defaults.baseURL.replace(/\/+$/, '') + '/' + destination.replace(/^\/+/, '')
69                     : destination
70             }
71         })
72
73     move = (url: string, destination: string, config: WebDAVRequestConfig = {}) =>
74         this.request({
75             ...config, url,
76             method: 'MOVE',
77             headers: {
78                 ...config.headers,
79                 Destination: this.defaults.baseURL
80                     ? this.defaults.baseURL.replace(/\/+$/, '') + '/' + destination.replace(/^\/+/, '')
81                     : destination
82             }
83         })
84
85     delete = (url: string, config: WebDAVRequestConfig = {}) =>
86         this.request({
87             ...config, url,
88             method: 'DELETE'
89         })
90
91     private request = (config: RequestConfig) => {
92         return new Promise<XMLHttpRequest>((resolve, reject) => {
93             const r = this.createRequest();
94             this.defaults.baseURL = this.defaults.baseURL.replace(/\/+$/, '');
95             r.open(config.method,
96                 `${this.defaults.baseURL
97                     ? this.defaults.baseURL + '/'
98                     : ''}${customEncodeURI(config.url)}`);
99
100             const headers = { ...this.defaults.headers, ...config.headers };
101             Object
102                 .keys(headers)
103                 .forEach(key => r.setRequestHeader(key, headers[key]));
104
105             if (!(window as any).cancelTokens) {
106                 Object.assign(window, { cancelTokens: {} });
107             }
108
109             (window as any).cancelTokens[config.url] = () => {
110                 resolve(r);
111                 r.abort();
112             }
113
114             if (config.onUploadProgress) {
115                 r.upload.addEventListener('progress', config.onUploadProgress);
116             }
117
118             // This event gets triggered on *any* server response
119             r.addEventListener('load', () => {
120                 if (r.status >= 400) {
121                     return reject(r);
122                 } else {
123                     return resolve(r);
124                 }
125             });
126
127             // This event gets triggered on network errors
128             r.addEventListener('error', () => {
129                 return reject(r);
130             });
131
132             r.upload.addEventListener('error', () => {
133                 return reject(r);
134             });
135
136             r.send(config.data);
137         });
138     }
139 }
140
141 export interface WebDAVRequestConfig {
142     headers?: {
143         [key: string]: string;
144     };
145     onUploadProgress?: (event: ProgressEvent) => void;
146 }
147
148 interface WebDAVDefaults {
149     baseURL: string;
150     headers: { [key: string]: string };
151 }
152
153 interface RequestConfig {
154     method: string;
155     url: string;
156     headers?: { [key: string]: string };
157     data?: any;
158     onUploadProgress?: (event: ProgressEvent) => void;
159 }