nec-oss 0.1.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/index.js ADDED
@@ -0,0 +1,801 @@
1
+ //#region ../core/dist/index.js
2
+ var EventEmitter = class {
3
+ listeners = /* @__PURE__ */ new Map();
4
+ emit(eventName, payload) {
5
+ (this.listeners.get(eventName) ?? /* @__PURE__ */ new Set()).forEach((handler) => {
6
+ handler(payload);
7
+ });
8
+ }
9
+ on(eventName, handler) {
10
+ const handlers = this.listeners.get(eventName) ?? /* @__PURE__ */ new Set();
11
+ handlers.add(handler);
12
+ this.listeners.set(eventName, handlers);
13
+ return () => {
14
+ handlers.delete(handler);
15
+ };
16
+ }
17
+ };
18
+ let nextId = 0;
19
+ function createUploadFile(file) {
20
+ const name = getFileName(file);
21
+ const type = "type" in file ? file.type : "";
22
+ const size = "size" in file ? file.size : 0;
23
+ nextId += 1;
24
+ return {
25
+ id: `upload-${Date.now()}-${nextId}`,
26
+ file,
27
+ name,
28
+ progress: {
29
+ percent: 0,
30
+ loaded: 0,
31
+ total: size
32
+ },
33
+ size,
34
+ status: "pending",
35
+ type
36
+ };
37
+ }
38
+ function normalizeFiles(files) {
39
+ if (isFileList(files)) return Array.from(files);
40
+ if (Array.isArray(files)) return files;
41
+ return [files];
42
+ }
43
+ function isFileList(files) {
44
+ return typeof FileList !== "undefined" && files instanceof FileList;
45
+ }
46
+ function getFileName(file) {
47
+ if ("name" in file && typeof file.name === "string") return file.name;
48
+ return "blob";
49
+ }
50
+ function validateFiles(files, existingFiles, options = {}) {
51
+ const acceptedFiles = [];
52
+ const invalidFiles = [];
53
+ return files.reduce((promise, file) => {
54
+ return promise.then(() => {
55
+ return validateFile(file, {
56
+ acceptedFiles,
57
+ existingFiles,
58
+ files
59
+ }, options).then((result) => {
60
+ if (result.valid) {
61
+ acceptedFiles.push(file);
62
+ return;
63
+ }
64
+ invalidFiles.push({
65
+ file: file.file,
66
+ name: file.name,
67
+ reason: result.message ?? "文件校验失败",
68
+ rule: result.rule ?? "custom"
69
+ });
70
+ });
71
+ });
72
+ }, Promise.resolve()).then(() => ({
73
+ acceptedFiles,
74
+ invalidFiles
75
+ }));
76
+ }
77
+ function validateFile(file, context, options) {
78
+ const builtInResult = validateBuiltInRules(file, context, options);
79
+ if (!builtInResult.valid) return Promise.resolve(builtInResult);
80
+ return (options.customValidators ?? []).reduce((promise, validator) => {
81
+ return promise.then((currentResult) => {
82
+ if (!currentResult.valid) return currentResult;
83
+ return Promise.resolve(validator(file, context)).then(normalizeValidationResult);
84
+ });
85
+ }, Promise.resolve({ valid: true }));
86
+ }
87
+ function validateBuiltInRules(file, context, options) {
88
+ if (options.allowEmpty !== true && file.size === 0) return {
89
+ valid: false,
90
+ message: "不允许上传空文件",
91
+ rule: "allowEmpty"
92
+ };
93
+ if (typeof options.maxCount === "number" && context.existingFiles.length + context.acceptedFiles.length >= options.maxCount) return {
94
+ valid: false,
95
+ message: `最多只能上传 ${options.maxCount} 个文件`,
96
+ rule: "maxCount"
97
+ };
98
+ if (typeof options.maxSize === "number" && file.size > options.maxSize) return {
99
+ valid: false,
100
+ message: `文件大小不能超过 ${options.maxSize} 字节`,
101
+ rule: "maxSize"
102
+ };
103
+ if (typeof options.minSize === "number" && file.size < options.minSize) return {
104
+ valid: false,
105
+ message: `文件大小不能小于 ${options.minSize} 字节`,
106
+ rule: "minSize"
107
+ };
108
+ if (options.allowDuplicateName !== true && hasDuplicateName(file, context)) return {
109
+ valid: false,
110
+ message: "不允许上传重名文件",
111
+ rule: "allowDuplicateName"
112
+ };
113
+ if (options.accept && !matchesAccept(file, options.accept)) return {
114
+ valid: false,
115
+ message: "文件类型不符合要求",
116
+ rule: "accept"
117
+ };
118
+ if (options.extensions && options.extensions.length > 0 && !matchesExtension(file, options.extensions)) return {
119
+ valid: false,
120
+ message: "文件扩展名不符合要求",
121
+ rule: "extensions"
122
+ };
123
+ return { valid: true };
124
+ }
125
+ function normalizeValidationResult(result) {
126
+ if (typeof result === "boolean") return { valid: result };
127
+ if (typeof result === "string") return {
128
+ message: result,
129
+ rule: "custom",
130
+ valid: false
131
+ };
132
+ return result;
133
+ }
134
+ function hasDuplicateName(file, context) {
135
+ return [...context.existingFiles, ...context.files].filter((item) => item.name === file.name).length > 1;
136
+ }
137
+ function matchesAccept(file, accept) {
138
+ return (Array.isArray(accept) ? accept : accept.split(",")).map((item) => item.trim()).filter(Boolean).some((item) => {
139
+ if (item.endsWith("/*")) return file.type.startsWith(item.slice(0, -1));
140
+ if (item.startsWith(".")) return file.name.toLowerCase().endsWith(item.toLowerCase());
141
+ return file.type === item;
142
+ });
143
+ }
144
+ function matchesExtension(file, extensions) {
145
+ const normalizedName = file.name.toLowerCase();
146
+ return extensions.some((extension) => {
147
+ const normalizedExtension = extension.startsWith(".") ? extension.toLowerCase() : `.${extension.toLowerCase()}`;
148
+ return normalizedName.endsWith(normalizedExtension);
149
+ });
150
+ }
151
+ function createUploader(options) {
152
+ return new UploadQueue(options);
153
+ }
154
+ var UploadQueue = class {
155
+ emitter = new EventEmitter();
156
+ files = [];
157
+ taskMap = /* @__PURE__ */ new Map();
158
+ activeCount = 0;
159
+ runningPromise;
160
+ resolveRunning;
161
+ constructor(options) {
162
+ this.options = options;
163
+ }
164
+ add(inputFiles) {
165
+ return validateFiles(normalizeFiles(inputFiles).map(createUploadFile), this.files, this.options.validate).then((result) => {
166
+ result.acceptedFiles.forEach((file) => {
167
+ this.files.push(file);
168
+ this.taskMap.set(file.id, new UploadTaskImpl(file, this));
169
+ });
170
+ this.emitQueueChange();
171
+ return result;
172
+ });
173
+ }
174
+ start() {
175
+ if (this.runningPromise) return this.runningPromise;
176
+ this.runningPromise = new Promise((resolve) => {
177
+ this.resolveRunning = resolve;
178
+ }).finally(() => {
179
+ this.resolveRunning = void 0;
180
+ this.runningPromise = void 0;
181
+ });
182
+ this.runNext();
183
+ return this.runningPromise;
184
+ }
185
+ pauseAll() {
186
+ return this.getTasks().reduce((promise, task) => promise.then(() => task.pause()), Promise.resolve()).then(() => void 0);
187
+ }
188
+ resumeAll() {
189
+ this.files.filter((file) => file.status === "paused").forEach((file) => {
190
+ file.status = "pending";
191
+ });
192
+ this.emitQueueChange();
193
+ return this.start();
194
+ }
195
+ cancelAll() {
196
+ return this.getTasks().reduce((promise, task) => promise.then(() => task.cancel()), Promise.resolve()).then(() => void 0);
197
+ }
198
+ cancel(fileIds) {
199
+ return this.runByFileIds(fileIds, (task) => task.cancel());
200
+ }
201
+ pause(fileIds) {
202
+ return this.runByFileIds(fileIds, (task) => task.pause());
203
+ }
204
+ resume(fileIds) {
205
+ return this.runByFileIds(fileIds, (task) => task.resume());
206
+ }
207
+ retry(fileIds) {
208
+ return this.runByFileIds(fileIds, (task) => this.retryTask(task.file));
209
+ }
210
+ clear() {
211
+ if (!this.files.every((file) => file.status !== "uploading")) throw new Error("上传中不能清空队列");
212
+ this.files.splice(0, this.files.length);
213
+ this.taskMap.clear();
214
+ this.emitQueueChange();
215
+ }
216
+ getFiles() {
217
+ return [...this.files];
218
+ }
219
+ getTasks() {
220
+ return Array.from(this.taskMap.values());
221
+ }
222
+ on(eventName, handler) {
223
+ return this.emitter.on(eventName, handler);
224
+ }
225
+ runByFileIds(fileIds, runner) {
226
+ return normalizeFileIds(fileIds).reduce((promise, fileId) => {
227
+ return promise.then(() => {
228
+ const task = this.taskMap.get(fileId);
229
+ if (!task) throw new Error(`未找到文件任务:${fileId}`);
230
+ return runner(task);
231
+ });
232
+ }, Promise.resolve());
233
+ }
234
+ pauseTask(file) {
235
+ const task = this.taskMap.get(file.id);
236
+ if (!task || file.status !== "uploading") {
237
+ if (file.status === "pending") {
238
+ file.status = "paused";
239
+ this.emitQueueChange();
240
+ }
241
+ return Promise.resolve();
242
+ }
243
+ return Promise.resolve(this.options.provider.transformLifecycleRequest?.({}, {
244
+ file,
245
+ lifecycle: "pause"
246
+ }) ?? {}).then(() => this.getAdapter().pause?.({
247
+ file,
248
+ lifecycle: "pause",
249
+ prepared: task.prepared
250
+ })).then(() => {
251
+ task.abortController?.abort();
252
+ file.status = "paused";
253
+ this.emitQueueChange();
254
+ });
255
+ }
256
+ resumeTask(file) {
257
+ return Promise.resolve(this.options.provider.transformLifecycleRequest?.({}, {
258
+ file,
259
+ lifecycle: "resume"
260
+ }) ?? {}).then(() => this.getAdapter().resume?.({
261
+ file,
262
+ lifecycle: "resume",
263
+ prepared: this.taskMap.get(file.id)?.prepared
264
+ })).then(() => {
265
+ if (file.status === "paused") {
266
+ file.status = "pending";
267
+ this.emitQueueChange();
268
+ }
269
+ return this.start();
270
+ });
271
+ }
272
+ retryTask(file) {
273
+ if (file.status === "uploading") return Promise.resolve();
274
+ file.status = "pending";
275
+ file.error = void 0;
276
+ file.response = void 0;
277
+ file.rawResponse = void 0;
278
+ file.progress = {
279
+ loaded: 0,
280
+ percent: 0,
281
+ total: file.size
282
+ };
283
+ this.emitQueueChange();
284
+ return this.start();
285
+ }
286
+ cancelTask(file) {
287
+ const task = this.taskMap.get(file.id);
288
+ if (!task || file.status === "pending" || file.status === "paused") {
289
+ file.status = "canceled";
290
+ this.emitQueueChange();
291
+ return Promise.resolve();
292
+ }
293
+ return Promise.resolve(this.options.provider.transformLifecycleRequest?.({}, {
294
+ file,
295
+ lifecycle: "cancel"
296
+ }) ?? {}).then(() => this.getAdapter().cancel?.({
297
+ file,
298
+ lifecycle: "cancel",
299
+ prepared: task?.prepared
300
+ })).then(() => {
301
+ task?.abortController?.abort();
302
+ file.status = "canceled";
303
+ this.emitQueueChange();
304
+ });
305
+ }
306
+ runNext() {
307
+ const maxConcurrency = Math.max(1, this.options.maxConcurrency ?? 3);
308
+ while (this.activeCount < maxConcurrency) {
309
+ const nextFile = this.files.find((file) => file.status === "pending");
310
+ if (!nextFile) break;
311
+ this.activeCount += 1;
312
+ this.uploadFile(nextFile).finally(() => {
313
+ this.activeCount -= 1;
314
+ this.runNext();
315
+ });
316
+ }
317
+ if (this.activeCount === 0 && !this.files.some((file) => file.status === "pending")) {
318
+ this.emitter.emit("complete", { files: this.getFiles() });
319
+ this.resolveRunning?.();
320
+ }
321
+ }
322
+ uploadFile(file) {
323
+ const task = this.taskMap.get(file.id);
324
+ if (!task) return Promise.resolve();
325
+ file.status = "uploading";
326
+ task.abortController = new AbortController();
327
+ this.emitQueueChange();
328
+ return Promise.resolve(this.options.provider.prepare(file)).then((prepared) => {
329
+ task.prepared = prepared;
330
+ return this.getAdapter().upload({
331
+ file,
332
+ onProgress: (progress) => this.handleProgress(file, progress),
333
+ prepared,
334
+ signal: task.abortController?.signal ?? new AbortController().signal
335
+ });
336
+ }).then((result) => this.handleSuccess(file, result, task.prepared)).catch((error) => this.handleError(file, error));
337
+ }
338
+ handleProgress(file, progress) {
339
+ file.progress = progress;
340
+ file.checkpoint = progress.checkpoint;
341
+ this.emitter.emit("progress", {
342
+ file,
343
+ progress
344
+ });
345
+ }
346
+ handleSuccess(file, result, prepared) {
347
+ const key = result.key ?? prepared?.params.key ?? file.name;
348
+ const context = {
349
+ file,
350
+ lifecycle: "upload",
351
+ params: prepared?.params
352
+ };
353
+ return Promise.resolve(this.options.provider.response?.(result.rawResponse, context) ?? result.rawResponse).then((response) => {
354
+ file.status = "success";
355
+ file.rawResponse = result.rawResponse;
356
+ file.response = response;
357
+ file.checkpoint = result.checkpoint;
358
+ const payload = {
359
+ checkpoint: result.checkpoint,
360
+ key,
361
+ rawResponse: result.rawResponse,
362
+ response,
363
+ url: result.url
364
+ };
365
+ this.emitter.emit("success", {
366
+ file,
367
+ result: payload
368
+ });
369
+ this.emitQueueChange();
370
+ });
371
+ }
372
+ handleError(file, error) {
373
+ if (file.status === "paused" || file.status === "canceled") return;
374
+ const uploadError = {
375
+ checkpoint: file.checkpoint,
376
+ code: getErrorCode(error),
377
+ message: getErrorMessage(error),
378
+ raw: error
379
+ };
380
+ file.status = "error";
381
+ file.error = uploadError;
382
+ this.emitter.emit("error", {
383
+ error: uploadError,
384
+ file
385
+ });
386
+ this.emitQueueChange();
387
+ }
388
+ emitQueueChange() {
389
+ this.emitter.emit("change", { files: this.getFiles() });
390
+ }
391
+ getAdapter() {
392
+ const adapter = this.options.adapter ?? this.options.provider.adapter;
393
+ if (!adapter) throw new Error("上传缺少 adapter,请通过 provider 内置 adapter 或显式传入 adapter");
394
+ return adapter;
395
+ }
396
+ };
397
+ var UploadTaskImpl = class {
398
+ abortController;
399
+ prepared;
400
+ constructor(file, queue) {
401
+ this.file = file;
402
+ this.queue = queue;
403
+ }
404
+ cancel() {
405
+ return this.queue.cancelTask(this.file);
406
+ }
407
+ on(eventName, handler) {
408
+ return this.queue.on(eventName, handler);
409
+ }
410
+ pause() {
411
+ return this.queue.pauseTask(this.file);
412
+ }
413
+ resume() {
414
+ return this.queue.resumeTask(this.file);
415
+ }
416
+ };
417
+ function getErrorCode(error) {
418
+ if (error && typeof error === "object" && "code" in error && typeof error.code === "string") return error.code;
419
+ return "UPLOAD_ERROR";
420
+ }
421
+ function getErrorMessage(error) {
422
+ if (error instanceof Error) return error.message;
423
+ if (error && typeof error === "object" && "message" in error && typeof error.message === "string") return error.message;
424
+ return "文件上传失败";
425
+ }
426
+ function normalizeFileIds(fileIds) {
427
+ return Array.isArray(fileIds) ? fileIds : [fileIds];
428
+ }
429
+
430
+ //#endregion
431
+ //#region src/oss/runtime.ts
432
+ const DEFAULT_OSS_STS_URL = "/education/serverApi/oss-sts/token";
433
+ function loadOssSdk(options) {
434
+ return Promise.resolve(options.loadSdk?.() ?? import("ali-oss").then((module) => module.default));
435
+ }
436
+ function buildOssClientOptions(options, token, refreshSTSToken) {
437
+ const runtimeConfig = resolveRuntimeConfig(options, token);
438
+ return {
439
+ accessKeyId: token.accessKeyId,
440
+ accessKeySecret: token.accessKeySecret,
441
+ authorizationV4: options.authorizationV4 ?? true,
442
+ bucket: runtimeConfig.bucket,
443
+ cname: runtimeConfig.cname,
444
+ endpoint: runtimeConfig.endpoint,
445
+ region: runtimeConfig.region,
446
+ ...refreshSTSToken ? { refreshSTSToken } : {},
447
+ secure: runtimeConfig.secure,
448
+ stsToken: token.stsToken,
449
+ timeout: options.timeout
450
+ };
451
+ }
452
+ function resolveRuntimeConfig(options, token) {
453
+ const bucket = options.bucket ?? token.bucket;
454
+ const endpoint = options.endpoint ?? token.endpoint;
455
+ const region = options.region ?? token.region ?? getRegionFromEndpoint(endpoint);
456
+ if (!bucket || !region) throw new Error("OSS 缺少 bucket 或 endpoint/region,请确认 STS 响应或 OSS 配置");
457
+ return {
458
+ bucket,
459
+ cname: options.cname ?? token.cname,
460
+ endpoint,
461
+ region,
462
+ secure: options.secure ?? token.secure
463
+ };
464
+ }
465
+ function requestAndTransformStsToken(context, options) {
466
+ const request = {
467
+ headers: {},
468
+ method: "GET",
469
+ query: {},
470
+ url: options.stsUrl ?? DEFAULT_OSS_STS_URL
471
+ };
472
+ return Promise.resolve(options.beforeStsTokenReq?.(request, context) ?? request).then((nextRequest) => requestStsToken(nextRequest, context, options)).then((stsResponse) => {
473
+ return Promise.resolve(options.beforeStsTokenRes?.(stsResponse, context) ?? defaultTransformStsResponse(stsResponse));
474
+ });
475
+ }
476
+ function createRefreshStsToken(context, options) {
477
+ if (!options.refreshStsToken && !options.refreshStsUrl) return;
478
+ if (options.refreshStsToken) {
479
+ const refreshStsToken = options.refreshStsToken;
480
+ return () => refreshStsToken(context);
481
+ }
482
+ return () => requestAndTransformRefreshStsToken(context, options);
483
+ }
484
+ function getOssRuntimeConfig(token) {
485
+ const region = token.region ?? getRegionFromEndpoint(token.endpoint);
486
+ if (!token.bucket || !region) throw new Error("STS 响应缺少 bucket 或 endpoint/region");
487
+ return {
488
+ bucket: token.bucket,
489
+ cname: token.cname,
490
+ endpoint: token.endpoint,
491
+ region,
492
+ secure: token.secure
493
+ };
494
+ }
495
+ function requestStsToken(request, context, options) {
496
+ return Promise.resolve(options.requestStsToken?.(request, context) ?? sendRequest(request));
497
+ }
498
+ function requestAndTransformRefreshStsToken(context, options) {
499
+ const request = {
500
+ headers: {},
501
+ method: "GET",
502
+ query: {},
503
+ url: options.refreshStsUrl
504
+ };
505
+ return Promise.resolve(options.beforeRefreshStsTokenReq?.(request, context) ?? request).then((nextRequest) => requestRefreshStsToken(nextRequest, context, options)).then((refreshResponse) => {
506
+ return Promise.resolve(options.beforeRefreshStsTokenRes?.(refreshResponse, context) ?? defaultTransformRefreshStsResponse(refreshResponse));
507
+ });
508
+ }
509
+ function requestRefreshStsToken(request, context, options) {
510
+ return Promise.resolve(options.requestRefreshStsToken?.(request, context) ?? sendRequest(request));
511
+ }
512
+ function sendRequest(request) {
513
+ if (!request.url) return Promise.reject(/* @__PURE__ */ new Error("STS 请求地址不能为空"));
514
+ const method = request.method ?? "GET";
515
+ const headers = request.headers ?? {};
516
+ const url = buildUrl(request.url, request.query);
517
+ const init = {
518
+ headers,
519
+ method
520
+ };
521
+ if (method.toUpperCase() !== "GET" && typeof request.body !== "undefined") {
522
+ init.body = typeof request.body === "string" ? request.body : JSON.stringify(request.body);
523
+ init.headers = {
524
+ "Content-Type": "application/json",
525
+ ...headers
526
+ };
527
+ }
528
+ return fetch(url, init).then((response) => {
529
+ if (!response.ok) throw new Error(`STS 请求失败:${response.status}`);
530
+ return response.json();
531
+ });
532
+ }
533
+ function defaultTransformRefreshStsResponse(response) {
534
+ const token = defaultTransformStsResponse(response);
535
+ return {
536
+ accessKeyId: token.accessKeyId,
537
+ accessKeySecret: token.accessKeySecret,
538
+ stsToken: token.stsToken
539
+ };
540
+ }
541
+ function defaultTransformStsResponse(response) {
542
+ const candidates = [
543
+ response,
544
+ getObjectValue(response, "data"),
545
+ getObjectValue(getObjectValue(response, "data"), "credentials"),
546
+ getObjectValue(response, "credentials")
547
+ ];
548
+ for (const candidate of candidates) {
549
+ if (!isRecord(candidate)) continue;
550
+ const accessKeyId = getStringValue(candidate, ["accessKeyId", "AccessKeyId"]);
551
+ const accessKeySecret = getStringValue(candidate, ["accessKeySecret", "AccessKeySecret"]);
552
+ const stsToken = getStringValue(candidate, [
553
+ "stsToken",
554
+ "securityToken",
555
+ "SecurityToken"
556
+ ]);
557
+ const expiration = getStringValue(candidate, ["expiration", "Expiration"]);
558
+ const bucket = getStringValue(candidate, [
559
+ "bucket",
560
+ "bucketName",
561
+ "Bucket",
562
+ "BucketName"
563
+ ]);
564
+ const endpoint = getStringValue(candidate, ["endpoint", "Endpoint"]);
565
+ const region = getStringValue(candidate, ["region", "Region"]) ?? getRegionFromEndpoint(endpoint);
566
+ const uploadPrefix = getStringValue(candidate, [
567
+ "uploadPrefix",
568
+ "prefix",
569
+ "Prefix"
570
+ ]);
571
+ if (accessKeyId && accessKeySecret && stsToken) return {
572
+ accessKeyId,
573
+ accessKeySecret,
574
+ bucket,
575
+ endpoint,
576
+ expiration,
577
+ region,
578
+ stsToken,
579
+ uploadPrefix
580
+ };
581
+ }
582
+ throw new Error("STS 响应缺少 accessKeyId/accessKeySecret/stsToken,请配置 beforeStsTokenRes");
583
+ }
584
+ function getRegionFromEndpoint(endpoint) {
585
+ if (!endpoint) return;
586
+ return getEndpointHostname(endpoint).match(/^(?:[^.]+--)?(oss-[a-z0-9-]+)\./)?.[1];
587
+ }
588
+ function getEndpointHostname(endpoint) {
589
+ try {
590
+ return new URL(endpoint).hostname;
591
+ } catch {
592
+ return endpoint.replace(/^https?:\/\//, "").split("/")[0] ?? endpoint;
593
+ }
594
+ }
595
+ function buildUrl(url, query = {}) {
596
+ const baseUrl = typeof window !== "undefined" && window.location?.origin ? window.location.origin : "http://localhost";
597
+ const targetUrl = new URL(url, baseUrl);
598
+ Object.entries(query).forEach(([key, value]) => {
599
+ if (typeof value === "undefined" || value === null) return;
600
+ targetUrl.searchParams.set(key, String(value));
601
+ });
602
+ return targetUrl.toString();
603
+ }
604
+ function getObjectValue(source, key) {
605
+ if (!isRecord(source)) return;
606
+ return source[key];
607
+ }
608
+ function getStringValue(source, keys) {
609
+ for (const key of keys) {
610
+ const value = source[key];
611
+ if (typeof value === "string" || typeof value === "number") return String(value);
612
+ }
613
+ }
614
+ function isRecord(value) {
615
+ return typeof value === "object" && value !== null;
616
+ }
617
+
618
+ //#endregion
619
+ //#region src/oss/adapter.ts
620
+ function createOssAdapter(options) {
621
+ const runtime = /* @__PURE__ */ new Map();
622
+ return {
623
+ name: "oss",
624
+ cancel(context) {
625
+ return cancelUpload(context, runtime);
626
+ },
627
+ pause(context) {
628
+ runtime.get(context.file.id)?.cancel?.();
629
+ return Promise.resolve();
630
+ },
631
+ resume() {
632
+ return Promise.resolve();
633
+ },
634
+ upload(context) {
635
+ const token = context.prepared.token;
636
+ if (!token) return Promise.reject(/* @__PURE__ */ new Error("OSS 上传缺少 STS Token"));
637
+ return loadOssSdk(options).then((OssClient) => {
638
+ const client = new OssClient(buildOssClientOptions(options, token, getRefreshStsToken(context)));
639
+ const runtimeItem = {
640
+ checkpoint: context.file.checkpoint,
641
+ client
642
+ };
643
+ runtime.set(context.file.id, runtimeItem);
644
+ return runMultipartUpload(context, options, runtimeItem).finally(() => {
645
+ runtime.delete(context.file.id);
646
+ });
647
+ });
648
+ }
649
+ };
650
+ }
651
+ function getRefreshStsToken(context) {
652
+ const refreshSTSToken = context.prepared.extra?.refreshSTSToken;
653
+ return typeof refreshSTSToken === "function" ? refreshSTSToken : void 0;
654
+ }
655
+ function runMultipartUpload(context, options, runtime) {
656
+ const params = context.prepared.params;
657
+ const uploadOptions = {
658
+ callback: params.callback,
659
+ checkpoint: runtime.checkpoint,
660
+ headers: params.headers,
661
+ meta: params.meta,
662
+ mime: params.mime,
663
+ parallel: params.parallel ?? options.parallel,
664
+ partSize: params.partSize ?? options.partSize,
665
+ progress(progress, checkpoint, raw) {
666
+ runtime.checkpoint = checkpoint;
667
+ context.onProgress({
668
+ checkpoint,
669
+ loaded: Math.round((context.file.size || 0) * progress),
670
+ percent: Math.round(progress * 1e4) / 100,
671
+ raw,
672
+ total: context.file.size
673
+ });
674
+ }
675
+ };
676
+ if (typeof AbortController !== "undefined") context.signal.addEventListener("abort", () => {
677
+ runtime.cancel?.();
678
+ });
679
+ const uploadPromise = runtime.client.multipartUpload(params.key, params.file, uploadOptions);
680
+ if (uploadPromise && typeof uploadPromise === "object" && "cancel" in uploadPromise) runtime.cancel = () => {
681
+ const cancel = uploadPromise.cancel;
682
+ if (typeof cancel === "function") cancel.call(uploadPromise);
683
+ };
684
+ return Promise.resolve(uploadPromise).then((rawResponse) => ({
685
+ checkpoint: runtime.checkpoint,
686
+ key: params.key,
687
+ rawResponse,
688
+ url: getResponseUrl(rawResponse)
689
+ }));
690
+ }
691
+ function cancelUpload(context, runtime) {
692
+ const runtimeItem = runtime.get(context.file.id);
693
+ const checkpoint = runtimeItem?.checkpoint ?? context.file.checkpoint;
694
+ runtimeItem?.cancel?.();
695
+ if (!runtimeItem?.client || !checkpoint?.name || !checkpoint.uploadId) return Promise.resolve();
696
+ return Promise.resolve(runtimeItem.client.abortMultipartUpload(checkpoint.name, checkpoint.uploadId)).then(() => void 0);
697
+ }
698
+ function getResponseUrl(response) {
699
+ if (response && typeof response === "object" && "url" in response && typeof response.url === "string") return response.url;
700
+ if (response && typeof response === "object" && "res" in response && response.res && typeof response.res === "object" && "requestUrls" in response.res && Array.isArray(response.res.requestUrls)) return response.res.requestUrls[0];
701
+ }
702
+
703
+ //#endregion
704
+ //#region src/oss/provider.ts
705
+ function createOssProvider(options = {}) {
706
+ return {
707
+ adapter: createOssAdapter(options),
708
+ name: "oss",
709
+ prepare(file) {
710
+ return requestAndTransformStsToken({
711
+ file,
712
+ lifecycle: "sts"
713
+ }, options).then((token) => buildUploadParams(file, token, options));
714
+ },
715
+ transformLifecycleRequest(request, context) {
716
+ return Promise.resolve(options.transformLifecycleRequest?.(request, context) ?? request);
717
+ },
718
+ response(response, context) {
719
+ return Promise.resolve(options.response?.(response, context) ?? response);
720
+ }
721
+ };
722
+ }
723
+ function buildUploadParams(file, token, options) {
724
+ const context = {
725
+ file,
726
+ lifecycle: "upload",
727
+ token
728
+ };
729
+ return Promise.resolve(options.keyResolver?.(file, context) ?? defaultKeyResolver(file, token, options)).then((key) => {
730
+ const refreshSTSToken = createRefreshStsToken({
731
+ file,
732
+ lifecycle: "sts"
733
+ }, options);
734
+ const params = {
735
+ callback: options.callback,
736
+ extra: {
737
+ ...options.extra ?? {},
738
+ ...refreshSTSToken ? { refreshSTSToken } : {},
739
+ token
740
+ },
741
+ file: file.file,
742
+ headers: {},
743
+ key,
744
+ meta: {},
745
+ mime: options.mime ?? file.type,
746
+ parallel: options.parallel,
747
+ partSize: options.partSize
748
+ };
749
+ const uploadContext = {
750
+ file,
751
+ lifecycle: "upload",
752
+ params,
753
+ token
754
+ };
755
+ return Promise.resolve(options.beforeUploadReq?.(params, uploadContext) ?? params).then((nextParams) => ({
756
+ extra: {
757
+ ...refreshSTSToken ? { refreshSTSToken } : {},
758
+ token
759
+ },
760
+ params: nextParams,
761
+ token
762
+ }));
763
+ });
764
+ }
765
+ function defaultKeyResolver(file, token, options) {
766
+ const safeName = file.name.replace(/^\.+/, "").replace(/[^\w.\\/=-]+/g, "-");
767
+ return `${options.uploadPrefix ? normalizePrefix(options.uploadPrefix) : token.uploadPrefix ? normalizePrefix(token.uploadPrefix) : "uploads/"}${Date.now()}-${safeName}`;
768
+ }
769
+ function normalizePrefix(prefix) {
770
+ if (!prefix) return "";
771
+ return prefix.endsWith("/") ? prefix : `${prefix}/`;
772
+ }
773
+
774
+ //#endregion
775
+ //#region src/oss/client.ts
776
+ function createOssClient(options = {}) {
777
+ const context = { lifecycle: "sts" };
778
+ return requestAndTransformStsToken(context, options).then((token) => {
779
+ const refreshSTSToken = createRefreshStsToken(context, options);
780
+ return loadOssSdk(options).then((OssClient) => {
781
+ return new OssClient(buildOssClientOptions(options, token, refreshSTSToken));
782
+ });
783
+ });
784
+ }
785
+
786
+ //#endregion
787
+ //#region src/uploader.ts
788
+ let defaultOssProviderOptions = {};
789
+ function createOssUploader(options = {}) {
790
+ return createUploader({
791
+ ...options,
792
+ provider: options.provider ?? createOssProvider(defaultOssProviderOptions)
793
+ });
794
+ }
795
+ function setDefaultOssProviderOptions(options) {
796
+ defaultOssProviderOptions = options;
797
+ }
798
+
799
+ //#endregion
800
+ export { DEFAULT_OSS_STS_URL, createOssAdapter, createOssClient, createOssProvider, createOssUploader, getOssRuntimeConfig, setDefaultOssProviderOptions };
801
+ //# sourceMappingURL=index.js.map