1 | /**
|
2 | * Copyright 2020 Inrupt Inc.
|
3 | *
|
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
|
5 | * of this software and associated documentation files (the "Software"), to deal in
|
6 | * the Software without restriction, including without limitation the rights to use,
|
7 | * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
8 | * Software, and to permit persons to whom the Software is furnished to do so,
|
9 | * subject to the following conditions:
|
10 | *
|
11 | * The above copyright notice and this permission notice shall be included in
|
12 | * all copies or substantial portions of the Software.
|
13 | *
|
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
16 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
17 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
18 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
19 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20 | */
|
21 |
|
22 | import { fetch } from "./fetcher";
|
23 | import { Headers } from "cross-fetch";
|
24 | import { unstable_UploadRequestInit } from "./interfaces";
|
25 |
|
26 | type FetchFileOptions = {
|
27 | fetch: typeof window.fetch;
|
28 | init: unstable_UploadRequestInit;
|
29 | };
|
30 |
|
31 | const defaultFetchFileOptions = {
|
32 | fetch: fetch,
|
33 | };
|
34 |
|
35 | const RESERVED_HEADERS = ["Slug", "If-None-Match", "Content-Type"];
|
36 |
|
37 | /**
|
38 | * Some of the headers must be set by the library, rather than directly.
|
39 | */
|
40 | function containsReserved(header: Headers): boolean {
|
41 | return RESERVED_HEADERS.some((reserved) => header.has(reserved));
|
42 | }
|
43 |
|
44 | /**
|
45 | * Fetches a file at a given URL, and returns it as a blob of data.
|
46 | *
|
47 | * Please note that this function is still experimental: its API can change in non-major releases.
|
48 | *
|
49 | * @param url The URL of the fetched file
|
50 | * @param options Fetching options: a custom fetcher and/or headers.
|
51 | */
|
52 | export async function unstable_fetchFile(
|
53 | input: RequestInfo,
|
54 | options: Partial<FetchFileOptions> = defaultFetchFileOptions
|
55 | ): Promise<Response> {
|
56 | const config = {
|
57 | ...defaultFetchFileOptions,
|
58 | ...options,
|
59 | };
|
60 | return config.fetch(input, config.init);
|
61 | }
|
62 |
|
63 | /**
|
64 | * Deletes a file at a given URL
|
65 | *
|
66 | * Please note that this function is still experimental: its API can change in non-major releases.
|
67 | *
|
68 | * @param input The URL of the file to delete
|
69 | */
|
70 | export async function unstable_deleteFile(
|
71 | input: RequestInfo,
|
72 | options: Partial<FetchFileOptions> = defaultFetchFileOptions
|
73 | ): Promise<Response> {
|
74 | const config = {
|
75 | ...defaultFetchFileOptions,
|
76 | ...options,
|
77 | };
|
78 | return config.fetch(input, {
|
79 | ...config.init,
|
80 | method: "DELETE",
|
81 | });
|
82 | }
|
83 |
|
84 | type SaveFileOptions = FetchFileOptions & {
|
85 | slug?: string;
|
86 | };
|
87 |
|
88 | /**
|
89 | * Saves a file in a folder at a given URL. The server will return the final
|
90 | * filename (which may or may not be the given `slug`), it will return it in
|
91 | * the response's Location header.
|
92 | *
|
93 | * @param folderUrl The URL of the folder where the new file is saved
|
94 | * @param file The file to be written
|
95 | * @param options Additional parameters for file creation (e.g. a slug)
|
96 | */
|
97 | export async function unstable_saveFileInContainer(
|
98 | folderUrl: RequestInfo,
|
99 | file: Blob,
|
100 | options: Partial<SaveFileOptions> = defaultFetchFileOptions
|
101 | ): Promise<Response> {
|
102 | return writeFile(folderUrl, file, "POST", options);
|
103 | }
|
104 |
|
105 | /**
|
106 | * Saves a file at a given URL, erasing any previous content.
|
107 | *
|
108 | * @param fileUrl The URL where the file is saved
|
109 | * @param file The file to be written
|
110 | * @param options Additional parameters for file creation (e.g. a slug)
|
111 | */
|
112 | export async function unstable_overwriteFile(
|
113 | fileUrl: RequestInfo,
|
114 | file: Blob,
|
115 | options: Partial<FetchFileOptions> = defaultFetchFileOptions
|
116 | ): Promise<Response> {
|
117 | return writeFile(fileUrl, file, "PUT", options);
|
118 | }
|
119 |
|
120 | /**
|
121 | * Internal function that performs the actual write HTTP query, either POST
|
122 | * or PUT depending on the use case.
|
123 | *
|
124 | * @param fileUrl The URL where the file is saved
|
125 | * @param file The file to be written
|
126 | * @param method The HTTP method
|
127 | * @param options Additional parameters for file creation (e.g. a slug)
|
128 | */
|
129 | async function writeFile(
|
130 | targetUrl: RequestInfo,
|
131 | file: Blob,
|
132 | method: "PUT" | "POST",
|
133 | options: Partial<SaveFileOptions>
|
134 | ): Promise<Response> {
|
135 | const config = {
|
136 | ...defaultFetchFileOptions,
|
137 | ...options,
|
138 | };
|
139 | const headers = new Headers(config.init?.headers ?? {});
|
140 | if (containsReserved(headers)) {
|
141 | throw new Error(
|
142 | `No reserved header (${RESERVED_HEADERS.join(
|
143 | ", "
|
144 | )}) should be set in the optional RequestInit.`
|
145 | );
|
146 | }
|
147 |
|
148 | // If a slug is in the parameters, set the request headers accordingly
|
149 | if (config.slug !== undefined) {
|
150 | headers.append("Slug", config.slug);
|
151 | }
|
152 | headers.append("Content-Type", file.type);
|
153 |
|
154 | return await config.fetch(targetUrl, {
|
155 | ...config.init,
|
156 | headers,
|
157 | method,
|
158 | body: file,
|
159 | });
|
160 | }
|