nuxt-media-library 0.0.49 → 0.0.50
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/README.md +45 -0
- package/dist/module.d.mts +14 -1
- package/dist/module.d.ts +14 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +11 -2
- package/dist/runtime/components/MLMedia/Dropzone.d.vue.ts +3 -3
- package/dist/runtime/components/MLMedia/Dropzone.vue.d.ts +3 -3
- package/dist/runtime/components/MLMedia/Field.d.vue.ts +4 -4
- package/dist/runtime/components/MLMedia/Field.vue.d.ts +4 -4
- package/dist/runtime/components/MLMedia/FilePicker.d.vue.ts +1 -1
- package/dist/runtime/components/MLMedia/FilePicker.vue.d.ts +1 -1
- package/dist/runtime/components/MLMedia/FilePickerPanel.d.vue.ts +1 -1
- package/dist/runtime/components/MLMedia/FilePickerPanel.vue.d.ts +1 -1
- package/dist/runtime/components/MLMedia/Gallery.d.vue.ts +3 -3
- package/dist/runtime/components/MLMedia/Gallery.vue.d.ts +3 -3
- package/dist/runtime/components/MLMedia/GallerySection.d.vue.ts +5 -5
- package/dist/runtime/components/MLMedia/GallerySection.vue.d.ts +5 -5
- package/dist/runtime/components/MLMedia/KindBadge.d.vue.ts +1 -1
- package/dist/runtime/components/MLMedia/KindBadge.vue.d.ts +1 -1
- package/dist/runtime/components/MLMedia/MediaTile.d.vue.ts +2 -2
- package/dist/runtime/components/MLMedia/MediaTile.vue.d.ts +2 -2
- package/dist/runtime/components/MLMedia/PickerCard.d.vue.ts +3 -3
- package/dist/runtime/components/MLMedia/PickerCard.vue.d.ts +3 -3
- package/dist/runtime/components/MLMedia/PickerRow.d.vue.ts +2 -2
- package/dist/runtime/components/MLMedia/PickerRow.vue.d.ts +2 -2
- package/dist/runtime/components/MLMedia/ProgressVeil.d.vue.ts +1 -1
- package/dist/runtime/components/MLMedia/ProgressVeil.vue.d.ts +1 -1
- package/dist/runtime/components/MLMedia/StatusBadge.d.vue.ts +1 -1
- package/dist/runtime/components/MLMedia/StatusBadge.vue.d.ts +1 -1
- package/dist/runtime/composables/useMLServiceUpload.d.ts +10 -2
- package/dist/runtime/composables/useMLServiceUpload.js +4 -1
- package/dist/runtime/server/index.d.ts +2 -0
- package/dist/runtime/server/index.js +1 -0
- package/dist/runtime/server/service-uploads-proxy.d.ts +31 -0
- package/dist/runtime/server/service-uploads-proxy.js +61 -0
- package/dist/runtime/utils/uploader/serviceUpload.d.ts +16 -2
- package/dist/runtime/utils/uploader/serviceUpload.js +5 -1
- package/dist/types.d.mts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -153,6 +153,51 @@ addMediaGrants(lesson.mediaGrant, profile.mediaGrant) // "mlg_…" or { grant, e
|
|
|
153
153
|
grants are added.
|
|
154
154
|
- Grants live in the browser only, never in server-side state.
|
|
155
155
|
|
|
156
|
+
### Uploads started by your backend services
|
|
157
|
+
|
|
158
|
+
When a backend service starts the upload (the Python client's
|
|
159
|
+
`create_upload_router`), the service creates the session and returns a grant
|
|
160
|
+
for it, and `useMLServiceUpload` sends the parts. Where the parts go is the
|
|
161
|
+
app's decision, not the service's:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
// nuxt.config.ts
|
|
165
|
+
export default defineNuxtConfig({
|
|
166
|
+
mediaLibrary: {
|
|
167
|
+
baseURL: process.env.MEDIA_LIBRARY_URL, // server side, e.g. http://media-library.internal/v1
|
|
168
|
+
serviceUploads: { proxy: true }, // parts go through /api/media-library/service-uploads
|
|
169
|
+
// or, direct to the media library (needs CORS for the site):
|
|
170
|
+
// serviceUploads: { baseURL: 'https://media.example.com/v1' },
|
|
171
|
+
},
|
|
172
|
+
})
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
const { upload, progress } = useMLServiceUpload()
|
|
177
|
+
const { sessionId, assetId } = await upload(file, {
|
|
178
|
+
start: '/api/videos/uploads',
|
|
179
|
+
body: { profile: 'lesson-video', context: { lessonId } },
|
|
180
|
+
})
|
|
181
|
+
await $fetch(`/api/videos/lessons/${lessonId}/video`, { method: 'PUT', body: { sessionId, assetId } })
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
- The upload base is, in order: `upload(file, { baseURL })`,
|
|
185
|
+
`useMLServiceUpload({ baseURL })`, `serviceUploads.baseURL`
|
|
186
|
+
(`NUXT_PUBLIC_MEDIA_LIBRARY_SERVICE_UPLOAD_BASE_URL`), the pass-through
|
|
187
|
+
route when `serviceUploads.proxy` is on, and last the `uploadBaseUrl` an
|
|
188
|
+
older service returns. With none, `upload` fails before any part is sent.
|
|
189
|
+
- The pass-through route (`serviceUploads.proxy: true`) forwards only a
|
|
190
|
+
session's `GET`, parts `PUT`, `finalize` and `abort` to the private
|
|
191
|
+
`mediaLibrary.baseURL`. It requires the browser's `Bearer mlg_…` grant and
|
|
192
|
+
forwards it and `X-Tenant` untouched (the static `X-Tenant` when the
|
|
193
|
+
browser sends none). It never adds the service token, static auth headers
|
|
194
|
+
or a Nuxt-issued grant, drops cookies, and streams request bodies. Mount it
|
|
195
|
+
yourself with `createMLServiceUploadsProxy()` from
|
|
196
|
+
`nuxt-media-library/server` for another path.
|
|
197
|
+
- Don't send these uploads through `createMLCatchAllProxy` with `authorize`:
|
|
198
|
+
grant mode replaces the browser's grant with its own, and the service's
|
|
199
|
+
`claim_upload` rejects the session.
|
|
200
|
+
|
|
156
201
|
### Vue installation (non-Nuxt)
|
|
157
202
|
|
|
158
203
|
```bash
|
package/dist/module.d.mts
CHANGED
|
@@ -24,8 +24,21 @@ interface ModuleOptions {
|
|
|
24
24
|
final?: boolean;
|
|
25
25
|
};
|
|
26
26
|
headers?: Record<string, string> | (() => Record<string, string>);
|
|
27
|
+
/**
|
|
28
|
+
* Uploads started by the app's backend services (`useMLServiceUpload`).
|
|
29
|
+
* `proxy: true` adds `/api/media-library/service-uploads/**`, a same-origin
|
|
30
|
+
* pass-through that forwards the browser's media grant and nothing else.
|
|
31
|
+
* `baseURL` is where the browser sends those uploads (the media library's
|
|
32
|
+
* public API root with `/v1`, or a proxy route); with `proxy` it defaults
|
|
33
|
+
* to the pass-through route.
|
|
34
|
+
*/
|
|
35
|
+
serviceUploads?: {
|
|
36
|
+
proxy?: boolean;
|
|
37
|
+
baseURL?: string;
|
|
38
|
+
};
|
|
27
39
|
}
|
|
40
|
+
declare const SERVICE_UPLOADS_ROUTE = "/api/media-library/service-uploads";
|
|
28
41
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
29
42
|
|
|
30
|
-
export { _default as default };
|
|
43
|
+
export { SERVICE_UPLOADS_ROUTE, _default as default };
|
|
31
44
|
export type { ChunkUploaderRoutesConfig, ModuleOptions };
|
package/dist/module.d.ts
CHANGED
|
@@ -24,8 +24,21 @@ interface ModuleOptions {
|
|
|
24
24
|
final?: boolean;
|
|
25
25
|
};
|
|
26
26
|
headers?: Record<string, string> | (() => Record<string, string>);
|
|
27
|
+
/**
|
|
28
|
+
* Uploads started by the app's backend services (`useMLServiceUpload`).
|
|
29
|
+
* `proxy: true` adds `/api/media-library/service-uploads/**`, a same-origin
|
|
30
|
+
* pass-through that forwards the browser's media grant and nothing else.
|
|
31
|
+
* `baseURL` is where the browser sends those uploads (the media library's
|
|
32
|
+
* public API root with `/v1`, or a proxy route); with `proxy` it defaults
|
|
33
|
+
* to the pass-through route.
|
|
34
|
+
*/
|
|
35
|
+
serviceUploads?: {
|
|
36
|
+
proxy?: boolean;
|
|
37
|
+
baseURL?: string;
|
|
38
|
+
};
|
|
27
39
|
}
|
|
40
|
+
declare const SERVICE_UPLOADS_ROUTE = "/api/media-library/service-uploads";
|
|
28
41
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
29
42
|
|
|
30
|
-
export { _default as default };
|
|
43
|
+
export { SERVICE_UPLOADS_ROUTE, _default as default };
|
|
31
44
|
export type { ChunkUploaderRoutesConfig, ModuleOptions };
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -4,6 +4,7 @@ export * from '../dist/runtime/types/uploader.js';
|
|
|
4
4
|
export * from '../dist/runtime/types/preview.js';
|
|
5
5
|
export * from '../dist/runtime/types/asset.js';
|
|
6
6
|
|
|
7
|
+
const SERVICE_UPLOADS_ROUTE = "/api/media-library/service-uploads";
|
|
7
8
|
const module$1 = defineNuxtModule({
|
|
8
9
|
meta: {
|
|
9
10
|
name: "media-library",
|
|
@@ -89,6 +90,12 @@ const module$1 = defineNuxtModule({
|
|
|
89
90
|
route: "/api/media-library/manage/processing/**",
|
|
90
91
|
handler: resolver.resolve("./runtime/server/processing-proxy")
|
|
91
92
|
});
|
|
93
|
+
if (options.serviceUploads?.proxy) {
|
|
94
|
+
addServerHandler({
|
|
95
|
+
route: `${SERVICE_UPLOADS_ROUTE}/**`,
|
|
96
|
+
handler: resolver.resolve("./runtime/server/service-uploads-proxy")
|
|
97
|
+
});
|
|
98
|
+
}
|
|
92
99
|
nuxt.options.runtimeConfig.mediaLibrary = {
|
|
93
100
|
...existingPrivate,
|
|
94
101
|
baseURL: existingPrivate.baseURL ?? envBaseURL ?? options.baseURL ?? "",
|
|
@@ -110,7 +117,9 @@ const module$1 = defineNuxtModule({
|
|
|
110
117
|
perPart: existingPublic.checksum?.perPart ?? options.checksum?.perPart ?? true,
|
|
111
118
|
final: existingPublic.checksum?.final ?? options.checksum?.final ?? true
|
|
112
119
|
},
|
|
113
|
-
headers: existingPublic.headers ?? options.headers ?? {}
|
|
120
|
+
headers: existingPublic.headers ?? options.headers ?? {},
|
|
121
|
+
// Where useMLServiceUpload sends parts (NUXT_PUBLIC_MEDIA_LIBRARY_SERVICE_UPLOAD_BASE_URL).
|
|
122
|
+
serviceUploadBaseURL: existingPublic.serviceUploadBaseURL || options.serviceUploads?.baseURL || (options.serviceUploads?.proxy ? SERVICE_UPLOADS_ROUTE : "")
|
|
114
123
|
};
|
|
115
124
|
addPlugin({
|
|
116
125
|
src: resolver.resolve("./runtime/plugins/config")
|
|
@@ -118,4 +127,4 @@ const module$1 = defineNuxtModule({
|
|
|
118
127
|
}
|
|
119
128
|
});
|
|
120
129
|
|
|
121
|
-
export { module$1 as default };
|
|
130
|
+
export { SERVICE_UPLOADS_ROUTE, module$1 as default };
|
|
@@ -39,13 +39,13 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
39
39
|
onFiles?: ((files: File[]) => any) | undefined;
|
|
40
40
|
onRejected?: ((files: RejectedFile[]) => any) | undefined;
|
|
41
41
|
}>, {
|
|
42
|
-
title: string;
|
|
43
|
-
accept: string;
|
|
44
|
-
maxSize: number | null;
|
|
45
42
|
variant: "area" | "overlay";
|
|
46
43
|
compact: boolean;
|
|
47
44
|
disabled: boolean;
|
|
48
45
|
multiple: boolean;
|
|
46
|
+
title: string;
|
|
47
|
+
accept: string;
|
|
48
|
+
maxSize: number | null;
|
|
49
49
|
maxFiles: number | null;
|
|
50
50
|
hint: string;
|
|
51
51
|
buttonLabel: string;
|
|
@@ -39,13 +39,13 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
39
39
|
onFiles?: ((files: File[]) => any) | undefined;
|
|
40
40
|
onRejected?: ((files: RejectedFile[]) => any) | undefined;
|
|
41
41
|
}>, {
|
|
42
|
-
title: string;
|
|
43
|
-
accept: string;
|
|
44
|
-
maxSize: number | null;
|
|
45
42
|
variant: "area" | "overlay";
|
|
46
43
|
compact: boolean;
|
|
47
44
|
disabled: boolean;
|
|
48
45
|
multiple: boolean;
|
|
46
|
+
title: string;
|
|
47
|
+
accept: string;
|
|
48
|
+
maxSize: number | null;
|
|
49
49
|
maxFiles: number | null;
|
|
50
50
|
hint: string;
|
|
51
51
|
buttonLabel: string;
|
|
@@ -41,19 +41,19 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
41
41
|
"onUpdate:modelValue"?: ((value: string | null) => any) | undefined;
|
|
42
42
|
"onUpdate:alt"?: ((value: string | undefined) => any) | undefined;
|
|
43
43
|
}>, {
|
|
44
|
-
|
|
44
|
+
disabled: boolean;
|
|
45
|
+
visibility: "private" | "public" | "restricted";
|
|
45
46
|
label: string;
|
|
46
47
|
accept: readonly string[] | string;
|
|
47
48
|
maxSize: number | null;
|
|
49
|
+
required: boolean;
|
|
50
|
+
removeLabel: string;
|
|
48
51
|
processingConfig: {
|
|
49
52
|
stepOptions?: Record<string, Record<string, unknown>> | null;
|
|
50
53
|
useFilenameAsAssetId?: boolean;
|
|
51
54
|
enableTranscription?: boolean;
|
|
52
55
|
};
|
|
53
56
|
collectionName: string | null;
|
|
54
|
-
disabled: boolean;
|
|
55
|
-
required: boolean;
|
|
56
|
-
removeLabel: string;
|
|
57
57
|
hint: string;
|
|
58
58
|
missingAlt: boolean;
|
|
59
59
|
picker: boolean;
|
|
@@ -41,19 +41,19 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
41
41
|
"onUpdate:modelValue"?: ((value: string | null) => any) | undefined;
|
|
42
42
|
"onUpdate:alt"?: ((value: string | undefined) => any) | undefined;
|
|
43
43
|
}>, {
|
|
44
|
-
|
|
44
|
+
disabled: boolean;
|
|
45
|
+
visibility: "private" | "public" | "restricted";
|
|
45
46
|
label: string;
|
|
46
47
|
accept: readonly string[] | string;
|
|
47
48
|
maxSize: number | null;
|
|
49
|
+
required: boolean;
|
|
50
|
+
removeLabel: string;
|
|
48
51
|
processingConfig: {
|
|
49
52
|
stepOptions?: Record<string, Record<string, unknown>> | null;
|
|
50
53
|
useFilenameAsAssetId?: boolean;
|
|
51
54
|
enableTranscription?: boolean;
|
|
52
55
|
};
|
|
53
56
|
collectionName: string | null;
|
|
54
|
-
disabled: boolean;
|
|
55
|
-
required: boolean;
|
|
56
|
-
removeLabel: string;
|
|
57
57
|
hint: string;
|
|
58
58
|
missingAlt: boolean;
|
|
59
59
|
picker: boolean;
|
|
@@ -14,9 +14,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
14
14
|
}>, {
|
|
15
15
|
title: string;
|
|
16
16
|
accept: readonly string[] | string;
|
|
17
|
-
maxSize: number | null;
|
|
18
17
|
max: number;
|
|
19
18
|
selected: readonly string[];
|
|
19
|
+
maxSize: number | null;
|
|
20
20
|
upload: MediaUploadOptions;
|
|
21
21
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
22
22
|
declare const _default: typeof __VLS_export;
|
|
@@ -14,9 +14,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
14
14
|
}>, {
|
|
15
15
|
title: string;
|
|
16
16
|
accept: readonly string[] | string;
|
|
17
|
-
maxSize: number | null;
|
|
18
17
|
max: number;
|
|
19
18
|
selected: readonly string[];
|
|
19
|
+
maxSize: number | null;
|
|
20
20
|
upload: MediaUploadOptions;
|
|
21
21
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
22
22
|
declare const _default: typeof __VLS_export;
|
|
@@ -23,9 +23,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
23
23
|
}>, {
|
|
24
24
|
title: string;
|
|
25
25
|
accept: readonly string[] | string;
|
|
26
|
-
maxSize: number | null;
|
|
27
26
|
max: number;
|
|
28
27
|
selected: readonly string[];
|
|
28
|
+
maxSize: number | null;
|
|
29
29
|
upload: MediaUploadOptions;
|
|
30
30
|
bodyClass: string;
|
|
31
31
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -23,9 +23,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
23
23
|
}>, {
|
|
24
24
|
title: string;
|
|
25
25
|
accept: readonly string[] | string;
|
|
26
|
-
maxSize: number | null;
|
|
27
26
|
max: number;
|
|
28
27
|
selected: readonly string[];
|
|
28
|
+
maxSize: number | null;
|
|
29
29
|
upload: MediaUploadOptions;
|
|
30
30
|
bodyClass: string;
|
|
31
31
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -51,10 +51,12 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
51
51
|
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
52
52
|
"onUpdate:profiles"?: ((profiles: MediaGalleryProfiles) => any) | undefined;
|
|
53
53
|
}>, {
|
|
54
|
-
|
|
54
|
+
disabled: boolean;
|
|
55
55
|
title: string;
|
|
56
|
+
visibility: "private" | "public" | "restricted";
|
|
56
57
|
profile: string | null;
|
|
57
58
|
accept: readonly string[] | string;
|
|
59
|
+
max: number | null;
|
|
58
60
|
maxSize: number | null;
|
|
59
61
|
processingConfig: {
|
|
60
62
|
stepOptions?: Record<string, Record<string, unknown>> | null;
|
|
@@ -62,8 +64,6 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
62
64
|
enableTranscription?: boolean;
|
|
63
65
|
};
|
|
64
66
|
collectionName: string | null;
|
|
65
|
-
disabled: boolean;
|
|
66
|
-
max: number | null;
|
|
67
67
|
hint: string;
|
|
68
68
|
picker: boolean;
|
|
69
69
|
onPick: () => void;
|
|
@@ -51,10 +51,12 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
51
51
|
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
52
52
|
"onUpdate:profiles"?: ((profiles: MediaGalleryProfiles) => any) | undefined;
|
|
53
53
|
}>, {
|
|
54
|
-
|
|
54
|
+
disabled: boolean;
|
|
55
55
|
title: string;
|
|
56
|
+
visibility: "private" | "public" | "restricted";
|
|
56
57
|
profile: string | null;
|
|
57
58
|
accept: readonly string[] | string;
|
|
59
|
+
max: number | null;
|
|
58
60
|
maxSize: number | null;
|
|
59
61
|
processingConfig: {
|
|
60
62
|
stepOptions?: Record<string, Record<string, unknown>> | null;
|
|
@@ -62,8 +64,6 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
62
64
|
enableTranscription?: boolean;
|
|
63
65
|
};
|
|
64
66
|
collectionName: string | null;
|
|
65
|
-
disabled: boolean;
|
|
66
|
-
max: number | null;
|
|
67
67
|
hint: string;
|
|
68
68
|
picker: boolean;
|
|
69
69
|
onPick: () => void;
|
|
@@ -66,21 +66,21 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
66
66
|
"onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
|
|
67
67
|
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
68
68
|
}>, {
|
|
69
|
-
|
|
69
|
+
disabled: boolean;
|
|
70
|
+
highlight: ReadonlySet<string> | null;
|
|
70
71
|
title: string;
|
|
72
|
+
visibility: "private" | "public" | "restricted";
|
|
71
73
|
profile: string | null;
|
|
72
74
|
accept: readonly string[] | string;
|
|
75
|
+
max: number | null;
|
|
73
76
|
maxSize: number | null;
|
|
77
|
+
removeLabel: string;
|
|
74
78
|
processingConfig: {
|
|
75
79
|
stepOptions?: Record<string, Record<string, unknown>> | null;
|
|
76
80
|
useFilenameAsAssetId?: boolean;
|
|
77
81
|
enableTranscription?: boolean;
|
|
78
82
|
};
|
|
79
83
|
collectionName: string | null;
|
|
80
|
-
disabled: boolean;
|
|
81
|
-
highlight: ReadonlySet<string> | null;
|
|
82
|
-
removeLabel: string;
|
|
83
|
-
max: number | null;
|
|
84
84
|
onAdd: (assetId: string, tag: string | undefined) => void;
|
|
85
85
|
hint: string;
|
|
86
86
|
picker: boolean;
|
|
@@ -66,21 +66,21 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
66
66
|
"onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
|
|
67
67
|
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
68
68
|
}>, {
|
|
69
|
-
|
|
69
|
+
disabled: boolean;
|
|
70
|
+
highlight: ReadonlySet<string> | null;
|
|
70
71
|
title: string;
|
|
72
|
+
visibility: "private" | "public" | "restricted";
|
|
71
73
|
profile: string | null;
|
|
72
74
|
accept: readonly string[] | string;
|
|
75
|
+
max: number | null;
|
|
73
76
|
maxSize: number | null;
|
|
77
|
+
removeLabel: string;
|
|
74
78
|
processingConfig: {
|
|
75
79
|
stepOptions?: Record<string, Record<string, unknown>> | null;
|
|
76
80
|
useFilenameAsAssetId?: boolean;
|
|
77
81
|
enableTranscription?: boolean;
|
|
78
82
|
};
|
|
79
83
|
collectionName: string | null;
|
|
80
|
-
disabled: boolean;
|
|
81
|
-
highlight: ReadonlySet<string> | null;
|
|
82
|
-
removeLabel: string;
|
|
83
|
-
max: number | null;
|
|
84
84
|
onAdd: (assetId: string, tag: string | undefined) => void;
|
|
85
85
|
hint: string;
|
|
86
86
|
picker: boolean;
|
|
@@ -9,7 +9,7 @@ type __VLS_Props = {
|
|
|
9
9
|
variant?: BadgeProps['variant'];
|
|
10
10
|
};
|
|
11
11
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
12
|
-
size: "
|
|
12
|
+
size: "xs" | "sm" | "md" | "lg" | "xl";
|
|
13
13
|
variant: "solid" | "outline" | "soft" | "subtle";
|
|
14
14
|
icon: boolean;
|
|
15
15
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -9,7 +9,7 @@ type __VLS_Props = {
|
|
|
9
9
|
variant?: BadgeProps['variant'];
|
|
10
10
|
};
|
|
11
11
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
12
|
-
size: "
|
|
12
|
+
size: "xs" | "sm" | "md" | "lg" | "xl";
|
|
13
13
|
variant: "solid" | "outline" | "soft" | "subtle";
|
|
14
14
|
icon: boolean;
|
|
15
15
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -40,10 +40,10 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
40
40
|
asset: AssetDetail | DeliveryAsset | null;
|
|
41
41
|
src: string | null;
|
|
42
42
|
progress: number | null;
|
|
43
|
-
|
|
43
|
+
disabled: boolean;
|
|
44
44
|
kind: string | null;
|
|
45
|
+
name: string;
|
|
45
46
|
phase: ProgressVeilPhase | null;
|
|
46
|
-
disabled: boolean;
|
|
47
47
|
selected: boolean;
|
|
48
48
|
missingAlt: boolean;
|
|
49
49
|
onOpen: () => void;
|
|
@@ -40,10 +40,10 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
40
40
|
asset: AssetDetail | DeliveryAsset | null;
|
|
41
41
|
src: string | null;
|
|
42
42
|
progress: number | null;
|
|
43
|
-
|
|
43
|
+
disabled: boolean;
|
|
44
44
|
kind: string | null;
|
|
45
|
+
name: string;
|
|
45
46
|
phase: ProgressVeilPhase | null;
|
|
46
|
-
disabled: boolean;
|
|
47
47
|
selected: boolean;
|
|
48
48
|
missingAlt: boolean;
|
|
49
49
|
onOpen: () => void;
|
|
@@ -24,11 +24,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
24
24
|
}>, {
|
|
25
25
|
asset: AssetDetail | DeliveryAsset | null;
|
|
26
26
|
src: string | null;
|
|
27
|
-
name: string;
|
|
28
|
-
kind: string | null;
|
|
29
27
|
disabled: boolean;
|
|
30
|
-
|
|
28
|
+
kind: string | null;
|
|
29
|
+
name: string;
|
|
31
30
|
selected: boolean;
|
|
31
|
+
meta: string;
|
|
32
32
|
disabledReason: string;
|
|
33
33
|
onPreview: () => void;
|
|
34
34
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -24,11 +24,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
24
24
|
}>, {
|
|
25
25
|
asset: AssetDetail | DeliveryAsset | null;
|
|
26
26
|
src: string | null;
|
|
27
|
-
name: string;
|
|
28
|
-
kind: string | null;
|
|
29
27
|
disabled: boolean;
|
|
30
|
-
|
|
28
|
+
kind: string | null;
|
|
29
|
+
name: string;
|
|
31
30
|
selected: boolean;
|
|
31
|
+
meta: string;
|
|
32
32
|
disabledReason: string;
|
|
33
33
|
onPreview: () => void;
|
|
34
34
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -20,9 +20,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
20
20
|
size: string;
|
|
21
21
|
asset: AssetDetail | DeliveryAsset | null;
|
|
22
22
|
src: string | null;
|
|
23
|
-
name: string;
|
|
24
|
-
kind: string | null;
|
|
25
23
|
disabled: boolean;
|
|
24
|
+
kind: string | null;
|
|
25
|
+
name: string;
|
|
26
26
|
selected: boolean;
|
|
27
27
|
date: string;
|
|
28
28
|
disabledReason: string;
|
|
@@ -20,9 +20,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
20
20
|
size: string;
|
|
21
21
|
asset: AssetDetail | DeliveryAsset | null;
|
|
22
22
|
src: string | null;
|
|
23
|
-
name: string;
|
|
24
|
-
kind: string | null;
|
|
25
23
|
disabled: boolean;
|
|
24
|
+
kind: string | null;
|
|
25
|
+
name: string;
|
|
26
26
|
selected: boolean;
|
|
27
27
|
date: string;
|
|
28
28
|
disabledReason: string;
|
|
@@ -15,8 +15,8 @@ type __VLS_Props = {
|
|
|
15
15
|
};
|
|
16
16
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
17
17
|
progress: number | null;
|
|
18
|
-
label: string;
|
|
19
18
|
compact: boolean;
|
|
19
|
+
label: string;
|
|
20
20
|
onCancel: () => void;
|
|
21
21
|
onRetry: () => void;
|
|
22
22
|
detail: string;
|
|
@@ -15,8 +15,8 @@ type __VLS_Props = {
|
|
|
15
15
|
};
|
|
16
16
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
17
17
|
progress: number | null;
|
|
18
|
-
label: string;
|
|
19
18
|
compact: boolean;
|
|
19
|
+
label: string;
|
|
20
20
|
onCancel: () => void;
|
|
21
21
|
onRetry: () => void;
|
|
22
22
|
detail: string;
|
|
@@ -8,7 +8,7 @@ type __VLS_Props = {
|
|
|
8
8
|
variant?: BadgeProps['variant'];
|
|
9
9
|
};
|
|
10
10
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
11
|
-
size: "
|
|
11
|
+
size: "xs" | "sm" | "md" | "lg" | "xl";
|
|
12
12
|
progress: number | null;
|
|
13
13
|
variant: "solid" | "outline" | "soft" | "subtle";
|
|
14
14
|
icon: boolean;
|
|
@@ -8,7 +8,7 @@ type __VLS_Props = {
|
|
|
8
8
|
variant?: BadgeProps['variant'];
|
|
9
9
|
};
|
|
10
10
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
11
|
-
size: "
|
|
11
|
+
size: "xs" | "sm" | "md" | "lg" | "xl";
|
|
12
12
|
progress: number | null;
|
|
13
13
|
variant: "solid" | "outline" | "soft" | "subtle";
|
|
14
14
|
icon: boolean;
|
|
@@ -16,10 +16,18 @@ export type { ServiceUploadOptions, ServiceUploadResult } from '../utils/uploade
|
|
|
16
16
|
* await $fetch(`/api/videos/lessons/${lessonId}/video`, { method: 'PUT', body: { sessionId, assetId } })
|
|
17
17
|
* ```
|
|
18
18
|
*
|
|
19
|
+
* Parts go to, in order: `upload(file, { baseURL })`, `useMLServiceUpload({
|
|
20
|
+
* baseURL })`, the app's `mediaLibrary.serviceUploads` (`baseURL`, or the
|
|
21
|
+
* pass-through route with `proxy: true`), then the URL the service returned.
|
|
22
|
+
* Direct calls need the media library to allow the site's origin (CORS);
|
|
23
|
+
* the pass-through route does not.
|
|
24
|
+
*
|
|
19
25
|
* One upload at a time per call of the composable; call it once per
|
|
20
|
-
* upload field.
|
|
26
|
+
* upload field.
|
|
21
27
|
*/
|
|
22
|
-
export declare function useMLServiceUpload(
|
|
28
|
+
export declare function useMLServiceUpload(defaults?: {
|
|
29
|
+
baseURL?: string;
|
|
30
|
+
}): {
|
|
23
31
|
upload: (file: File, options: ServiceUploadOptions) => Promise<ServiceUploadResult>;
|
|
24
32
|
abort: () => void;
|
|
25
33
|
uploading: import("vue").Ref<boolean, boolean>;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { ref } from "vue";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
2
3
|
import { ChunkUploader } from "../utils/uploader/uploader.js";
|
|
3
4
|
import { createServiceUpload } from "../utils/uploader/serviceUpload.js";
|
|
4
|
-
export function useMLServiceUpload() {
|
|
5
|
+
export function useMLServiceUpload(defaults = {}) {
|
|
5
6
|
const uploading = ref(false);
|
|
6
7
|
const progress = ref(null);
|
|
7
8
|
const error = ref(null);
|
|
9
|
+
const configured = useRuntimeConfig().public?.mediaLibrary?.serviceUploadBaseURL;
|
|
8
10
|
const runner = createServiceUpload({
|
|
11
|
+
baseURL: defaults.baseURL || configured || void 0,
|
|
9
12
|
fetch: (url, opts) => $fetch(url, opts),
|
|
10
13
|
createUploader: (cfg) => new ChunkUploader(cfg)
|
|
11
14
|
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { createMLProxy, createMLCatchAllProxy } from './create-ml-proxy.js';
|
|
2
|
+
export { createMLServiceUploadsProxy } from './service-uploads-proxy.js';
|
|
3
|
+
export type { MLServiceUploadsProxyOptions } from './service-uploads-proxy.js';
|
|
2
4
|
export type { MLProxyType, MLProxyOptions } from './create-ml-proxy.js';
|
|
3
5
|
export { cachedIssuer, grantNeed, MLGrantIssueError, serviceTokenIssuer, specFromNeed, } from './grants.js';
|
|
4
6
|
export type { MLAuthorize, MLGrantNeed, MLGrantProfile, MLGrantScope, MLGrantSpec, MLIssueGrant, MLIssuedGrant, } from './grants.js';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Same-origin pass-through for uploads started by the app's backend services
|
|
3
|
+
* (py_client `create_upload_router`, `useMLServiceUpload`).
|
|
4
|
+
*
|
|
5
|
+
* The service created the session and gave the browser a media grant for it;
|
|
6
|
+
* this route only carries the browser's own requests to the media library:
|
|
7
|
+
*
|
|
8
|
+
* - only `GET …/sessions/{id}`, `PUT …/sessions/{id}/parts/{n}`,
|
|
9
|
+
* `POST …/sessions/{id}/finalize` and `POST …/sessions/{id}/abort`
|
|
10
|
+
* (never session creation: the service does that);
|
|
11
|
+
* - the browser's `Authorization: Bearer mlg_…` is required and forwarded
|
|
12
|
+
* untouched, with its `X-Tenant`; no service token, static auth header or
|
|
13
|
+
* Nuxt-issued grant is ever added, and cookies are not forwarded;
|
|
14
|
+
* - request bodies are streamed (parts are megabytes, files gigabytes).
|
|
15
|
+
*/
|
|
16
|
+
export interface MLServiceUploadsProxyOptions {
|
|
17
|
+
/** The media library API root with `/v1`. Default: private `mediaLibrary.baseURL`. */
|
|
18
|
+
baseURL?: string;
|
|
19
|
+
/** `X-Tenant` when the browser sends none. Default: the static `X-Tenant` header. */
|
|
20
|
+
tenant?: string;
|
|
21
|
+
}
|
|
22
|
+
/** The upstream path and method for a request, or null when it is not a session route. */
|
|
23
|
+
export declare function serviceUploadRoute(pathname: string, method: string): {
|
|
24
|
+
path: string;
|
|
25
|
+
method: string;
|
|
26
|
+
} | null;
|
|
27
|
+
/** The headers sent upstream: the grant, the tenant, and the upload headers. */
|
|
28
|
+
export declare function serviceUploadHeaders(incoming: Record<string, string | undefined>, tenant?: string): Record<string, string> | null;
|
|
29
|
+
export declare function createMLServiceUploadsProxy(options?: MLServiceUploadsProxyOptions): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
|
30
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
|
31
|
+
export default _default;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createError, defineEventHandler, getRequestHeaders, getRequestURL, getRequestWebStream, sendProxy } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
const SESSION_PATH = /\/manage\/uploads\/sessions\/([^/]+)(\/parts\/\d+|\/finalize|\/abort)?\/?$/;
|
|
4
|
+
const GRANT = /^Bearer mlg_[\w.-]+$/;
|
|
5
|
+
const FORWARDED = ["content-type", "content-length", "content-range", "x-part-sha256", "accept"];
|
|
6
|
+
export function serviceUploadRoute(pathname, method) {
|
|
7
|
+
const match = pathname.match(SESSION_PATH);
|
|
8
|
+
if (!match) return null;
|
|
9
|
+
const [, sessionId, action = ""] = match;
|
|
10
|
+
const expected = action.startsWith("/parts/") ? "PUT" : action ? "POST" : "GET";
|
|
11
|
+
if (method !== expected) return null;
|
|
12
|
+
return { path: `/manage/uploads/sessions/${sessionId}${action}`, method };
|
|
13
|
+
}
|
|
14
|
+
export function serviceUploadHeaders(incoming, tenant) {
|
|
15
|
+
const lower = Object.fromEntries(Object.entries(incoming).map(([k, v]) => [k.toLowerCase(), v]));
|
|
16
|
+
const authorization = lower.authorization;
|
|
17
|
+
if (!authorization || !GRANT.test(authorization)) return null;
|
|
18
|
+
const headers = { authorization };
|
|
19
|
+
const xTenant = lower["x-tenant"] || tenant;
|
|
20
|
+
if (xTenant) headers["x-tenant"] = xTenant;
|
|
21
|
+
for (const name of FORWARDED) {
|
|
22
|
+
const value = lower[name];
|
|
23
|
+
if (value) headers[name] = value;
|
|
24
|
+
}
|
|
25
|
+
return headers;
|
|
26
|
+
}
|
|
27
|
+
export function createMLServiceUploadsProxy(options = {}) {
|
|
28
|
+
return defineEventHandler((event) => proxyServiceUpload(event, options));
|
|
29
|
+
}
|
|
30
|
+
async function proxyServiceUpload(event, options) {
|
|
31
|
+
const method = (event.node.req.method || "GET").toUpperCase();
|
|
32
|
+
const reqUrl = getRequestURL(event);
|
|
33
|
+
const route = serviceUploadRoute(reqUrl.pathname, method);
|
|
34
|
+
if (!route) throw createError({ statusCode: 404, statusMessage: "Not found" });
|
|
35
|
+
const config = useRuntimeConfig().mediaLibrary || {};
|
|
36
|
+
const baseURL = options.baseURL ?? config.baseURL;
|
|
37
|
+
if (!baseURL || !/^https?:\/\//.test(baseURL)) {
|
|
38
|
+
throw createError({ statusCode: 500, statusMessage: "mediaLibrary.baseURL must be an absolute http(s) URL." });
|
|
39
|
+
}
|
|
40
|
+
const headers = serviceUploadHeaders(getRequestHeaders(event), options.tenant ?? staticTenant(config.headers));
|
|
41
|
+
if (!headers) throw createError({ statusCode: 401, statusMessage: "A media grant (Bearer mlg_\u2026) is required" });
|
|
42
|
+
const upstream = new URL(`${baseURL.replace(/\/+$/, "")}${route.path}`);
|
|
43
|
+
upstream.search = reqUrl.search;
|
|
44
|
+
const fetchOptions = { method, headers };
|
|
45
|
+
if (method !== "GET") {
|
|
46
|
+
fetchOptions.body = getRequestWebStream(event);
|
|
47
|
+
fetchOptions.duplex = "half";
|
|
48
|
+
}
|
|
49
|
+
const result = await sendProxy(event, upstream.toString(), { fetchOptions });
|
|
50
|
+
if (event.node.res.statusCode >= 400) {
|
|
51
|
+
console.error(`[media-library] service-uploads-proxy upstream responded ${event.node.res.statusCode} for ${method} ${upstream}`);
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
function staticTenant(headers) {
|
|
56
|
+
const resolved = typeof headers === "function" ? headers() : headers;
|
|
57
|
+
if (!resolved || typeof resolved !== "object") return void 0;
|
|
58
|
+
const entry = Object.entries(resolved).find(([k]) => k.toLowerCase() === "x-tenant");
|
|
59
|
+
return entry?.[1] || void 0;
|
|
60
|
+
}
|
|
61
|
+
export default createMLServiceUploadsProxy();
|
|
@@ -11,7 +11,12 @@ export interface ServiceUploadStart {
|
|
|
11
11
|
sessionId: string;
|
|
12
12
|
grant: string;
|
|
13
13
|
expiresAt: string;
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Where the service says to upload. Used only when the app configures no
|
|
16
|
+
* upload base (`baseURL` option, or `mediaLibrary.serviceUploads`); newer
|
|
17
|
+
* services leave it out.
|
|
18
|
+
*/
|
|
19
|
+
uploadBaseUrl?: string;
|
|
15
20
|
tenant: string;
|
|
16
21
|
partSizeBytes?: number;
|
|
17
22
|
}
|
|
@@ -24,6 +29,13 @@ export interface ServiceUploadGrant {
|
|
|
24
29
|
export interface ServiceUploadOptions {
|
|
25
30
|
/** The service's start endpoint, e.g. `/api/videos/uploads`. */
|
|
26
31
|
start: string;
|
|
32
|
+
/**
|
|
33
|
+
* Where to send the parts: the media library's public API root with
|
|
34
|
+
* `/v1`, or a same-origin pass-through route such as
|
|
35
|
+
* `/api/media-library/service-uploads`. Default: the app's configured
|
|
36
|
+
* upload base, then the service's `uploadBaseUrl`.
|
|
37
|
+
*/
|
|
38
|
+
baseURL?: string;
|
|
27
39
|
/**
|
|
28
40
|
* Extra fields for the start request, e.g. `{ profile, context }`.
|
|
29
41
|
* `mimeType`, `sizeBytes` and `filename` are taken from the file
|
|
@@ -59,12 +71,14 @@ interface UploaderLike {
|
|
|
59
71
|
}
|
|
60
72
|
export interface ServiceUploadDeps {
|
|
61
73
|
fetch: FetchLike;
|
|
74
|
+
/** The app's configured upload base (used when an upload passes no `baseURL`). */
|
|
75
|
+
baseURL?: string;
|
|
62
76
|
createUploader: (cfg: ChunkUploaderConfig) => UploaderLike;
|
|
63
77
|
now?: () => number;
|
|
64
78
|
setTimer?: (fn: () => void, ms: number) => unknown;
|
|
65
79
|
clearTimer?: (handle: unknown) => void;
|
|
66
80
|
}
|
|
67
|
-
/** The media library routes a session grant allows (relative to
|
|
81
|
+
/** The media library routes a session grant allows (relative to the upload base). */
|
|
68
82
|
export declare const serviceUploadRoutes: {
|
|
69
83
|
createSession: string;
|
|
70
84
|
getSession: (id: string) => string;
|
|
@@ -41,6 +41,10 @@ export function createServiceUpload(deps) {
|
|
|
41
41
|
}
|
|
42
42
|
});
|
|
43
43
|
if (run.aborted) throw abortError();
|
|
44
|
+
const baseURL = (options.baseURL || deps.baseURL || started.uploadBaseUrl || "").replace(/\/+$/, "");
|
|
45
|
+
if (!baseURL) {
|
|
46
|
+
throw new Error("No upload base URL: pass baseURL, or set mediaLibrary.serviceUploads (baseURL or proxy) in nuxt.config.");
|
|
47
|
+
}
|
|
44
48
|
const sessionId = started.sessionId;
|
|
45
49
|
const refreshUrl = typeof options.refresh === "function" ? options.refresh(sessionId) : options.refresh ?? `${start}/${encodeURIComponent(sessionId)}/grant`;
|
|
46
50
|
let grant = started.grant;
|
|
@@ -73,7 +77,7 @@ export function createServiceUpload(deps) {
|
|
|
73
77
|
timer = null;
|
|
74
78
|
};
|
|
75
79
|
const uploader = deps.createUploader({
|
|
76
|
-
baseURL
|
|
80
|
+
baseURL,
|
|
77
81
|
routes: serviceUploadRoutes,
|
|
78
82
|
partSize: started.partSizeBytes ?? DEFAULT_PART_SIZE,
|
|
79
83
|
concurrency: options.concurrency ?? 3,
|
package/dist/types.d.mts
CHANGED