@edgestore/react 0.0.0-alpha.12 → 0.0.0-alpha.14
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.d.ts.map +1 -1
- package/dist/createNextProxy.d.ts +28 -8
- package/dist/createNextProxy.d.ts.map +1 -1
- package/dist/index.js +23 -7
- package/dist/index.mjs +23 -7
- package/package.json +9 -7
- package/src/contextProvider.tsx +4 -4
- package/src/createNextProxy.ts +59 -17
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contextProvider.d.ts","sourceRoot":"","sources":["../src/contextProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAmB,MAAM,mBAAmB,CAAC;AAKrE,KAAK,qBAAqB,CAAC,OAAO,SAAS,SAAS,IAAI;IACtD,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;IACpC;;;OAGG;IACH,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CACjC,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,OAAO,SAAS,SAAS,EAAE,IAAI,CAAC,EAAE;IACxE;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;;
|
|
1
|
+
{"version":3,"file":"contextProvider.d.ts","sourceRoot":"","sources":["../src/contextProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAmB,MAAM,mBAAmB,CAAC;AAKrE,KAAK,qBAAqB,CAAC,OAAO,SAAS,SAAS,IAAI;IACtD,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;IACpC;;;OAGG;IACH,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CACjC,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,OAAO,SAAS,SAAS,EAAE,IAAI,CAAC,EAAE;IACxE;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;;kBASa,MAAM,SAAS;QACzB;;;;;;WAMG;;;;EAgCN"}
|
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import { AnyRouter,
|
|
2
|
+
import { AnyRouter, InferBucketPathObject, InferMetadataObject } from '@edgestore/server/core';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
export type BucketFunctions<TRouter extends AnyRouter> = {
|
|
5
5
|
[K in keyof TRouter['buckets']]: {
|
|
6
|
-
upload: (params: z.infer<TRouter['buckets'][K]['_def']['input']> extends
|
|
6
|
+
upload: (params: z.infer<TRouter['buckets'][K]['_def']['input']> extends never ? {
|
|
7
7
|
file: File;
|
|
8
|
-
input: z.infer<TRouter['buckets'][K]['_def']['input']>;
|
|
9
8
|
onProgressChange?: OnProgressChangeHandler;
|
|
10
9
|
options?: UploadOptions;
|
|
11
10
|
} : {
|
|
12
11
|
file: File;
|
|
12
|
+
input: z.infer<TRouter['buckets'][K]['_def']['input']>;
|
|
13
13
|
onProgressChange?: OnProgressChangeHandler;
|
|
14
14
|
options?: UploadOptions;
|
|
15
|
-
}) => Promise<{
|
|
15
|
+
}) => Promise<TRouter['buckets'][K]['_def']['type'] extends 'IMAGE' ? {
|
|
16
|
+
url: string;
|
|
17
|
+
thumbnailUrl: string | null;
|
|
18
|
+
size: number;
|
|
19
|
+
uploadedAt: Date;
|
|
20
|
+
metadata: InferMetadataObject<TRouter['buckets'][K]>;
|
|
21
|
+
path: InferBucketPathObject<TRouter['buckets'][K]>;
|
|
22
|
+
} : {
|
|
16
23
|
url: string;
|
|
17
|
-
thumbnailUrl: TRouter['buckets'][K]['_def']['type'] extends 'IMAGE' ? string | null : never;
|
|
18
24
|
size: number;
|
|
19
25
|
uploadedAt: Date;
|
|
20
26
|
metadata: InferMetadataObject<TRouter['buckets'][K]>;
|
|
21
|
-
path:
|
|
22
|
-
[TKey in InferBucketPathKeys<TRouter['buckets'][K]>]: string;
|
|
23
|
-
};
|
|
27
|
+
path: InferBucketPathObject<TRouter['buckets'][K]>;
|
|
24
28
|
}>;
|
|
25
29
|
delete: (params: {
|
|
26
30
|
url: string;
|
|
@@ -31,6 +35,22 @@ export type BucketFunctions<TRouter extends AnyRouter> = {
|
|
|
31
35
|
};
|
|
32
36
|
type OnProgressChangeHandler = (progress: number) => void;
|
|
33
37
|
type UploadOptions = {
|
|
38
|
+
/**
|
|
39
|
+
* e.g. 'my-file-name.jpg'
|
|
40
|
+
*
|
|
41
|
+
* By default, a unique file name will be generated for each upload.
|
|
42
|
+
* If you want to use a custom file name, you can use this option.
|
|
43
|
+
* If you use the same file name for multiple uploads, the previous file will be overwritten.
|
|
44
|
+
* But it might take some time for the CDN cache to be cleared.
|
|
45
|
+
* So maybe you will keep seeing the old file for a while.
|
|
46
|
+
*
|
|
47
|
+
* If you want to replace an existing file immediately leave the `manualFileName` option empty and use the `replaceTargetUrl` option.
|
|
48
|
+
*/
|
|
49
|
+
manualFileName?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Use this to replace an existing file.
|
|
52
|
+
* It will automatically delete the existing file when the upload is complete.
|
|
53
|
+
*/
|
|
34
54
|
replaceTargetUrl?: string;
|
|
35
55
|
};
|
|
36
56
|
export declare function createNextProxy<TRouter extends AnyRouter>({ apiPath, uploadingCountRef, maxConcurrentUploads, }: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createNextProxy.d.ts","sourceRoot":"","sources":["../src/createNextProxy.ts"],"names":[],"mappings":";AAAA,OAAO,EACL,SAAS,EACT,
|
|
1
|
+
{"version":3,"file":"createNextProxy.d.ts","sourceRoot":"","sources":["../src/createNextProxy.ts"],"names":[],"mappings":";AAAA,OAAO,EACL,SAAS,EACT,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,MAAM,eAAe,CAAC,OAAO,SAAS,SAAS,IAAI;KACtD,CAAC,IAAI,MAAM,OAAO,CAAC,SAAS,CAAC,GAAG;QAC/B,MAAM,EAAE,CACN,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,KAAK,GACjE;YACE,IAAI,EAAE,IAAI,CAAC;YACX,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;YAC3C,OAAO,CAAC,EAAE,aAAa,CAAC;SACzB,GACD;YACE,IAAI,EAAE,IAAI,CAAC;YACX,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;YACvD,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;YAC3C,OAAO,CAAC,EAAE,aAAa,CAAC;SACzB,KACF,OAAO,CACV,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,SAAS,OAAO,GACjD;YACE,GAAG,EAAE,MAAM,CAAC;YACZ,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;YAC5B,IAAI,EAAE,MAAM,CAAC;YACb,UAAU,EAAE,IAAI,CAAC;YACjB,QAAQ,EAAE,mBAAmB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACrD,IAAI,EAAE,qBAAqB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SACpD,GACD;YACE,GAAG,EAAE,MAAM,CAAC;YACZ,IAAI,EAAE,MAAM,CAAC;YACb,UAAU,EAAE,IAAI,CAAC;YACjB,QAAQ,EAAE,mBAAmB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACrD,IAAI,EAAE,qBAAqB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SACpD,CACN,CAAC;QACF,MAAM,EAAE,CAAC,MAAM,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC;YAC3C,OAAO,EAAE,OAAO,CAAC;SAClB,CAAC,CAAC;KACJ;CACF,CAAC;AAEF,KAAK,uBAAuB,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;AAE1D,KAAK,aAAa,GAAG;IACnB;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,wBAAgB,eAAe,CAAC,OAAO,SAAS,SAAS,EAAE,EACzD,OAAO,EACP,iBAAiB,EACjB,oBAAwB,GACzB,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAClD,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,4BAiCA"}
|
package/dist/index.js
CHANGED
|
@@ -74,6 +74,7 @@ async function uploadFile({ file, input, onProgressChange, options }, { apiPath,
|
|
|
74
74
|
extension: file.name.split('.').pop(),
|
|
75
75
|
type: file.type,
|
|
76
76
|
size: file.size,
|
|
77
|
+
fileName: options?.manualFileName,
|
|
77
78
|
replaceTargetUrl: options?.replaceTargetUrl
|
|
78
79
|
}
|
|
79
80
|
}),
|
|
@@ -88,8 +89,8 @@ async function uploadFile({ file, input, onProgressChange, options }, { apiPath,
|
|
|
88
89
|
// Upload the file to the signed URL and get the progress
|
|
89
90
|
await uploadFileInner(file, json.uploadUrl, onProgressChange);
|
|
90
91
|
return {
|
|
91
|
-
url: json.accessUrl,
|
|
92
|
-
thumbnailUrl: json.thumbnailUrl,
|
|
92
|
+
url: getUrl(json.accessUrl, apiPath),
|
|
93
|
+
thumbnailUrl: json.thumbnailUrl ? getUrl(json.thumbnailUrl, apiPath) : null,
|
|
93
94
|
size: json.size,
|
|
94
95
|
uploadedAt: new Date(json.uploadedAt),
|
|
95
96
|
path: json.path,
|
|
@@ -100,6 +101,21 @@ async function uploadFile({ file, input, onProgressChange, options }, { apiPath,
|
|
|
100
101
|
throw e;
|
|
101
102
|
}
|
|
102
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Protected files need third-party cookies to work.
|
|
106
|
+
* Since third party cookies doesn't work on localhost,
|
|
107
|
+
* we need to proxy the file through the server.
|
|
108
|
+
*/ function getUrl(url, apiPath) {
|
|
109
|
+
if (process.env.NODE_ENV === 'development' && !url.includes('/_public/')) {
|
|
110
|
+
const proxyUrl = new URL(window.location.origin);
|
|
111
|
+
proxyUrl.pathname = `${apiPath}/proxy-file`;
|
|
112
|
+
proxyUrl.search = new URLSearchParams({
|
|
113
|
+
url
|
|
114
|
+
}).toString();
|
|
115
|
+
return proxyUrl.toString();
|
|
116
|
+
}
|
|
117
|
+
return url;
|
|
118
|
+
}
|
|
103
119
|
const uploadFileInner = async (file, uploadUrl, onProgressChange)=>{
|
|
104
120
|
const promise = new Promise((resolve, reject)=>{
|
|
105
121
|
const request = new XMLHttpRequest();
|
|
@@ -146,11 +162,10 @@ async function deleteFile({ url }, { apiPath, bucketName }) {
|
|
|
146
162
|
};
|
|
147
163
|
}
|
|
148
164
|
|
|
149
|
-
const DEFAULT_BASE_URL = process.env.NEXT_PUBLIC_EDGE_STORE_BASE_URL ?? 'https://files.
|
|
165
|
+
const DEFAULT_BASE_URL = process.env.NEXT_PUBLIC_EDGE_STORE_BASE_URL ?? 'https://files.edgestore.dev';
|
|
150
166
|
function createEdgeStoreProvider(opts) {
|
|
151
167
|
const EdgeStoreContext = /*#__PURE__*/ React__namespace.createContext(undefined);
|
|
152
|
-
const EdgeStoreProvider = ({
|
|
153
|
-
children, basePath })=>{
|
|
168
|
+
const EdgeStoreProvider = ({ children, basePath })=>{
|
|
154
169
|
return EdgeStoreProviderInner({
|
|
155
170
|
children,
|
|
156
171
|
context: EdgeStoreContext,
|
|
@@ -165,7 +180,7 @@ function createEdgeStoreProvider(opts) {
|
|
|
165
180
|
// @ts-expect-error - We know that the context value should not be undefined
|
|
166
181
|
const value = React__namespace.useContext(EdgeStoreContext);
|
|
167
182
|
if (!value && process.env.NODE_ENV !== 'production') {
|
|
168
|
-
throw new Error('[
|
|
183
|
+
throw new Error('[edgestore]: `useEdgeStore` must be wrapped in a <EdgeStoreProvider />');
|
|
169
184
|
}
|
|
170
185
|
return value;
|
|
171
186
|
}
|
|
@@ -193,11 +208,12 @@ function EdgeStoreProviderInner({ children, context, basePath, maxConcurrentUplo
|
|
|
193
208
|
});
|
|
194
209
|
}
|
|
195
210
|
});
|
|
211
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
196
212
|
}, []);
|
|
197
213
|
function getSrc(url) {
|
|
198
214
|
if (// in production we use cookies, so we don't need a token
|
|
199
215
|
process.env.NODE_ENV === 'production' || // public urls don't need a token
|
|
200
|
-
// e.g. https://files.
|
|
216
|
+
// e.g. https://files.edgestore.dev/project/bucket/_public/...
|
|
201
217
|
url.match(/^https?:\/\/[^\/]+\/[^\/]+\/[^\/]+\/_public\/.+/)) {
|
|
202
218
|
return `${url}`;
|
|
203
219
|
} else {
|
package/dist/index.mjs
CHANGED
|
@@ -50,6 +50,7 @@ async function uploadFile({ file, input, onProgressChange, options }, { apiPath,
|
|
|
50
50
|
extension: file.name.split('.').pop(),
|
|
51
51
|
type: file.type,
|
|
52
52
|
size: file.size,
|
|
53
|
+
fileName: options?.manualFileName,
|
|
53
54
|
replaceTargetUrl: options?.replaceTargetUrl
|
|
54
55
|
}
|
|
55
56
|
}),
|
|
@@ -64,8 +65,8 @@ async function uploadFile({ file, input, onProgressChange, options }, { apiPath,
|
|
|
64
65
|
// Upload the file to the signed URL and get the progress
|
|
65
66
|
await uploadFileInner(file, json.uploadUrl, onProgressChange);
|
|
66
67
|
return {
|
|
67
|
-
url: json.accessUrl,
|
|
68
|
-
thumbnailUrl: json.thumbnailUrl,
|
|
68
|
+
url: getUrl(json.accessUrl, apiPath),
|
|
69
|
+
thumbnailUrl: json.thumbnailUrl ? getUrl(json.thumbnailUrl, apiPath) : null,
|
|
69
70
|
size: json.size,
|
|
70
71
|
uploadedAt: new Date(json.uploadedAt),
|
|
71
72
|
path: json.path,
|
|
@@ -76,6 +77,21 @@ async function uploadFile({ file, input, onProgressChange, options }, { apiPath,
|
|
|
76
77
|
throw e;
|
|
77
78
|
}
|
|
78
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Protected files need third-party cookies to work.
|
|
82
|
+
* Since third party cookies doesn't work on localhost,
|
|
83
|
+
* we need to proxy the file through the server.
|
|
84
|
+
*/ function getUrl(url, apiPath) {
|
|
85
|
+
if (process.env.NODE_ENV === 'development' && !url.includes('/_public/')) {
|
|
86
|
+
const proxyUrl = new URL(window.location.origin);
|
|
87
|
+
proxyUrl.pathname = `${apiPath}/proxy-file`;
|
|
88
|
+
proxyUrl.search = new URLSearchParams({
|
|
89
|
+
url
|
|
90
|
+
}).toString();
|
|
91
|
+
return proxyUrl.toString();
|
|
92
|
+
}
|
|
93
|
+
return url;
|
|
94
|
+
}
|
|
79
95
|
const uploadFileInner = async (file, uploadUrl, onProgressChange)=>{
|
|
80
96
|
const promise = new Promise((resolve, reject)=>{
|
|
81
97
|
const request = new XMLHttpRequest();
|
|
@@ -122,11 +138,10 @@ async function deleteFile({ url }, { apiPath, bucketName }) {
|
|
|
122
138
|
};
|
|
123
139
|
}
|
|
124
140
|
|
|
125
|
-
const DEFAULT_BASE_URL = process.env.NEXT_PUBLIC_EDGE_STORE_BASE_URL ?? 'https://files.
|
|
141
|
+
const DEFAULT_BASE_URL = process.env.NEXT_PUBLIC_EDGE_STORE_BASE_URL ?? 'https://files.edgestore.dev';
|
|
126
142
|
function createEdgeStoreProvider(opts) {
|
|
127
143
|
const EdgeStoreContext = /*#__PURE__*/ React.createContext(undefined);
|
|
128
|
-
const EdgeStoreProvider = ({
|
|
129
|
-
children, basePath })=>{
|
|
144
|
+
const EdgeStoreProvider = ({ children, basePath })=>{
|
|
130
145
|
return EdgeStoreProviderInner({
|
|
131
146
|
children,
|
|
132
147
|
context: EdgeStoreContext,
|
|
@@ -141,7 +156,7 @@ function createEdgeStoreProvider(opts) {
|
|
|
141
156
|
// @ts-expect-error - We know that the context value should not be undefined
|
|
142
157
|
const value = React.useContext(EdgeStoreContext);
|
|
143
158
|
if (!value && process.env.NODE_ENV !== 'production') {
|
|
144
|
-
throw new Error('[
|
|
159
|
+
throw new Error('[edgestore]: `useEdgeStore` must be wrapped in a <EdgeStoreProvider />');
|
|
145
160
|
}
|
|
146
161
|
return value;
|
|
147
162
|
}
|
|
@@ -169,11 +184,12 @@ function EdgeStoreProviderInner({ children, context, basePath, maxConcurrentUplo
|
|
|
169
184
|
});
|
|
170
185
|
}
|
|
171
186
|
});
|
|
187
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
172
188
|
}, []);
|
|
173
189
|
function getSrc(url) {
|
|
174
190
|
if (// in production we use cookies, so we don't need a token
|
|
175
191
|
process.env.NODE_ENV === 'production' || // public urls don't need a token
|
|
176
|
-
// e.g. https://files.
|
|
192
|
+
// e.g. https://files.edgestore.dev/project/bucket/_public/...
|
|
177
193
|
url.match(/^https?:\/\/[^\/]+\/[^\/]+\/[^\/]+\/_public\/.+/)) {
|
|
178
194
|
return `${url}`;
|
|
179
195
|
} else {
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edgestore/react",
|
|
3
|
-
"version": "0.0.0-alpha.
|
|
4
|
-
"description": "
|
|
5
|
-
"homepage": "https://
|
|
6
|
-
"repository": "https://github.com/edgestorejs/
|
|
3
|
+
"version": "0.0.0-alpha.14",
|
|
4
|
+
"description": "The best DX for uploading files from your Next.js app",
|
|
5
|
+
"homepage": "https://edgestore.dev",
|
|
6
|
+
"repository": "https://github.com/edgestorejs/edgestore.git",
|
|
7
7
|
"author": "Ravi <me@ravi.com>",
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"module": "dist/index.mjs",
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"react",
|
|
13
13
|
"nodejs",
|
|
14
14
|
"nextjs",
|
|
15
|
+
"upload",
|
|
16
|
+
"file",
|
|
15
17
|
"image",
|
|
16
18
|
"cdn",
|
|
17
19
|
"edgestore",
|
|
@@ -52,14 +54,14 @@
|
|
|
52
54
|
"uuid": "^9.0.0"
|
|
53
55
|
},
|
|
54
56
|
"peerDependencies": {
|
|
55
|
-
"@edgestore/server": "0.0.0-alpha.
|
|
57
|
+
"@edgestore/server": "0.0.0-alpha.14",
|
|
56
58
|
"next": "*",
|
|
57
59
|
"react": ">=16.8.0",
|
|
58
60
|
"react-dom": ">=16.8.0",
|
|
59
61
|
"zod": ">=3.0.0"
|
|
60
62
|
},
|
|
61
63
|
"devDependencies": {
|
|
62
|
-
"@edgestore/server": "0.0.0-alpha.
|
|
64
|
+
"@edgestore/server": "0.0.0-alpha.14",
|
|
63
65
|
"@types/cookie": "^0.5.1",
|
|
64
66
|
"@types/node": "^18.11.18",
|
|
65
67
|
"@types/uuid": "^9.0.1",
|
|
@@ -69,5 +71,5 @@
|
|
|
69
71
|
"typescript": "^5.1.6",
|
|
70
72
|
"zod": "^3.21.4"
|
|
71
73
|
},
|
|
72
|
-
"gitHead": "
|
|
74
|
+
"gitHead": "6e0044e7fcb252014e5a4fc9257d54e897f42b84"
|
|
73
75
|
}
|
package/src/contextProvider.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import * as React from 'react';
|
|
|
3
3
|
import { BucketFunctions, createNextProxy } from './createNextProxy';
|
|
4
4
|
|
|
5
5
|
const DEFAULT_BASE_URL =
|
|
6
|
-
process.env.NEXT_PUBLIC_EDGE_STORE_BASE_URL ?? 'https://files.
|
|
6
|
+
process.env.NEXT_PUBLIC_EDGE_STORE_BASE_URL ?? 'https://files.edgestore.dev';
|
|
7
7
|
|
|
8
8
|
type EdgeStoreContextValue<TRouter extends AnyRouter> = {
|
|
9
9
|
edgestore: BucketFunctions<TRouter>;
|
|
@@ -29,7 +29,6 @@ export function createEdgeStoreProvider<TRouter extends AnyRouter>(opts?: {
|
|
|
29
29
|
>(undefined);
|
|
30
30
|
|
|
31
31
|
const EdgeStoreProvider = ({
|
|
32
|
-
// TODO: Add basePath when custom domain is supported
|
|
33
32
|
children,
|
|
34
33
|
basePath,
|
|
35
34
|
}: {
|
|
@@ -61,7 +60,7 @@ export function createEdgeStoreProvider<TRouter extends AnyRouter>(opts?: {
|
|
|
61
60
|
React.useContext(EdgeStoreContext);
|
|
62
61
|
if (!value && process.env.NODE_ENV !== 'production') {
|
|
63
62
|
throw new Error(
|
|
64
|
-
'[
|
|
63
|
+
'[edgestore]: `useEdgeStore` must be wrapped in a <EdgeStoreProvider />',
|
|
65
64
|
);
|
|
66
65
|
}
|
|
67
66
|
|
|
@@ -103,6 +102,7 @@ function EdgeStoreProviderInner<TRouter extends AnyRouter>({
|
|
|
103
102
|
});
|
|
104
103
|
}
|
|
105
104
|
});
|
|
105
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
106
106
|
}, []);
|
|
107
107
|
|
|
108
108
|
function getSrc(url: string) {
|
|
@@ -110,7 +110,7 @@ function EdgeStoreProviderInner<TRouter extends AnyRouter>({
|
|
|
110
110
|
// in production we use cookies, so we don't need a token
|
|
111
111
|
process.env.NODE_ENV === 'production' ||
|
|
112
112
|
// public urls don't need a token
|
|
113
|
-
// e.g. https://files.
|
|
113
|
+
// e.g. https://files.edgestore.dev/project/bucket/_public/...
|
|
114
114
|
url.match(/^https?:\/\/[^\/]+\/[^\/]+\/[^\/]+\/_public\/.+/)
|
|
115
115
|
) {
|
|
116
116
|
return `${url}`;
|
package/src/createNextProxy.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AnyRouter,
|
|
3
|
-
|
|
3
|
+
InferBucketPathObject,
|
|
4
4
|
InferMetadataObject,
|
|
5
5
|
} from '@edgestore/server/core';
|
|
6
6
|
import { z } from 'zod';
|
|
@@ -9,30 +9,36 @@ import EdgeStoreError from './libs/errors/EdgeStoreError';
|
|
|
9
9
|
export type BucketFunctions<TRouter extends AnyRouter> = {
|
|
10
10
|
[K in keyof TRouter['buckets']]: {
|
|
11
11
|
upload: (
|
|
12
|
-
params: z.infer<TRouter['buckets'][K]['_def']['input']> extends
|
|
12
|
+
params: z.infer<TRouter['buckets'][K]['_def']['input']> extends never
|
|
13
13
|
? {
|
|
14
14
|
file: File;
|
|
15
|
-
input: z.infer<TRouter['buckets'][K]['_def']['input']>;
|
|
16
15
|
onProgressChange?: OnProgressChangeHandler;
|
|
17
16
|
options?: UploadOptions;
|
|
18
17
|
}
|
|
19
18
|
: {
|
|
20
19
|
file: File;
|
|
20
|
+
input: z.infer<TRouter['buckets'][K]['_def']['input']>;
|
|
21
21
|
onProgressChange?: OnProgressChangeHandler;
|
|
22
22
|
options?: UploadOptions;
|
|
23
23
|
},
|
|
24
|
-
) => Promise<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
) => Promise<
|
|
25
|
+
TRouter['buckets'][K]['_def']['type'] extends 'IMAGE'
|
|
26
|
+
? {
|
|
27
|
+
url: string;
|
|
28
|
+
thumbnailUrl: string | null;
|
|
29
|
+
size: number;
|
|
30
|
+
uploadedAt: Date;
|
|
31
|
+
metadata: InferMetadataObject<TRouter['buckets'][K]>;
|
|
32
|
+
path: InferBucketPathObject<TRouter['buckets'][K]>;
|
|
33
|
+
}
|
|
34
|
+
: {
|
|
35
|
+
url: string;
|
|
36
|
+
size: number;
|
|
37
|
+
uploadedAt: Date;
|
|
38
|
+
metadata: InferMetadataObject<TRouter['buckets'][K]>;
|
|
39
|
+
path: InferBucketPathObject<TRouter['buckets'][K]>;
|
|
40
|
+
}
|
|
41
|
+
>;
|
|
36
42
|
delete: (params: { url: string }) => Promise<{
|
|
37
43
|
success: boolean;
|
|
38
44
|
}>;
|
|
@@ -42,6 +48,22 @@ export type BucketFunctions<TRouter extends AnyRouter> = {
|
|
|
42
48
|
type OnProgressChangeHandler = (progress: number) => void;
|
|
43
49
|
|
|
44
50
|
type UploadOptions = {
|
|
51
|
+
/**
|
|
52
|
+
* e.g. 'my-file-name.jpg'
|
|
53
|
+
*
|
|
54
|
+
* By default, a unique file name will be generated for each upload.
|
|
55
|
+
* If you want to use a custom file name, you can use this option.
|
|
56
|
+
* If you use the same file name for multiple uploads, the previous file will be overwritten.
|
|
57
|
+
* But it might take some time for the CDN cache to be cleared.
|
|
58
|
+
* So maybe you will keep seeing the old file for a while.
|
|
59
|
+
*
|
|
60
|
+
* If you want to replace an existing file immediately leave the `manualFileName` option empty and use the `replaceTargetUrl` option.
|
|
61
|
+
*/
|
|
62
|
+
manualFileName?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Use this to replace an existing file.
|
|
65
|
+
* It will automatically delete the existing file when the upload is complete.
|
|
66
|
+
*/
|
|
45
67
|
replaceTargetUrl?: string;
|
|
46
68
|
};
|
|
47
69
|
|
|
@@ -119,6 +141,7 @@ async function uploadFile(
|
|
|
119
141
|
extension: file.name.split('.').pop(),
|
|
120
142
|
type: file.type,
|
|
121
143
|
size: file.size,
|
|
144
|
+
fileName: options?.manualFileName,
|
|
122
145
|
replaceTargetUrl: options?.replaceTargetUrl,
|
|
123
146
|
},
|
|
124
147
|
}),
|
|
@@ -133,8 +156,10 @@ async function uploadFile(
|
|
|
133
156
|
// Upload the file to the signed URL and get the progress
|
|
134
157
|
await uploadFileInner(file, json.uploadUrl, onProgressChange);
|
|
135
158
|
return {
|
|
136
|
-
url: json.accessUrl,
|
|
137
|
-
thumbnailUrl: json.thumbnailUrl
|
|
159
|
+
url: getUrl(json.accessUrl, apiPath),
|
|
160
|
+
thumbnailUrl: json.thumbnailUrl
|
|
161
|
+
? getUrl(json.thumbnailUrl, apiPath)
|
|
162
|
+
: null,
|
|
138
163
|
size: json.size,
|
|
139
164
|
uploadedAt: new Date(json.uploadedAt),
|
|
140
165
|
path: json.path,
|
|
@@ -146,6 +171,23 @@ async function uploadFile(
|
|
|
146
171
|
}
|
|
147
172
|
}
|
|
148
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Protected files need third-party cookies to work.
|
|
176
|
+
* Since third party cookies doesn't work on localhost,
|
|
177
|
+
* we need to proxy the file through the server.
|
|
178
|
+
*/
|
|
179
|
+
function getUrl(url: string, apiPath: string) {
|
|
180
|
+
if (process.env.NODE_ENV === 'development' && !url.includes('/_public/')) {
|
|
181
|
+
const proxyUrl = new URL(window.location.origin);
|
|
182
|
+
proxyUrl.pathname = `${apiPath}/proxy-file`;
|
|
183
|
+
proxyUrl.search = new URLSearchParams({
|
|
184
|
+
url,
|
|
185
|
+
}).toString();
|
|
186
|
+
return proxyUrl.toString();
|
|
187
|
+
}
|
|
188
|
+
return url;
|
|
189
|
+
}
|
|
190
|
+
|
|
149
191
|
const uploadFileInner = async (
|
|
150
192
|
file: File,
|
|
151
193
|
uploadUrl: string,
|