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