@blocklet/uploader-server 0.1.49
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/LICENSE +13 -0
- package/README.md +101 -0
- package/es/index.d.ts +1 -0
- package/es/index.js +1 -0
- package/es/middlewares/companion.d.ts +6 -0
- package/es/middlewares/companion.js +92 -0
- package/es/middlewares/local-storage.d.ts +18 -0
- package/es/middlewares/local-storage.js +374 -0
- package/es/middlewares/static-resource.d.ts +11 -0
- package/es/middlewares/static-resource.js +107 -0
- package/es/middlewares.d.ts +3 -0
- package/es/middlewares.js +3 -0
- package/es/types.d.ts +0 -0
- package/es/types.js +0 -0
- package/es/utils.d.ts +0 -0
- package/es/utils.js +0 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +16 -0
- package/lib/middlewares/companion.d.ts +6 -0
- package/lib/middlewares/companion.js +104 -0
- package/lib/middlewares/local-storage.d.ts +18 -0
- package/lib/middlewares/local-storage.js +423 -0
- package/lib/middlewares/static-resource.d.ts +11 -0
- package/lib/middlewares/static-resource.js +133 -0
- package/lib/middlewares.d.ts +3 -0
- package/lib/middlewares.js +38 -0
- package/lib/types.d.ts +0 -0
- package/lib/types.js +1 -0
- package/lib/utils.d.ts +0 -0
- package/lib/utils.js +1 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2018-2020 ArcBlock
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# @blocklet/uploader-server-server
|
|
2
|
+
|
|
3
|
+
**@blocklet/uploader-server** is a package that integrates the **uppy** service to provide universal upload capability for blocklets. For more information about uppy, refer to the [official documentation](https://uppy.io/docs/quick-start/).
|
|
4
|
+
|
|
5
|
+
## Package Structure
|
|
6
|
+
|
|
7
|
+
The package is composed of both frontend and backend components. The backend code can be found in the `middlewares` folder.
|
|
8
|
+
|
|
9
|
+
## Development
|
|
10
|
+
|
|
11
|
+
### Install In Blocklet
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
# You can use npm / yarn
|
|
15
|
+
pnpm add @blocklet/uploader-server
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Install Dependencies
|
|
19
|
+
|
|
20
|
+
To install the required dependencies, run the following command:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
pnpm i
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Build Packages
|
|
27
|
+
|
|
28
|
+
To build the packages, execute the following command:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
pnpm build
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Build, Watch, and Run Development Server
|
|
35
|
+
|
|
36
|
+
For building, watching changes, and running the development server, use the following command:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
pnpm run dev
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Backend Example
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
const { initLocalStorageServer, initCompanion } = require('@blocklet/uploader-server');
|
|
46
|
+
|
|
47
|
+
// init uploader server
|
|
48
|
+
const localStorageServer = initLocalStorageServer({
|
|
49
|
+
path: env.uploadDir,
|
|
50
|
+
express,
|
|
51
|
+
onUploadFinish: async (req, res, uploadMetadata) => {
|
|
52
|
+
const {
|
|
53
|
+
id: filename,
|
|
54
|
+
size,
|
|
55
|
+
metadata: { filename: originalname, filetype: mimetype },
|
|
56
|
+
} = uploadMetadata;
|
|
57
|
+
|
|
58
|
+
const obj = new URL(env.appUrl);
|
|
59
|
+
obj.protocol = req.get('x-forwarded-proto') || req.protocol;
|
|
60
|
+
obj.pathname = joinUrl(req.headers['x-path-prefix'] || '/', '/uploads', filename);
|
|
61
|
+
|
|
62
|
+
const doc = await Upload.insert({
|
|
63
|
+
mimetype,
|
|
64
|
+
originalname,
|
|
65
|
+
filename,
|
|
66
|
+
size,
|
|
67
|
+
remark: req.body.remark || '',
|
|
68
|
+
tags: (req.body.tags || '')
|
|
69
|
+
.split(',')
|
|
70
|
+
.map((x) => x.trim())
|
|
71
|
+
.filter(Boolean),
|
|
72
|
+
folderId: req.componentDid,
|
|
73
|
+
createdAt: new Date().toISOString(),
|
|
74
|
+
updatedAt: new Date().toISOString(),
|
|
75
|
+
createdBy: req.user.did,
|
|
76
|
+
updatedBy: req.user.did,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const resData = { url: obj.href, ...doc };
|
|
80
|
+
|
|
81
|
+
return resData;
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
router.use('/uploads', user, auth, ensureComponentDid, localStorageServer.handle);
|
|
86
|
+
|
|
87
|
+
// if you need to load file from remote
|
|
88
|
+
// companion
|
|
89
|
+
const companion = initCompanion({
|
|
90
|
+
path: env.uploadDir,
|
|
91
|
+
express,
|
|
92
|
+
providerOptions: env.providerOptions,
|
|
93
|
+
uploadUrls: [env.appUrl],
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
router.use('/companion', user, auth, ensureComponentDid, companion.handle);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
This package is licensed under the MIT license.
|
package/es/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './middlewares';
|
package/es/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./middlewares.js";
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const companion = require("@uppy/companion");
|
|
2
|
+
const bodyParser = require("body-parser");
|
|
3
|
+
const session = require("express-session");
|
|
4
|
+
const axios = require("axios");
|
|
5
|
+
const crypto = require("crypto");
|
|
6
|
+
const secret = crypto.randomBytes(32).toString("hex");
|
|
7
|
+
export function initCompanion({
|
|
8
|
+
path,
|
|
9
|
+
express,
|
|
10
|
+
providerOptions,
|
|
11
|
+
...restProps
|
|
12
|
+
}) {
|
|
13
|
+
const app = express();
|
|
14
|
+
app.use(bodyParser.json());
|
|
15
|
+
app.use(session({ secret }));
|
|
16
|
+
app.use("/proxy", proxyImageDownload);
|
|
17
|
+
let dynamicProviderOptions = providerOptions;
|
|
18
|
+
const companionOptions = {
|
|
19
|
+
secret,
|
|
20
|
+
providerOptions,
|
|
21
|
+
// unused
|
|
22
|
+
server: {
|
|
23
|
+
protocol: "https",
|
|
24
|
+
host: "UNUSED_HOST",
|
|
25
|
+
// unused
|
|
26
|
+
path: "UNUSED_PATH"
|
|
27
|
+
// unused
|
|
28
|
+
},
|
|
29
|
+
filePath: path,
|
|
30
|
+
streamingUpload: true,
|
|
31
|
+
metrics: false,
|
|
32
|
+
...restProps
|
|
33
|
+
};
|
|
34
|
+
const newCompanion = companion.app(companionOptions);
|
|
35
|
+
const { app: companionApp } = newCompanion;
|
|
36
|
+
app.all(
|
|
37
|
+
"*",
|
|
38
|
+
(req, res, next) => {
|
|
39
|
+
let hackerCompanion = {};
|
|
40
|
+
Object.defineProperty(req, "companion", {
|
|
41
|
+
get() {
|
|
42
|
+
return hackerCompanion;
|
|
43
|
+
},
|
|
44
|
+
set(value) {
|
|
45
|
+
hackerCompanion = value;
|
|
46
|
+
hackerCompanion.options.providerOptions = dynamicProviderOptions;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
next();
|
|
50
|
+
},
|
|
51
|
+
companionApp
|
|
52
|
+
);
|
|
53
|
+
newCompanion.handle = app;
|
|
54
|
+
newCompanion.setProviderOptions = (options) => {
|
|
55
|
+
dynamicProviderOptions = options;
|
|
56
|
+
};
|
|
57
|
+
return newCompanion;
|
|
58
|
+
}
|
|
59
|
+
export async function proxyImageDownload(req, res, next) {
|
|
60
|
+
let { url, responseType = "stream" } = {
|
|
61
|
+
...req.query,
|
|
62
|
+
...req.body
|
|
63
|
+
};
|
|
64
|
+
if (url) {
|
|
65
|
+
url = encodeURI(url);
|
|
66
|
+
try {
|
|
67
|
+
const { headers, data, status } = await axios.get(url, {
|
|
68
|
+
responseType
|
|
69
|
+
});
|
|
70
|
+
if (data && status >= 200 && status < 302) {
|
|
71
|
+
res.setHeader("Content-Type", headers["content-type"]);
|
|
72
|
+
try {
|
|
73
|
+
} catch (error) {
|
|
74
|
+
}
|
|
75
|
+
if (responseType === "stream") {
|
|
76
|
+
data.pipe(res);
|
|
77
|
+
} else if (responseType === "arraybuffer") {
|
|
78
|
+
res.end(data?.toString?.("binary"), "binary");
|
|
79
|
+
} else {
|
|
80
|
+
res.send(data);
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
throw new Error("download image error");
|
|
84
|
+
}
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error("Proxy url failed: ", err);
|
|
87
|
+
res.status(500).send("Proxy url failed");
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
res.status(500).send('Parameter "url" is required');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ServerOptions } from '@tus/server';
|
|
2
|
+
export declare function initLocalStorageServer({ path: _path, onUploadFinish: _onUploadFinish, onUploadCreate: _onUploadCreate, express, expiredUploadTime, // default 3 days expire
|
|
3
|
+
...restProps }: ServerOptions & {
|
|
4
|
+
path: string;
|
|
5
|
+
onUploadFinish?: Function;
|
|
6
|
+
onUploadCreate?: Function;
|
|
7
|
+
express: Function;
|
|
8
|
+
expiredUploadTime?: Number;
|
|
9
|
+
}): any;
|
|
10
|
+
export declare const getFileName: (req: any) => any;
|
|
11
|
+
export declare function getFileNameParam(req: any, res: any, { isRequired }?: {
|
|
12
|
+
isRequired: boolean;
|
|
13
|
+
}): any;
|
|
14
|
+
export declare function getLocalStorageFile({ server }: any): (req: any, res: any, next: any) => Promise<void>;
|
|
15
|
+
export declare function setHeaders(req: any, res: any, next?: Function): void;
|
|
16
|
+
export declare function fileExistBeforeUpload(req: any, res: any, next?: Function): Promise<void>;
|
|
17
|
+
export declare function getMetaDataByFilePath(filePath: string): Promise<any>;
|
|
18
|
+
export declare function joinUrl(...args: string[]): any;
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
const { Server, EVENTS } = require("@tus/server");
|
|
2
|
+
const { FileStore } = require("@tus/file-store");
|
|
3
|
+
const cron = require("@abtnode/cron");
|
|
4
|
+
const fs = require("fs").promises;
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const crypto = require("crypto");
|
|
7
|
+
const mime = require("mime-types");
|
|
8
|
+
const joinUrlLib = require("url-join");
|
|
9
|
+
const { default: queue } = require("p-queue");
|
|
10
|
+
const validFilePathInDirPath = (dirPath, filePath) => {
|
|
11
|
+
const fileName = path.basename(filePath);
|
|
12
|
+
if (!filePath.startsWith(dirPath) || path.join(dirPath, fileName) !== filePath) {
|
|
13
|
+
console.error("Invalid file path: ", filePath);
|
|
14
|
+
throw new Error("Invalid file path");
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
};
|
|
18
|
+
export function initLocalStorageServer({
|
|
19
|
+
path: _path,
|
|
20
|
+
onUploadFinish: _onUploadFinish,
|
|
21
|
+
onUploadCreate: _onUploadCreate,
|
|
22
|
+
express,
|
|
23
|
+
expiredUploadTime = 1e3 * 60 * 60 * 24 * 3,
|
|
24
|
+
// default 3 days expire
|
|
25
|
+
...restProps
|
|
26
|
+
}) {
|
|
27
|
+
const app = express();
|
|
28
|
+
const configstore = new RewriteFileConfigstore(_path);
|
|
29
|
+
const datastore = new RewriteFileStore({
|
|
30
|
+
directory: _path,
|
|
31
|
+
expirationPeriodInMilliseconds: expiredUploadTime,
|
|
32
|
+
configstore
|
|
33
|
+
});
|
|
34
|
+
const formatMetadata = (uploadMetadata) => {
|
|
35
|
+
const cloneUploadMetadata = {
|
|
36
|
+
...uploadMetadata
|
|
37
|
+
};
|
|
38
|
+
if (cloneUploadMetadata.metadata?.name?.indexOf("/") > -1 && cloneUploadMetadata.metadata?.relativePath?.indexOf("/") > -1) {
|
|
39
|
+
cloneUploadMetadata.metadata.name = cloneUploadMetadata.metadata.name.split("/").pop();
|
|
40
|
+
cloneUploadMetadata.metadata.filename = cloneUploadMetadata.metadata.name;
|
|
41
|
+
}
|
|
42
|
+
if (cloneUploadMetadata.id && !cloneUploadMetadata.runtime) {
|
|
43
|
+
const { id, metadata, size } = cloneUploadMetadata;
|
|
44
|
+
cloneUploadMetadata.runtime = {
|
|
45
|
+
relativePath: metadata?.relativePath,
|
|
46
|
+
absolutePath: path.join(_path, id),
|
|
47
|
+
size,
|
|
48
|
+
hashFileName: id,
|
|
49
|
+
originFileName: metadata?.filename,
|
|
50
|
+
type: metadata?.type,
|
|
51
|
+
fileType: metadata?.filetype
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return cloneUploadMetadata;
|
|
55
|
+
};
|
|
56
|
+
const rewriteMetaDataFile = async (uploadMetadata) => {
|
|
57
|
+
uploadMetadata = formatMetadata(uploadMetadata);
|
|
58
|
+
const { id } = uploadMetadata;
|
|
59
|
+
if (!id) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const oldMetadata = formatMetadata(await configstore.get(id));
|
|
63
|
+
if (JSON.stringify(oldMetadata) !== JSON.stringify(uploadMetadata)) {
|
|
64
|
+
await configstore.set(id, uploadMetadata);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const onUploadCreate = async (req, res, uploadMetadata) => {
|
|
68
|
+
uploadMetadata = formatMetadata(uploadMetadata);
|
|
69
|
+
await rewriteMetaDataFile(uploadMetadata);
|
|
70
|
+
if (uploadMetadata.offset === 0 && uploadMetadata.size === 0) {
|
|
71
|
+
res.status(200);
|
|
72
|
+
res.setHeader("Location", joinUrl(req.headers["x-uploader-base-url"], uploadMetadata.id));
|
|
73
|
+
res.setHeader("Upload-Offset", 0);
|
|
74
|
+
res.setHeader("Upload-Length", 0);
|
|
75
|
+
res.setHeader("x-uploader-file-exist", true);
|
|
76
|
+
}
|
|
77
|
+
if (_onUploadCreate) {
|
|
78
|
+
const result = await _onUploadCreate(req, res, uploadMetadata);
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
return res;
|
|
82
|
+
};
|
|
83
|
+
const onUploadFinish = async (req, res, uploadMetadata) => {
|
|
84
|
+
res.setHeader("x-uploader-file-exist", true);
|
|
85
|
+
uploadMetadata = formatMetadata(uploadMetadata);
|
|
86
|
+
await rewriteMetaDataFile(uploadMetadata);
|
|
87
|
+
if (_onUploadFinish) {
|
|
88
|
+
try {
|
|
89
|
+
const result = await _onUploadFinish(req, res, uploadMetadata);
|
|
90
|
+
return result;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.error("@blocklet/uploader: onUploadFinish error: ", err);
|
|
93
|
+
newServer.delete(uploadMetadata.id);
|
|
94
|
+
res.setHeader("x-uploader-file-exist", false);
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return res;
|
|
99
|
+
};
|
|
100
|
+
const newServer = new Server({
|
|
101
|
+
path: "/",
|
|
102
|
+
// UNUSED
|
|
103
|
+
relativeLocation: true,
|
|
104
|
+
// respectForwardedHeaders: true,
|
|
105
|
+
namingFunction: (req) => {
|
|
106
|
+
const fileName = getFileName(req);
|
|
107
|
+
const filePath = path.join(_path, fileName);
|
|
108
|
+
validFilePathInDirPath(_path, filePath);
|
|
109
|
+
return fileName;
|
|
110
|
+
},
|
|
111
|
+
datastore,
|
|
112
|
+
onUploadFinish: async (req, res, uploadMetadata) => {
|
|
113
|
+
uploadMetadata = formatMetadata(uploadMetadata);
|
|
114
|
+
const result = await onUploadFinish(req, res, uploadMetadata);
|
|
115
|
+
if (result && !result.send) {
|
|
116
|
+
const body = typeof result === "string" ? result : JSON.stringify(result);
|
|
117
|
+
throw { body, status_code: 200 };
|
|
118
|
+
} else {
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
onUploadCreate,
|
|
123
|
+
...restProps
|
|
124
|
+
});
|
|
125
|
+
app.use((req, res, next) => {
|
|
126
|
+
req.uploaderProps = {
|
|
127
|
+
server: newServer,
|
|
128
|
+
onUploadFinish,
|
|
129
|
+
onUploadCreate
|
|
130
|
+
};
|
|
131
|
+
next();
|
|
132
|
+
});
|
|
133
|
+
cron.init({
|
|
134
|
+
context: {},
|
|
135
|
+
jobs: [
|
|
136
|
+
{
|
|
137
|
+
name: "auto-cleanup-expired-uploads",
|
|
138
|
+
time: "0 0 * * * *",
|
|
139
|
+
// each hour
|
|
140
|
+
fn: () => {
|
|
141
|
+
try {
|
|
142
|
+
newServer.cleanUpExpiredUploads().then((count) => {
|
|
143
|
+
console.info(`@blocklet/uploader: cleanup expired uploads done: ${count}`);
|
|
144
|
+
}).catch((err) => {
|
|
145
|
+
console.error(`@blocklet/uploader: cleanup expired uploads error`, err);
|
|
146
|
+
});
|
|
147
|
+
} catch (err) {
|
|
148
|
+
console.error(`@blocklet/uploader: cleanup expired uploads error`, err);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
options: { runOnInit: false }
|
|
152
|
+
}
|
|
153
|
+
],
|
|
154
|
+
onError: (err) => {
|
|
155
|
+
console.error("@blocklet/uploader: cleanup job failed", err);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
newServer.delete = async (key) => {
|
|
159
|
+
try {
|
|
160
|
+
await configstore.delete(key);
|
|
161
|
+
await configstore.delete(key, false);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
console.error("@blocklet/uploader: delete error: ", err);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
newServer.on(EVENTS.POST_RECEIVE, async (req, res, uploadMetadata) => {
|
|
167
|
+
uploadMetadata = formatMetadata(uploadMetadata);
|
|
168
|
+
await rewriteMetaDataFile(uploadMetadata);
|
|
169
|
+
});
|
|
170
|
+
app.all("*", setHeaders, fileExistBeforeUpload, newServer.handle.bind(newServer));
|
|
171
|
+
newServer.handle = app;
|
|
172
|
+
return newServer;
|
|
173
|
+
}
|
|
174
|
+
export const getFileName = (req) => {
|
|
175
|
+
const ext = req.headers["x-uploader-file-ext"];
|
|
176
|
+
const randomName = `${crypto.randomBytes(16).toString("hex")}${ext ? `.${ext}` : ""}`;
|
|
177
|
+
return req.headers["x-uploader-file-name"] || randomName;
|
|
178
|
+
};
|
|
179
|
+
export function getFileNameParam(req, res, { isRequired = true } = {}) {
|
|
180
|
+
let { fileName } = req.params;
|
|
181
|
+
if (!fileName) {
|
|
182
|
+
fileName = req.originalUrl.replace(req.baseUrl, "");
|
|
183
|
+
}
|
|
184
|
+
if (!fileName && isRequired) {
|
|
185
|
+
res.status(400).json({ error: 'Parameter "fileName" is required' });
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
return fileName;
|
|
189
|
+
}
|
|
190
|
+
export function getLocalStorageFile({ server }) {
|
|
191
|
+
return async (req, res, next) => {
|
|
192
|
+
const fileName = getFileNameParam(req, res);
|
|
193
|
+
const filePath = path.join(server.datastore.directory, fileName);
|
|
194
|
+
validFilePathInDirPath(server.datastore.directory, filePath);
|
|
195
|
+
const fileExists = await fs.stat(filePath).catch(() => false);
|
|
196
|
+
if (!fileExists) {
|
|
197
|
+
res.status(404).json({ error: "file not found" });
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
setHeaders(req, res);
|
|
201
|
+
const file = await fs.readFile(filePath);
|
|
202
|
+
res.send(file);
|
|
203
|
+
next?.();
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
export function setHeaders(req, res, next) {
|
|
207
|
+
let { method } = req;
|
|
208
|
+
method = method.toUpperCase();
|
|
209
|
+
const fileName = getFileNameParam(req, res, {
|
|
210
|
+
isRequired: false
|
|
211
|
+
});
|
|
212
|
+
if (req.headers["x-uploader-endpoint-url"]) {
|
|
213
|
+
const query = new URL(req.headers["x-uploader-endpoint-url"]).searchParams;
|
|
214
|
+
req.query = {
|
|
215
|
+
...Object.fromEntries(query),
|
|
216
|
+
// query params convert to object
|
|
217
|
+
...req.query
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
if (method === "POST" && req.headers["x-uploader-base-url"]) {
|
|
221
|
+
req.baseUrl = req.headers["x-uploader-base-url"];
|
|
222
|
+
}
|
|
223
|
+
if (method === "GET" && fileName) {
|
|
224
|
+
const contentType = mime.lookup(fileName);
|
|
225
|
+
if (contentType) {
|
|
226
|
+
res.setHeader("Content-Type", contentType);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
next?.();
|
|
230
|
+
}
|
|
231
|
+
export async function fileExistBeforeUpload(req, res, next) {
|
|
232
|
+
let { method, uploaderProps } = req;
|
|
233
|
+
method = method.toUpperCase();
|
|
234
|
+
if (["PATCH", "POST"].includes(method)) {
|
|
235
|
+
const _path = uploaderProps.server.datastore.directory;
|
|
236
|
+
const fileName = getFileName(req);
|
|
237
|
+
const filePath = path.join(_path, fileName);
|
|
238
|
+
validFilePathInDirPath(_path, filePath);
|
|
239
|
+
const isExist = await fs.stat(filePath).catch(() => false);
|
|
240
|
+
if (isExist) {
|
|
241
|
+
const metaData = await getMetaDataByFilePath(filePath);
|
|
242
|
+
if (isExist?.size >= 0 && isExist?.size === metaData?.size) {
|
|
243
|
+
const prepareUpload = method === "POST";
|
|
244
|
+
if (prepareUpload) {
|
|
245
|
+
res.status(200);
|
|
246
|
+
res.setHeader("Location", joinUrl(req.headers["x-uploader-base-url"], fileName));
|
|
247
|
+
res.setHeader("Upload-Offset", +metaData.offset);
|
|
248
|
+
res.setHeader("Upload-Length", +metaData.size);
|
|
249
|
+
}
|
|
250
|
+
if (req.headers["x-uploader-metadata"]) {
|
|
251
|
+
try {
|
|
252
|
+
const realMetaData = JSON.parse(req.headers["x-uploader-metadata"], (key, value) => {
|
|
253
|
+
if (typeof value === "string") {
|
|
254
|
+
return decodeURIComponent(value);
|
|
255
|
+
}
|
|
256
|
+
return value;
|
|
257
|
+
});
|
|
258
|
+
metaData.metadata = {
|
|
259
|
+
...metaData.metadata,
|
|
260
|
+
...realMetaData
|
|
261
|
+
};
|
|
262
|
+
} catch (err) {
|
|
263
|
+
console.error("@blocklet/uploader: parse metadata error: ", err);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const uploadResult = await uploaderProps.onUploadFinish(req, res, metaData);
|
|
267
|
+
res.json(uploadResult);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
next?.();
|
|
273
|
+
}
|
|
274
|
+
export async function getMetaDataByFilePath(filePath) {
|
|
275
|
+
const metaDataPath = `${filePath}.json`;
|
|
276
|
+
const isExist = await fs.stat(filePath).catch(() => false);
|
|
277
|
+
if (isExist) {
|
|
278
|
+
try {
|
|
279
|
+
const metaData = await fs.readFile(metaDataPath, "utf-8");
|
|
280
|
+
const metaDataJson = JSON.parse(metaData);
|
|
281
|
+
return metaDataJson;
|
|
282
|
+
} catch (err) {
|
|
283
|
+
console.error("@blocklet/uploader: getMetaDataByPath error: ", err);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
export function joinUrl(...args) {
|
|
289
|
+
const realArgs = args.filter(Boolean).map((item) => {
|
|
290
|
+
if (item === "/") {
|
|
291
|
+
return "";
|
|
292
|
+
}
|
|
293
|
+
return item;
|
|
294
|
+
});
|
|
295
|
+
return joinUrlLib(...realArgs);
|
|
296
|
+
}
|
|
297
|
+
class RewriteFileConfigstore {
|
|
298
|
+
directory;
|
|
299
|
+
queue;
|
|
300
|
+
constructor(path2) {
|
|
301
|
+
this.directory = path2;
|
|
302
|
+
this.queue = new queue({ concurrency: 1 });
|
|
303
|
+
}
|
|
304
|
+
async get(key) {
|
|
305
|
+
try {
|
|
306
|
+
const buffer = await this.queue.add(() => fs.readFile(this.resolve(key), "utf8"));
|
|
307
|
+
const metadata = JSON.parse(buffer);
|
|
308
|
+
if (metadata.offset !== metadata.size) {
|
|
309
|
+
const info = await fs.stat(this.resolve(key, false)).catch(() => false);
|
|
310
|
+
if (info?.size !== metadata?.offset) {
|
|
311
|
+
metadata.offset = info.size;
|
|
312
|
+
this.set(key, metadata);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return metadata;
|
|
316
|
+
} catch {
|
|
317
|
+
return void 0;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async set(key, value) {
|
|
321
|
+
if (value?.runtime) {
|
|
322
|
+
delete value.runtime;
|
|
323
|
+
}
|
|
324
|
+
if (value?.metadata?.runtime) {
|
|
325
|
+
delete value.metadata.runtime;
|
|
326
|
+
}
|
|
327
|
+
await this.queue.add(() => fs.writeFile(this.resolve(key), JSON.stringify(value)));
|
|
328
|
+
}
|
|
329
|
+
async safeDeleteFile(filePath) {
|
|
330
|
+
validFilePathInDirPath(this.directory, filePath);
|
|
331
|
+
try {
|
|
332
|
+
const isExist = await fs.stat(filePath).catch(() => false);
|
|
333
|
+
if (isExist) {
|
|
334
|
+
await fs.rm(filePath);
|
|
335
|
+
} else {
|
|
336
|
+
console.log("Can not remove file, the file not exist: ", filePath);
|
|
337
|
+
}
|
|
338
|
+
} catch (err) {
|
|
339
|
+
console.error("@blocklet/uploader: safeDeleteFile error: ", err);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
async delete(key, isMetadata = true) {
|
|
343
|
+
try {
|
|
344
|
+
await this.queue.add(() => this.safeDeleteFile(this.resolve(key, isMetadata)));
|
|
345
|
+
} catch (err) {
|
|
346
|
+
console.error("@blocklet/uploader: delete error: ", err);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
async list() {
|
|
350
|
+
return this.queue.add(async () => {
|
|
351
|
+
const files = await fs.readdir(this.directory, { withFileTypes: true });
|
|
352
|
+
const promises = files.filter((file) => file.isFile() && file.name.endsWith(".json")).map((file) => {
|
|
353
|
+
return file.name.replace(".json", "");
|
|
354
|
+
});
|
|
355
|
+
return Promise.all(promises);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
resolve(key, isMetadata = true) {
|
|
359
|
+
let fileKey = key;
|
|
360
|
+
if (isMetadata) {
|
|
361
|
+
fileKey = `${key}.json`;
|
|
362
|
+
}
|
|
363
|
+
return path.join(this.directory, fileKey);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
class RewriteFileStore extends FileStore {
|
|
367
|
+
constructor(options) {
|
|
368
|
+
super(options);
|
|
369
|
+
}
|
|
370
|
+
async remove(key) {
|
|
371
|
+
this.configstore.delete(key);
|
|
372
|
+
this.configstore.delete(key, false);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const mappingResource: () => Promise<any>;
|
|
2
|
+
type initStaticResourceMiddlewareOptions = {
|
|
3
|
+
options?: any;
|
|
4
|
+
resourceTypes?: string[] | Object[];
|
|
5
|
+
express: any;
|
|
6
|
+
skipRunningCheck?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare const initStaticResourceMiddleware: ({ options, resourceTypes: _resourceTypes, express, skipRunningCheck: _skipRunningCheck, }?: initStaticResourceMiddlewareOptions) => (req: any, res: any, next: Function) => void;
|
|
9
|
+
export declare const getCanUseResources: () => any;
|
|
10
|
+
export declare const initProxyToMediaKitUploadsMiddleware: ({ options, express }?: any) => (req: any, res: any, next: Function) => any;
|
|
11
|
+
export {};
|