@edgestore/react 0.6.0-canary.2 → 0.6.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/contextProvider.js +152 -0
- package/dist/contextProvider.mjs +131 -0
- package/dist/createNextProxy.js +322 -0
- package/dist/createNextProxy.mjs +320 -0
- package/dist/errors/index.js +2 -4
- package/dist/errors/index.mjs +1 -1
- package/dist/index.js +2 -483
- package/dist/index.mjs +1 -464
- package/dist/libs/errors/EdgeStoreClientError.js +10 -0
- package/dist/libs/errors/EdgeStoreClientError.mjs +8 -0
- package/dist/libs/errors/handleError.js +18 -0
- package/dist/libs/errors/handleError.mjs +16 -0
- package/dist/{uploadAbortedError-e1379bb0.mjs → libs/errors/uploadAbortedError.mjs} +1 -1
- package/dist/shared/index.js +0 -2
- package/dist/utils/index.js +0 -2
- package/package.json +25 -20
- package/dist/uploadAbortedError-a628b025.js +0 -8
- /package/dist/{uploadAbortedError-fbfcc57b.js → libs/errors/uploadAbortedError.js} +0 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React = require('react');
|
|
4
|
+
var createNextProxy = require('./createNextProxy.js');
|
|
5
|
+
var EdgeStoreClientError = require('./libs/errors/EdgeStoreClientError.js');
|
|
6
|
+
var handleError = require('./libs/errors/handleError.js');
|
|
7
|
+
|
|
8
|
+
function _interopNamespaceDefault(e) {
|
|
9
|
+
var n = Object.create(null);
|
|
10
|
+
if (e) {
|
|
11
|
+
Object.keys(e).forEach(function (k) {
|
|
12
|
+
if (k !== 'default') {
|
|
13
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
14
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () { return e[k]; }
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
n.default = e;
|
|
22
|
+
return Object.freeze(n);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
|
|
26
|
+
|
|
27
|
+
const DEFAULT_BASE_URL = (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_EDGE_STORE_BASE_URL : undefined?.EDGE_STORE_BASE_URL) ?? 'https://files.edgestore.dev';
|
|
28
|
+
function createEdgeStoreProvider(opts) {
|
|
29
|
+
const EdgeStoreContext = /*#__PURE__*/ React__namespace.createContext(undefined);
|
|
30
|
+
const EdgeStoreProvider = ({ children, basePath })=>{
|
|
31
|
+
return EdgeStoreProviderInner({
|
|
32
|
+
children,
|
|
33
|
+
context: EdgeStoreContext,
|
|
34
|
+
basePath,
|
|
35
|
+
maxConcurrentUploads: opts?.maxConcurrentUploads,
|
|
36
|
+
disableDevProxy: opts?.disableDevProxy
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
function useEdgeStore() {
|
|
40
|
+
if (!EdgeStoreContext) {
|
|
41
|
+
throw new Error('React Context is unavailable in Server Components');
|
|
42
|
+
}
|
|
43
|
+
// @ts-expect-error - We know that the context value should not be undefined
|
|
44
|
+
const value = React__namespace.useContext(EdgeStoreContext);
|
|
45
|
+
if (!value && process.env.NODE_ENV !== 'production') {
|
|
46
|
+
throw new Error('[edgestore]: `useEdgeStore` must be wrapped in a <EdgeStoreProvider />');
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
EdgeStoreProvider,
|
|
52
|
+
useEdgeStore
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function EdgeStoreProviderInner({ children, context, basePath, maxConcurrentUploads, disableDevProxy }) {
|
|
56
|
+
const apiPath = basePath ? `${basePath}` : '/api/edgestore';
|
|
57
|
+
const [state, setState] = React__namespace.useState({
|
|
58
|
+
loading: true,
|
|
59
|
+
initialized: false,
|
|
60
|
+
error: false
|
|
61
|
+
});
|
|
62
|
+
const uploadingCountRef = React__namespace.useRef(0);
|
|
63
|
+
const initExecuted = React__namespace.useRef(false); // to make sure we don't run init twice
|
|
64
|
+
React__namespace.useEffect(()=>{
|
|
65
|
+
if (!initExecuted.current) {
|
|
66
|
+
void init();
|
|
67
|
+
}
|
|
68
|
+
return ()=>{
|
|
69
|
+
initExecuted.current = true;
|
|
70
|
+
};
|
|
71
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
72
|
+
}, []);
|
|
73
|
+
async function init() {
|
|
74
|
+
try {
|
|
75
|
+
setState({
|
|
76
|
+
loading: true,
|
|
77
|
+
initialized: false,
|
|
78
|
+
error: false
|
|
79
|
+
});
|
|
80
|
+
const res = await fetch(`${apiPath}/init`, {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
credentials: 'include'
|
|
83
|
+
});
|
|
84
|
+
if (res.ok) {
|
|
85
|
+
const json = await res.json();
|
|
86
|
+
// Only call _init API if provider is edgestore
|
|
87
|
+
if (json.providerName === 'edgestore') {
|
|
88
|
+
const innerRes = await fetch(`${DEFAULT_BASE_URL}/_init`, {
|
|
89
|
+
method: 'GET',
|
|
90
|
+
credentials: 'include',
|
|
91
|
+
headers: {
|
|
92
|
+
'x-edgestore-token': json.token
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
if (innerRes.ok) {
|
|
96
|
+
// update state
|
|
97
|
+
setState({
|
|
98
|
+
loading: false,
|
|
99
|
+
initialized: true,
|
|
100
|
+
error: false
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
setState({
|
|
104
|
+
loading: false,
|
|
105
|
+
initialized: false,
|
|
106
|
+
error: true
|
|
107
|
+
});
|
|
108
|
+
throw new EdgeStoreClientError("Couldn't initialize EdgeStore.");
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
// For non-edgestore providers, just update state without calling _init
|
|
112
|
+
setState({
|
|
113
|
+
loading: false,
|
|
114
|
+
initialized: true,
|
|
115
|
+
error: false
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
setState({
|
|
120
|
+
loading: false,
|
|
121
|
+
initialized: false,
|
|
122
|
+
error: true
|
|
123
|
+
});
|
|
124
|
+
await handleError.handleError(res);
|
|
125
|
+
}
|
|
126
|
+
} catch (err) {
|
|
127
|
+
setState({
|
|
128
|
+
loading: false,
|
|
129
|
+
initialized: false,
|
|
130
|
+
error: true
|
|
131
|
+
});
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function reset() {
|
|
136
|
+
await init();
|
|
137
|
+
}
|
|
138
|
+
return /*#__PURE__*/ React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/ React__namespace.createElement(context.Provider, {
|
|
139
|
+
value: {
|
|
140
|
+
edgestore: createNextProxy.createNextProxy({
|
|
141
|
+
apiPath,
|
|
142
|
+
uploadingCountRef,
|
|
143
|
+
maxConcurrentUploads,
|
|
144
|
+
disableDevProxy
|
|
145
|
+
}),
|
|
146
|
+
reset,
|
|
147
|
+
state
|
|
148
|
+
}
|
|
149
|
+
}, children));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
exports.createEdgeStoreProvider = createEdgeStoreProvider;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createNextProxy } from './createNextProxy.mjs';
|
|
3
|
+
import EdgeStoreClientError from './libs/errors/EdgeStoreClientError.mjs';
|
|
4
|
+
import { handleError } from './libs/errors/handleError.mjs';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_BASE_URL = (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_EDGE_STORE_BASE_URL : import.meta.env?.EDGE_STORE_BASE_URL) ?? 'https://files.edgestore.dev';
|
|
7
|
+
function createEdgeStoreProvider(opts) {
|
|
8
|
+
const EdgeStoreContext = /*#__PURE__*/ React.createContext(undefined);
|
|
9
|
+
const EdgeStoreProvider = ({ children, basePath })=>{
|
|
10
|
+
return EdgeStoreProviderInner({
|
|
11
|
+
children,
|
|
12
|
+
context: EdgeStoreContext,
|
|
13
|
+
basePath,
|
|
14
|
+
maxConcurrentUploads: opts?.maxConcurrentUploads,
|
|
15
|
+
disableDevProxy: opts?.disableDevProxy
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
function useEdgeStore() {
|
|
19
|
+
if (!EdgeStoreContext) {
|
|
20
|
+
throw new Error('React Context is unavailable in Server Components');
|
|
21
|
+
}
|
|
22
|
+
// @ts-expect-error - We know that the context value should not be undefined
|
|
23
|
+
const value = React.useContext(EdgeStoreContext);
|
|
24
|
+
if (!value && process.env.NODE_ENV !== 'production') {
|
|
25
|
+
throw new Error('[edgestore]: `useEdgeStore` must be wrapped in a <EdgeStoreProvider />');
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
EdgeStoreProvider,
|
|
31
|
+
useEdgeStore
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function EdgeStoreProviderInner({ children, context, basePath, maxConcurrentUploads, disableDevProxy }) {
|
|
35
|
+
const apiPath = basePath ? `${basePath}` : '/api/edgestore';
|
|
36
|
+
const [state, setState] = React.useState({
|
|
37
|
+
loading: true,
|
|
38
|
+
initialized: false,
|
|
39
|
+
error: false
|
|
40
|
+
});
|
|
41
|
+
const uploadingCountRef = React.useRef(0);
|
|
42
|
+
const initExecuted = React.useRef(false); // to make sure we don't run init twice
|
|
43
|
+
React.useEffect(()=>{
|
|
44
|
+
if (!initExecuted.current) {
|
|
45
|
+
void init();
|
|
46
|
+
}
|
|
47
|
+
return ()=>{
|
|
48
|
+
initExecuted.current = true;
|
|
49
|
+
};
|
|
50
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
51
|
+
}, []);
|
|
52
|
+
async function init() {
|
|
53
|
+
try {
|
|
54
|
+
setState({
|
|
55
|
+
loading: true,
|
|
56
|
+
initialized: false,
|
|
57
|
+
error: false
|
|
58
|
+
});
|
|
59
|
+
const res = await fetch(`${apiPath}/init`, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
credentials: 'include'
|
|
62
|
+
});
|
|
63
|
+
if (res.ok) {
|
|
64
|
+
const json = await res.json();
|
|
65
|
+
// Only call _init API if provider is edgestore
|
|
66
|
+
if (json.providerName === 'edgestore') {
|
|
67
|
+
const innerRes = await fetch(`${DEFAULT_BASE_URL}/_init`, {
|
|
68
|
+
method: 'GET',
|
|
69
|
+
credentials: 'include',
|
|
70
|
+
headers: {
|
|
71
|
+
'x-edgestore-token': json.token
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
if (innerRes.ok) {
|
|
75
|
+
// update state
|
|
76
|
+
setState({
|
|
77
|
+
loading: false,
|
|
78
|
+
initialized: true,
|
|
79
|
+
error: false
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
setState({
|
|
83
|
+
loading: false,
|
|
84
|
+
initialized: false,
|
|
85
|
+
error: true
|
|
86
|
+
});
|
|
87
|
+
throw new EdgeStoreClientError("Couldn't initialize EdgeStore.");
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
// For non-edgestore providers, just update state without calling _init
|
|
91
|
+
setState({
|
|
92
|
+
loading: false,
|
|
93
|
+
initialized: true,
|
|
94
|
+
error: false
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
setState({
|
|
99
|
+
loading: false,
|
|
100
|
+
initialized: false,
|
|
101
|
+
error: true
|
|
102
|
+
});
|
|
103
|
+
await handleError(res);
|
|
104
|
+
}
|
|
105
|
+
} catch (err) {
|
|
106
|
+
setState({
|
|
107
|
+
loading: false,
|
|
108
|
+
initialized: false,
|
|
109
|
+
error: true
|
|
110
|
+
});
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function reset() {
|
|
115
|
+
await init();
|
|
116
|
+
}
|
|
117
|
+
return /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement(context.Provider, {
|
|
118
|
+
value: {
|
|
119
|
+
edgestore: createNextProxy({
|
|
120
|
+
apiPath,
|
|
121
|
+
uploadingCountRef,
|
|
122
|
+
maxConcurrentUploads,
|
|
123
|
+
disableDevProxy
|
|
124
|
+
}),
|
|
125
|
+
reset,
|
|
126
|
+
state
|
|
127
|
+
}
|
|
128
|
+
}, children));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export { createEdgeStoreProvider };
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var EdgeStoreClientError = require('./libs/errors/EdgeStoreClientError.js');
|
|
4
|
+
var handleError = require('./libs/errors/handleError.js');
|
|
5
|
+
var uploadAbortedError = require('./libs/errors/uploadAbortedError.js');
|
|
6
|
+
|
|
7
|
+
function createNextProxy({ apiPath, uploadingCountRef, maxConcurrentUploads = 5, disableDevProxy }) {
|
|
8
|
+
return new Proxy({}, {
|
|
9
|
+
get (_, prop) {
|
|
10
|
+
const bucketName = prop;
|
|
11
|
+
const bucketFunctions = {
|
|
12
|
+
upload: async (params)=>{
|
|
13
|
+
try {
|
|
14
|
+
params.onProgressChange?.(0);
|
|
15
|
+
// This handles the case where the user cancels the upload while it's waiting in the queue
|
|
16
|
+
const abortPromise = new Promise((resolve)=>{
|
|
17
|
+
params.signal?.addEventListener('abort', ()=>{
|
|
18
|
+
resolve();
|
|
19
|
+
}, {
|
|
20
|
+
once: true
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
while(uploadingCountRef.current >= maxConcurrentUploads && uploadingCountRef.current > 0){
|
|
24
|
+
await Promise.race([
|
|
25
|
+
new Promise((resolve)=>setTimeout(resolve, 300)),
|
|
26
|
+
abortPromise
|
|
27
|
+
]);
|
|
28
|
+
if (params.signal?.aborted) {
|
|
29
|
+
throw new uploadAbortedError.UploadAbortedError('File upload aborted');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
uploadingCountRef.current++;
|
|
33
|
+
const fileInfo = await uploadFile(params, {
|
|
34
|
+
bucketName: bucketName,
|
|
35
|
+
apiPath
|
|
36
|
+
}, disableDevProxy);
|
|
37
|
+
return fileInfo;
|
|
38
|
+
} finally{
|
|
39
|
+
uploadingCountRef.current--;
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
confirmUpload: async (params)=>{
|
|
43
|
+
const { success } = await confirmUpload(params, {
|
|
44
|
+
bucketName: bucketName,
|
|
45
|
+
apiPath
|
|
46
|
+
});
|
|
47
|
+
if (!success) {
|
|
48
|
+
throw new EdgeStoreClientError('Failed to confirm upload');
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
delete: async (params)=>{
|
|
52
|
+
const { success } = await deleteFile(params, {
|
|
53
|
+
bucketName: bucketName,
|
|
54
|
+
apiPath
|
|
55
|
+
});
|
|
56
|
+
if (!success) {
|
|
57
|
+
throw new EdgeStoreClientError('Failed to delete file');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
return bucketFunctions;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async function uploadFile({ file, signal, input, onProgressChange, options }, { apiPath, bucketName }, disableDevProxy) {
|
|
66
|
+
try {
|
|
67
|
+
onProgressChange?.(0);
|
|
68
|
+
const res = await fetch(`${apiPath}/request-upload`, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
credentials: 'include',
|
|
71
|
+
signal: signal,
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
bucketName,
|
|
74
|
+
input,
|
|
75
|
+
fileInfo: {
|
|
76
|
+
extension: file.name.split('.').pop(),
|
|
77
|
+
type: file.type,
|
|
78
|
+
size: file.size,
|
|
79
|
+
fileName: options?.manualFileName,
|
|
80
|
+
replaceTargetUrl: options?.replaceTargetUrl,
|
|
81
|
+
temporary: options?.temporary
|
|
82
|
+
}
|
|
83
|
+
}),
|
|
84
|
+
headers: {
|
|
85
|
+
'Content-Type': 'application/json'
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
if (!res.ok) {
|
|
89
|
+
await handleError.handleError(res);
|
|
90
|
+
}
|
|
91
|
+
const json = await res.json();
|
|
92
|
+
if ('multipart' in json) {
|
|
93
|
+
await multipartUpload({
|
|
94
|
+
bucketName,
|
|
95
|
+
multipartInfo: json.multipart,
|
|
96
|
+
onProgressChange,
|
|
97
|
+
signal,
|
|
98
|
+
file,
|
|
99
|
+
apiPath
|
|
100
|
+
});
|
|
101
|
+
} else if ('uploadUrl' in json) {
|
|
102
|
+
// Single part upload
|
|
103
|
+
// Upload the file to the signed URL and get the progress
|
|
104
|
+
await uploadFileInner({
|
|
105
|
+
file,
|
|
106
|
+
uploadUrl: json.uploadUrl,
|
|
107
|
+
onProgressChange,
|
|
108
|
+
signal
|
|
109
|
+
});
|
|
110
|
+
} else {
|
|
111
|
+
throw new EdgeStoreClientError('An error occurred');
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
url: getUrl(json.accessUrl, apiPath, disableDevProxy),
|
|
115
|
+
thumbnailUrl: json.thumbnailUrl ? getUrl(json.thumbnailUrl, apiPath, disableDevProxy) : null,
|
|
116
|
+
size: json.size,
|
|
117
|
+
uploadedAt: new Date(json.uploadedAt),
|
|
118
|
+
path: json.path,
|
|
119
|
+
pathOrder: json.pathOrder,
|
|
120
|
+
metadata: json.metadata
|
|
121
|
+
};
|
|
122
|
+
} catch (e) {
|
|
123
|
+
if (e instanceof Error && e.name === 'AbortError') {
|
|
124
|
+
throw new uploadAbortedError.UploadAbortedError('File upload aborted');
|
|
125
|
+
}
|
|
126
|
+
onProgressChange?.(0);
|
|
127
|
+
throw e;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Protected files need third-party cookies to work.
|
|
132
|
+
* Since third party cookies don't work on localhost,
|
|
133
|
+
* we need to proxy the file through the server.
|
|
134
|
+
*/ function getUrl(url, apiPath, disableDevProxy) {
|
|
135
|
+
const mode = typeof process !== 'undefined' ? process.env.NODE_ENV : undefined?.DEV ? 'development' : 'production';
|
|
136
|
+
if (mode === 'development' && !url.includes('/_public/') && !disableDevProxy) {
|
|
137
|
+
const proxyUrl = new URL(window.location.origin);
|
|
138
|
+
proxyUrl.pathname = `${apiPath}/proxy-file`;
|
|
139
|
+
proxyUrl.search = new URLSearchParams({
|
|
140
|
+
url
|
|
141
|
+
}).toString();
|
|
142
|
+
return proxyUrl.toString();
|
|
143
|
+
}
|
|
144
|
+
return url;
|
|
145
|
+
}
|
|
146
|
+
async function uploadFileInner(props) {
|
|
147
|
+
const { file, uploadUrl, onProgressChange, signal } = props;
|
|
148
|
+
const promise = new Promise((resolve, reject)=>{
|
|
149
|
+
if (signal?.aborted) {
|
|
150
|
+
reject(new uploadAbortedError.UploadAbortedError('File upload aborted'));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const request = new XMLHttpRequest();
|
|
154
|
+
request.open('PUT', uploadUrl);
|
|
155
|
+
// This is for Azure provider. Specifies the blob type
|
|
156
|
+
request.setRequestHeader('x-ms-blob-type', 'BlockBlob');
|
|
157
|
+
request.addEventListener('loadstart', ()=>{
|
|
158
|
+
onProgressChange?.(0);
|
|
159
|
+
});
|
|
160
|
+
request.upload.addEventListener('progress', (e)=>{
|
|
161
|
+
if (e.lengthComputable) {
|
|
162
|
+
// 2 decimal progress
|
|
163
|
+
const progress = Math.round(e.loaded / e.total * 10000) / 100;
|
|
164
|
+
onProgressChange?.(progress);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
request.addEventListener('error', ()=>{
|
|
168
|
+
reject(new Error('Error uploading file'));
|
|
169
|
+
});
|
|
170
|
+
request.addEventListener('abort', ()=>{
|
|
171
|
+
reject(new uploadAbortedError.UploadAbortedError('File upload aborted'));
|
|
172
|
+
});
|
|
173
|
+
request.addEventListener('loadend', ()=>{
|
|
174
|
+
// Return the ETag header (needed to complete multipart upload)
|
|
175
|
+
resolve(request.getResponseHeader('ETag'));
|
|
176
|
+
});
|
|
177
|
+
if (signal) {
|
|
178
|
+
signal.addEventListener('abort', ()=>{
|
|
179
|
+
request.abort();
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
request.send(file);
|
|
183
|
+
});
|
|
184
|
+
return promise;
|
|
185
|
+
}
|
|
186
|
+
async function multipartUpload(params) {
|
|
187
|
+
const { bucketName, multipartInfo, onProgressChange, file, signal, apiPath } = params;
|
|
188
|
+
const { partSize, parts, totalParts, uploadId, key } = multipartInfo;
|
|
189
|
+
const uploadingParts = [];
|
|
190
|
+
const uploadPart = async (params)=>{
|
|
191
|
+
const { part, chunk } = params;
|
|
192
|
+
const { uploadUrl } = part;
|
|
193
|
+
const eTag = await uploadFileInner({
|
|
194
|
+
file: chunk,
|
|
195
|
+
uploadUrl,
|
|
196
|
+
signal,
|
|
197
|
+
onProgressChange: (progress)=>{
|
|
198
|
+
const uploadingPart = uploadingParts.find((p)=>p.partNumber === part.partNumber);
|
|
199
|
+
if (uploadingPart) {
|
|
200
|
+
uploadingPart.progress = progress;
|
|
201
|
+
} else {
|
|
202
|
+
uploadingParts.push({
|
|
203
|
+
partNumber: part.partNumber,
|
|
204
|
+
progress
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
const totalProgress = Math.round(uploadingParts.reduce((acc, p)=>acc + p.progress * 100, 0) / totalParts) / 100;
|
|
208
|
+
onProgressChange?.(totalProgress);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
if (!eTag) {
|
|
212
|
+
throw new EdgeStoreClientError('Could not get ETag from multipart response');
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
partNumber: part.partNumber,
|
|
216
|
+
eTag
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
// Upload the parts in parallel
|
|
220
|
+
const completedParts = await queuedPromises({
|
|
221
|
+
items: parts.map((part)=>({
|
|
222
|
+
part,
|
|
223
|
+
chunk: file.slice((part.partNumber - 1) * partSize, part.partNumber * partSize)
|
|
224
|
+
})),
|
|
225
|
+
fn: uploadPart,
|
|
226
|
+
maxParallel: 5,
|
|
227
|
+
maxRetries: 10
|
|
228
|
+
});
|
|
229
|
+
// Complete multipart upload
|
|
230
|
+
const res = await fetch(`${apiPath}/complete-multipart-upload`, {
|
|
231
|
+
method: 'POST',
|
|
232
|
+
credentials: 'include',
|
|
233
|
+
body: JSON.stringify({
|
|
234
|
+
bucketName,
|
|
235
|
+
uploadId,
|
|
236
|
+
key,
|
|
237
|
+
parts: completedParts
|
|
238
|
+
}),
|
|
239
|
+
headers: {
|
|
240
|
+
'Content-Type': 'application/json'
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
if (!res.ok) {
|
|
244
|
+
await handleError.handleError(res);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function confirmUpload({ url }, { apiPath, bucketName }) {
|
|
248
|
+
const res = await fetch(`${apiPath}/confirm-upload`, {
|
|
249
|
+
method: 'POST',
|
|
250
|
+
credentials: 'include',
|
|
251
|
+
body: JSON.stringify({
|
|
252
|
+
url,
|
|
253
|
+
bucketName
|
|
254
|
+
}),
|
|
255
|
+
headers: {
|
|
256
|
+
'Content-Type': 'application/json'
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
if (!res.ok) {
|
|
260
|
+
await handleError.handleError(res);
|
|
261
|
+
}
|
|
262
|
+
return res.json();
|
|
263
|
+
}
|
|
264
|
+
async function deleteFile({ url }, { apiPath, bucketName }) {
|
|
265
|
+
const res = await fetch(`${apiPath}/delete-file`, {
|
|
266
|
+
method: 'POST',
|
|
267
|
+
credentials: 'include',
|
|
268
|
+
body: JSON.stringify({
|
|
269
|
+
url,
|
|
270
|
+
bucketName
|
|
271
|
+
}),
|
|
272
|
+
headers: {
|
|
273
|
+
'Content-Type': 'application/json'
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
if (!res.ok) {
|
|
277
|
+
await handleError.handleError(res);
|
|
278
|
+
}
|
|
279
|
+
return res.json();
|
|
280
|
+
}
|
|
281
|
+
async function queuedPromises({ items, fn, maxParallel, maxRetries = 0 }) {
|
|
282
|
+
const results = new Array(items.length);
|
|
283
|
+
const executeWithRetry = async (func, retries)=>{
|
|
284
|
+
try {
|
|
285
|
+
return await func();
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (error instanceof uploadAbortedError.UploadAbortedError) {
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
if (retries > 0) {
|
|
291
|
+
await new Promise((resolve)=>setTimeout(resolve, 5000));
|
|
292
|
+
return executeWithRetry(func, retries - 1);
|
|
293
|
+
} else {
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
const semaphore = {
|
|
299
|
+
count: maxParallel,
|
|
300
|
+
async wait () {
|
|
301
|
+
// If we've reached our maximum concurrency, or it's the last item, wait
|
|
302
|
+
while(this.count <= 0)await new Promise((resolve)=>setTimeout(resolve, 500));
|
|
303
|
+
this.count--;
|
|
304
|
+
},
|
|
305
|
+
signal () {
|
|
306
|
+
this.count++;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
const tasks = items.map((item, i)=>(async ()=>{
|
|
310
|
+
await semaphore.wait();
|
|
311
|
+
try {
|
|
312
|
+
const result = await executeWithRetry(()=>fn(item), maxRetries);
|
|
313
|
+
results[i] = result;
|
|
314
|
+
} finally{
|
|
315
|
+
semaphore.signal();
|
|
316
|
+
}
|
|
317
|
+
})());
|
|
318
|
+
await Promise.all(tasks);
|
|
319
|
+
return results;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
exports.createNextProxy = createNextProxy;
|