next-tinacms-s3 13.0.1 → 14.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/handlers.js +22 -52
- package/dist/index.js +167 -170
- package/package.json +4 -4
- package/dist/index.mjs +0 -179
package/dist/handlers.js
CHANGED
|
@@ -1,49 +1,20 @@
|
|
|
1
|
-
var __create = Object.create;
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
-
};
|
|
11
|
-
var __copyProps = (to, from, except, desc) => {
|
|
12
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
-
for (let key of __getOwnPropNames(from))
|
|
14
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
-
mod
|
|
26
|
-
));
|
|
27
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
-
|
|
29
1
|
// src/handlers.ts
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
var import_node_path = __toESM(require("node:path"));
|
|
2
|
+
import {
|
|
3
|
+
S3Client,
|
|
4
|
+
ListObjectsCommand,
|
|
5
|
+
PutObjectCommand,
|
|
6
|
+
DeleteObjectCommand,
|
|
7
|
+
HeadObjectCommand
|
|
8
|
+
} from "@aws-sdk/client-s3";
|
|
9
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
10
|
+
import path from "node:path";
|
|
40
11
|
var mediaHandlerConfig = {
|
|
41
12
|
api: {
|
|
42
13
|
bodyParser: false
|
|
43
14
|
}
|
|
44
15
|
};
|
|
45
16
|
var createMediaHandler = (config, options) => {
|
|
46
|
-
const client = new
|
|
17
|
+
const client = new S3Client(config.config);
|
|
47
18
|
const bucket = config.bucket;
|
|
48
19
|
const region = config.config.region || "us-east-1";
|
|
49
20
|
let mediaRoot = config.mediaRoot || "";
|
|
@@ -119,11 +90,11 @@ async function listMedia(req, res, client, bucket, mediaRoot, cdnUrl) {
|
|
|
119
90
|
const params = {
|
|
120
91
|
Bucket: bucket,
|
|
121
92
|
Delimiter: "/",
|
|
122
|
-
Prefix: mediaRoot ?
|
|
93
|
+
Prefix: mediaRoot ? path.join(mediaRoot, prefix) : prefix,
|
|
123
94
|
Marker: offset?.toString(),
|
|
124
95
|
MaxKeys: directory && !offset ? +limit + 1 : +limit
|
|
125
96
|
};
|
|
126
|
-
const command = new
|
|
97
|
+
const command = new ListObjectsCommand(params);
|
|
127
98
|
const response = await client.send(command);
|
|
128
99
|
const items = [];
|
|
129
100
|
response.CommonPrefixes?.forEach(({ Prefix }) => {
|
|
@@ -134,8 +105,8 @@ async function listMedia(req, res, client, bucket, mediaRoot, cdnUrl) {
|
|
|
134
105
|
items.push({
|
|
135
106
|
id: Prefix,
|
|
136
107
|
type: "dir",
|
|
137
|
-
filename:
|
|
138
|
-
directory:
|
|
108
|
+
filename: path.basename(strippedPrefix),
|
|
109
|
+
directory: path.dirname(strippedPrefix)
|
|
139
110
|
});
|
|
140
111
|
});
|
|
141
112
|
items.push(
|
|
@@ -169,7 +140,7 @@ async function deleteAsset(req, res, client, bucket) {
|
|
|
169
140
|
Bucket: bucket,
|
|
170
141
|
Key: objectKey
|
|
171
142
|
};
|
|
172
|
-
const command = new
|
|
143
|
+
const command = new DeleteObjectCommand(params);
|
|
173
144
|
try {
|
|
174
145
|
const data = await client.send(command);
|
|
175
146
|
res.json(data);
|
|
@@ -183,7 +154,7 @@ async function deleteAsset(req, res, client, bucket) {
|
|
|
183
154
|
}
|
|
184
155
|
async function keyExists(client, bucket, key) {
|
|
185
156
|
try {
|
|
186
|
-
const cmd = new
|
|
157
|
+
const cmd = new HeadObjectCommand({
|
|
187
158
|
Bucket: bucket,
|
|
188
159
|
Key: key
|
|
189
160
|
});
|
|
@@ -200,9 +171,9 @@ async function keyExists(client, bucket, key) {
|
|
|
200
171
|
}
|
|
201
172
|
}
|
|
202
173
|
var getUploadUrl = async (bucket, key, expiresIn, client) => {
|
|
203
|
-
return
|
|
174
|
+
return getSignedUrl(
|
|
204
175
|
client,
|
|
205
|
-
new
|
|
176
|
+
new PutObjectCommand({
|
|
206
177
|
Bucket: bucket,
|
|
207
178
|
Key: key
|
|
208
179
|
}),
|
|
@@ -212,8 +183,8 @@ var getUploadUrl = async (bucket, key, expiresIn, client) => {
|
|
|
212
183
|
function getS3ToTinaFunc(cdnUrl, mediaRoot) {
|
|
213
184
|
return function s3ToTina(file) {
|
|
214
185
|
const strippedKey = stripMediaRoot(mediaRoot, file.Key);
|
|
215
|
-
const filename =
|
|
216
|
-
const directory =
|
|
186
|
+
const filename = path.basename(strippedKey);
|
|
187
|
+
const directory = path.dirname(strippedKey) + "/";
|
|
217
188
|
const src = cdnUrl + file.Key;
|
|
218
189
|
return {
|
|
219
190
|
id: file.Key,
|
|
@@ -229,9 +200,8 @@ function getS3ToTinaFunc(cdnUrl, mediaRoot) {
|
|
|
229
200
|
};
|
|
230
201
|
};
|
|
231
202
|
}
|
|
232
|
-
|
|
233
|
-
0 && (module.exports = {
|
|
203
|
+
export {
|
|
234
204
|
createMediaHandler,
|
|
235
205
|
getUploadUrl,
|
|
236
206
|
mediaHandlerConfig
|
|
237
|
-
}
|
|
207
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,182 +1,179 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
this.ERR_TYPE = "MediaListError";
|
|
9
|
-
this.title = config.title;
|
|
10
|
-
this.docsLink = config.docsLink;
|
|
11
|
-
}
|
|
1
|
+
import { DEFAULT_MEDIA_UPLOAD_TYPES } from "tinacms";
|
|
2
|
+
class MediaListError extends Error {
|
|
3
|
+
constructor(config) {
|
|
4
|
+
super(config.message);
|
|
5
|
+
this.ERR_TYPE = "MediaListError";
|
|
6
|
+
this.title = config.title;
|
|
7
|
+
this.docsLink = config.docsLink;
|
|
12
8
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const path = `${directory && directory !== "/" ? `${directory}/${item.file.name}` : item.file.name}`;
|
|
78
|
-
const res = await this.fetchWithBasePath(
|
|
79
|
-
`/api/s3/media/upload_url?key=${path}`,
|
|
80
|
-
{
|
|
81
|
-
method: "GET"
|
|
82
|
-
}
|
|
83
|
-
);
|
|
84
|
-
if (res.status != 200) {
|
|
85
|
-
const responseData = await res.json();
|
|
86
|
-
throw new Error(responseData.message);
|
|
87
|
-
}
|
|
88
|
-
const { signedUrl, src } = await res.json();
|
|
89
|
-
if (!signedUrl || !src) {
|
|
90
|
-
throw new Error("Unexpected error generating upload url");
|
|
91
|
-
}
|
|
92
|
-
const uploadRes = await fetch(signedUrl, {
|
|
93
|
-
method: "PUT",
|
|
94
|
-
body: item.file,
|
|
95
|
-
headers: {
|
|
96
|
-
"Content-Type": item.file.type || "application/octet-stream"
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
if (!uploadRes.ok) {
|
|
100
|
-
const xmlRes = await uploadRes.text();
|
|
101
|
-
const matches = s3ErrorRegex.exec(xmlRes);
|
|
102
|
-
console.error(xmlRes);
|
|
103
|
-
if (!matches) {
|
|
104
|
-
throw new Error("Unexpected error uploading media asset");
|
|
105
|
-
} else {
|
|
106
|
-
throw new Error(`Upload error: '${matches[2]}'`);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
await new Promise((resolve) => {
|
|
110
|
-
setTimeout(resolve, 2e3);
|
|
111
|
-
});
|
|
112
|
-
newFiles.push({
|
|
113
|
-
directory: item.directory,
|
|
114
|
-
filename: item.file.name,
|
|
115
|
-
id: item.file.name,
|
|
116
|
-
type: "file",
|
|
117
|
-
thumbnails: {
|
|
118
|
-
"75x75": src,
|
|
119
|
-
"400x400": src,
|
|
120
|
-
"1000x1000": src
|
|
121
|
-
},
|
|
122
|
-
src
|
|
123
|
-
});
|
|
9
|
+
}
|
|
10
|
+
const E_DEFAULT = new MediaListError({
|
|
11
|
+
title: "An Error Occurred",
|
|
12
|
+
message: "Something went wrong fetching your media from S3.",
|
|
13
|
+
docsLink: "https://tina.io/docs/r/aws-s3-bucket"
|
|
14
|
+
});
|
|
15
|
+
const E_UNAUTHORIZED = new MediaListError({
|
|
16
|
+
title: "Unauthorized",
|
|
17
|
+
message: "You don't have access to this resource.",
|
|
18
|
+
docsLink: "https://tina.io/docs/r/aws-s3-bucket"
|
|
19
|
+
});
|
|
20
|
+
const E_CONFIG = new MediaListError({
|
|
21
|
+
title: "Missing Credentials",
|
|
22
|
+
message: "Unable to connect to S3 because one or more environment variables are missing.",
|
|
23
|
+
docsLink: "https://tina.io/docs/r/aws-s3-bucket/"
|
|
24
|
+
});
|
|
25
|
+
const E_KEY_FAIL = new MediaListError({
|
|
26
|
+
title: "Bad Credentials",
|
|
27
|
+
message: "Unable to connect to S3 because one or more environment variables are misconfigured.",
|
|
28
|
+
docsLink: "https://tina.io/docs/r/aws-s3-bucket/"
|
|
29
|
+
});
|
|
30
|
+
const E_BAD_ROUTE = new MediaListError({
|
|
31
|
+
title: "Bad Route",
|
|
32
|
+
message: "The S3 API route is missing or misconfigured.",
|
|
33
|
+
docsLink: "https://tina.io/docs/r/aws-s3-bucket/#set-up-api-routes"
|
|
34
|
+
});
|
|
35
|
+
const interpretErrorMessage = (message) => {
|
|
36
|
+
switch (message) {
|
|
37
|
+
case "Must supply cloud_name":
|
|
38
|
+
case "Must supply api_key":
|
|
39
|
+
case "Must supply api_secret":
|
|
40
|
+
return E_CONFIG;
|
|
41
|
+
case "unknown api_key":
|
|
42
|
+
return E_KEY_FAIL;
|
|
43
|
+
default:
|
|
44
|
+
return E_DEFAULT;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const s3ErrorRegex = /<Error>.*<Code>(.+)<\/Code>.*<Message>(.+)<\/Message>.*/;
|
|
48
|
+
class S3MediaStore {
|
|
49
|
+
constructor() {
|
|
50
|
+
this.fetchFunction = (input, init) => {
|
|
51
|
+
return fetch(input, init);
|
|
52
|
+
};
|
|
53
|
+
this.accept = DEFAULT_MEDIA_UPLOAD_TYPES;
|
|
54
|
+
this.basePath = "";
|
|
55
|
+
this.parse = (img) => {
|
|
56
|
+
return img.src;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
fetchWithBasePath(path, init) {
|
|
60
|
+
const fullPath = this.getFullPath(path);
|
|
61
|
+
const normalizedPath = fullPath.startsWith("/") ? fullPath : `/${fullPath}`;
|
|
62
|
+
return this.fetchFunction(normalizedPath, init);
|
|
63
|
+
}
|
|
64
|
+
getFullPath(path) {
|
|
65
|
+
return `${this.basePath}${path}`;
|
|
66
|
+
}
|
|
67
|
+
async persist(media) {
|
|
68
|
+
const newFiles = [];
|
|
69
|
+
for (const item of media) {
|
|
70
|
+
let directory = item.directory;
|
|
71
|
+
if (directory == null ? void 0 : directory.endsWith("/")) {
|
|
72
|
+
directory = directory.substr(0, directory.length - 1);
|
|
124
73
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
await this.fetchWithBasePath(
|
|
129
|
-
`/api/s3/media/${encodeURIComponent(media.id)}`,
|
|
74
|
+
const path = `${directory && directory !== "/" ? `${directory}/${item.file.name}` : item.file.name}`;
|
|
75
|
+
const res = await this.fetchWithBasePath(
|
|
76
|
+
`/api/s3/media/upload_url?key=${path}`,
|
|
130
77
|
{
|
|
131
|
-
method: "
|
|
78
|
+
method: "GET"
|
|
132
79
|
}
|
|
133
80
|
);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const response = await this.fetchWithBasePath("/api/s3/media" + query);
|
|
138
|
-
if (response.status == 401) {
|
|
139
|
-
throw E_UNAUTHORIZED;
|
|
81
|
+
if (res.status != 200) {
|
|
82
|
+
const responseData = await res.json();
|
|
83
|
+
throw new Error(responseData.message);
|
|
140
84
|
}
|
|
141
|
-
|
|
142
|
-
|
|
85
|
+
const { signedUrl, src } = await res.json();
|
|
86
|
+
if (!signedUrl || !src) {
|
|
87
|
+
throw new Error("Unexpected error generating upload url");
|
|
143
88
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
89
|
+
const uploadRes = await fetch(signedUrl, {
|
|
90
|
+
method: "PUT",
|
|
91
|
+
body: item.file,
|
|
92
|
+
headers: {
|
|
93
|
+
"Content-Type": item.file.type || "application/octet-stream"
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
if (!uploadRes.ok) {
|
|
97
|
+
const xmlRes = await uploadRes.text();
|
|
98
|
+
const matches = s3ErrorRegex.exec(xmlRes);
|
|
99
|
+
console.error(xmlRes);
|
|
100
|
+
if (!matches) {
|
|
101
|
+
throw new Error("Unexpected error uploading media asset");
|
|
102
|
+
} else {
|
|
103
|
+
throw new Error(`Upload error: '${matches[2]}'`);
|
|
104
|
+
}
|
|
148
105
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
106
|
+
await new Promise((resolve) => {
|
|
107
|
+
setTimeout(resolve, 2e3);
|
|
108
|
+
});
|
|
109
|
+
newFiles.push({
|
|
110
|
+
directory: item.directory,
|
|
111
|
+
filename: item.file.name,
|
|
112
|
+
id: item.file.name,
|
|
113
|
+
type: "file",
|
|
114
|
+
thumbnails: {
|
|
115
|
+
"75x75": src,
|
|
116
|
+
"400x400": src,
|
|
117
|
+
"1000x1000": src
|
|
118
|
+
},
|
|
119
|
+
src
|
|
120
|
+
});
|
|
158
121
|
}
|
|
122
|
+
return newFiles;
|
|
159
123
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
124
|
+
async delete(media) {
|
|
125
|
+
await this.fetchWithBasePath(
|
|
126
|
+
`/api/s3/media/${encodeURIComponent(media.id)}`,
|
|
127
|
+
{
|
|
128
|
+
method: "DELETE"
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
async list(options) {
|
|
133
|
+
const query = this.buildQuery(options);
|
|
134
|
+
const response = await this.fetchWithBasePath("/api/s3/media" + query);
|
|
135
|
+
if (response.status == 401) {
|
|
136
|
+
throw E_UNAUTHORIZED;
|
|
137
|
+
}
|
|
138
|
+
if (response.status == 404) {
|
|
139
|
+
throw E_BAD_ROUTE;
|
|
140
|
+
}
|
|
141
|
+
if (response.status >= 500) {
|
|
142
|
+
const { e } = await response.json();
|
|
143
|
+
const error = interpretErrorMessage(e);
|
|
144
|
+
throw error;
|
|
177
145
|
}
|
|
146
|
+
const { items, offset } = await response.json();
|
|
147
|
+
return {
|
|
148
|
+
items: items.map((item) => item),
|
|
149
|
+
nextOffset: offset
|
|
150
|
+
};
|
|
178
151
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
152
|
+
buildQuery(options) {
|
|
153
|
+
const params = Object.keys(options).filter((key) => options[key] !== "" && options[key] !== void 0).map((key) => `${key}=${options[key]}`).join("&");
|
|
154
|
+
return `?${params}`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
class TinaCloudS3MediaStore extends S3MediaStore {
|
|
158
|
+
constructor(client) {
|
|
159
|
+
var _a;
|
|
160
|
+
super();
|
|
161
|
+
this.client = client;
|
|
162
|
+
const basePath = (_a = this.client.schema.config.config.build) == null ? void 0 : _a.basePath;
|
|
163
|
+
this.basePath = basePath || "";
|
|
164
|
+
this.fetchFunction = async (input, init) => {
|
|
165
|
+
try {
|
|
166
|
+
const url = input.toString();
|
|
167
|
+
const query = `${url.includes("?") ? "&" : "?"}clientID=${client.clientId}`;
|
|
168
|
+
const res = client.authProvider.fetchWithToken(url + query, init);
|
|
169
|
+
return res;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error(error);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
export {
|
|
177
|
+
S3MediaStore,
|
|
178
|
+
TinaCloudS3MediaStore
|
|
179
|
+
};
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-tinacms-s3",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "14.0.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
|
-
"module": "dist/index.
|
|
5
|
+
"module": "./dist/index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist"
|
|
8
8
|
],
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
"react-dom": "^18.3.1",
|
|
28
28
|
"typescript": "^5.7.3",
|
|
29
29
|
"@tinacms/scripts": "1.4.1",
|
|
30
|
-
"tinacms": "
|
|
30
|
+
"tinacms": "3.0.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"tinacms": "
|
|
33
|
+
"tinacms": "3.0.0"
|
|
34
34
|
},
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"registry": "https://registry.npmjs.org"
|
package/dist/index.mjs
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_MEDIA_UPLOAD_TYPES } from "tinacms";
|
|
2
|
-
class MediaListError extends Error {
|
|
3
|
-
constructor(config) {
|
|
4
|
-
super(config.message);
|
|
5
|
-
this.ERR_TYPE = "MediaListError";
|
|
6
|
-
this.title = config.title;
|
|
7
|
-
this.docsLink = config.docsLink;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
const E_DEFAULT = new MediaListError({
|
|
11
|
-
title: "An Error Occurred",
|
|
12
|
-
message: "Something went wrong fetching your media from S3.",
|
|
13
|
-
docsLink: "https://tina.io/docs/r/aws-s3-bucket"
|
|
14
|
-
});
|
|
15
|
-
const E_UNAUTHORIZED = new MediaListError({
|
|
16
|
-
title: "Unauthorized",
|
|
17
|
-
message: "You don't have access to this resource.",
|
|
18
|
-
docsLink: "https://tina.io/docs/r/aws-s3-bucket"
|
|
19
|
-
});
|
|
20
|
-
const E_CONFIG = new MediaListError({
|
|
21
|
-
title: "Missing Credentials",
|
|
22
|
-
message: "Unable to connect to S3 because one or more environment variables are missing.",
|
|
23
|
-
docsLink: "https://tina.io/docs/r/aws-s3-bucket/"
|
|
24
|
-
});
|
|
25
|
-
const E_KEY_FAIL = new MediaListError({
|
|
26
|
-
title: "Bad Credentials",
|
|
27
|
-
message: "Unable to connect to S3 because one or more environment variables are misconfigured.",
|
|
28
|
-
docsLink: "https://tina.io/docs/r/aws-s3-bucket/"
|
|
29
|
-
});
|
|
30
|
-
const E_BAD_ROUTE = new MediaListError({
|
|
31
|
-
title: "Bad Route",
|
|
32
|
-
message: "The S3 API route is missing or misconfigured.",
|
|
33
|
-
docsLink: "https://tina.io/docs/r/aws-s3-bucket/#set-up-api-routes"
|
|
34
|
-
});
|
|
35
|
-
const interpretErrorMessage = (message) => {
|
|
36
|
-
switch (message) {
|
|
37
|
-
case "Must supply cloud_name":
|
|
38
|
-
case "Must supply api_key":
|
|
39
|
-
case "Must supply api_secret":
|
|
40
|
-
return E_CONFIG;
|
|
41
|
-
case "unknown api_key":
|
|
42
|
-
return E_KEY_FAIL;
|
|
43
|
-
default:
|
|
44
|
-
return E_DEFAULT;
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
const s3ErrorRegex = /<Error>.*<Code>(.+)<\/Code>.*<Message>(.+)<\/Message>.*/;
|
|
48
|
-
class S3MediaStore {
|
|
49
|
-
constructor() {
|
|
50
|
-
this.fetchFunction = (input, init) => {
|
|
51
|
-
return fetch(input, init);
|
|
52
|
-
};
|
|
53
|
-
this.accept = DEFAULT_MEDIA_UPLOAD_TYPES;
|
|
54
|
-
this.basePath = "";
|
|
55
|
-
this.parse = (img) => {
|
|
56
|
-
return img.src;
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
fetchWithBasePath(path, init) {
|
|
60
|
-
const fullPath = this.getFullPath(path);
|
|
61
|
-
const normalizedPath = fullPath.startsWith("/") ? fullPath : `/${fullPath}`;
|
|
62
|
-
return this.fetchFunction(normalizedPath, init);
|
|
63
|
-
}
|
|
64
|
-
getFullPath(path) {
|
|
65
|
-
return `${this.basePath}${path}`;
|
|
66
|
-
}
|
|
67
|
-
async persist(media) {
|
|
68
|
-
const newFiles = [];
|
|
69
|
-
for (const item of media) {
|
|
70
|
-
let directory = item.directory;
|
|
71
|
-
if (directory == null ? void 0 : directory.endsWith("/")) {
|
|
72
|
-
directory = directory.substr(0, directory.length - 1);
|
|
73
|
-
}
|
|
74
|
-
const path = `${directory && directory !== "/" ? `${directory}/${item.file.name}` : item.file.name}`;
|
|
75
|
-
const res = await this.fetchWithBasePath(
|
|
76
|
-
`/api/s3/media/upload_url?key=${path}`,
|
|
77
|
-
{
|
|
78
|
-
method: "GET"
|
|
79
|
-
}
|
|
80
|
-
);
|
|
81
|
-
if (res.status != 200) {
|
|
82
|
-
const responseData = await res.json();
|
|
83
|
-
throw new Error(responseData.message);
|
|
84
|
-
}
|
|
85
|
-
const { signedUrl, src } = await res.json();
|
|
86
|
-
if (!signedUrl || !src) {
|
|
87
|
-
throw new Error("Unexpected error generating upload url");
|
|
88
|
-
}
|
|
89
|
-
const uploadRes = await fetch(signedUrl, {
|
|
90
|
-
method: "PUT",
|
|
91
|
-
body: item.file,
|
|
92
|
-
headers: {
|
|
93
|
-
"Content-Type": item.file.type || "application/octet-stream"
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
if (!uploadRes.ok) {
|
|
97
|
-
const xmlRes = await uploadRes.text();
|
|
98
|
-
const matches = s3ErrorRegex.exec(xmlRes);
|
|
99
|
-
console.error(xmlRes);
|
|
100
|
-
if (!matches) {
|
|
101
|
-
throw new Error("Unexpected error uploading media asset");
|
|
102
|
-
} else {
|
|
103
|
-
throw new Error(`Upload error: '${matches[2]}'`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
await new Promise((resolve) => {
|
|
107
|
-
setTimeout(resolve, 2e3);
|
|
108
|
-
});
|
|
109
|
-
newFiles.push({
|
|
110
|
-
directory: item.directory,
|
|
111
|
-
filename: item.file.name,
|
|
112
|
-
id: item.file.name,
|
|
113
|
-
type: "file",
|
|
114
|
-
thumbnails: {
|
|
115
|
-
"75x75": src,
|
|
116
|
-
"400x400": src,
|
|
117
|
-
"1000x1000": src
|
|
118
|
-
},
|
|
119
|
-
src
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
return newFiles;
|
|
123
|
-
}
|
|
124
|
-
async delete(media) {
|
|
125
|
-
await this.fetchWithBasePath(
|
|
126
|
-
`/api/s3/media/${encodeURIComponent(media.id)}`,
|
|
127
|
-
{
|
|
128
|
-
method: "DELETE"
|
|
129
|
-
}
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
async list(options) {
|
|
133
|
-
const query = this.buildQuery(options);
|
|
134
|
-
const response = await this.fetchWithBasePath("/api/s3/media" + query);
|
|
135
|
-
if (response.status == 401) {
|
|
136
|
-
throw E_UNAUTHORIZED;
|
|
137
|
-
}
|
|
138
|
-
if (response.status == 404) {
|
|
139
|
-
throw E_BAD_ROUTE;
|
|
140
|
-
}
|
|
141
|
-
if (response.status >= 500) {
|
|
142
|
-
const { e } = await response.json();
|
|
143
|
-
const error = interpretErrorMessage(e);
|
|
144
|
-
throw error;
|
|
145
|
-
}
|
|
146
|
-
const { items, offset } = await response.json();
|
|
147
|
-
return {
|
|
148
|
-
items: items.map((item) => item),
|
|
149
|
-
nextOffset: offset
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
buildQuery(options) {
|
|
153
|
-
const params = Object.keys(options).filter((key) => options[key] !== "" && options[key] !== void 0).map((key) => `${key}=${options[key]}`).join("&");
|
|
154
|
-
return `?${params}`;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
class TinaCloudS3MediaStore extends S3MediaStore {
|
|
158
|
-
constructor(client) {
|
|
159
|
-
var _a;
|
|
160
|
-
super();
|
|
161
|
-
this.client = client;
|
|
162
|
-
const basePath = (_a = this.client.schema.config.config.build) == null ? void 0 : _a.basePath;
|
|
163
|
-
this.basePath = basePath || "";
|
|
164
|
-
this.fetchFunction = async (input, init) => {
|
|
165
|
-
try {
|
|
166
|
-
const url = input.toString();
|
|
167
|
-
const query = `${url.includes("?") ? "&" : "?"}clientID=${client.clientId}`;
|
|
168
|
-
const res = client.authProvider.fetchWithToken(url + query, init);
|
|
169
|
-
return res;
|
|
170
|
-
} catch (error) {
|
|
171
|
-
console.error(error);
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
export {
|
|
177
|
-
S3MediaStore,
|
|
178
|
-
TinaCloudS3MediaStore
|
|
179
|
-
};
|