@windwalker-io/unicorn-next 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,12 +40,21 @@ class S3MultipartUploader extends (/* @__PURE__ */ Mixin(EventMixin)) {
40
40
  if (this.options.leaveAlert === true) {
41
41
  window.addEventListener("beforeunload", beforeUnloadHandler);
42
42
  }
43
- const { id } = await this.request(
44
- "init",
45
- initData
46
- );
47
- this.trigger("inited", { id, path });
43
+ let uploadId = null;
44
+ let isCancel = false;
45
+ let signal = options.abortController?.signal;
46
+ if (signal) {
47
+ signal.addEventListener("abort", (e) => {
48
+ isCancel = true;
49
+ });
50
+ }
48
51
  try {
52
+ const { id } = await this.request(
53
+ "init",
54
+ initData
55
+ );
56
+ uploadId = id;
57
+ this.trigger("inited", { id, path });
49
58
  const chunkSize = this.options.chunkSize;
50
59
  const chunks = Math.ceil(file.size / chunkSize);
51
60
  let uploadedBytes = 0;
@@ -64,6 +73,7 @@ class S3MultipartUploader extends (/* @__PURE__ */ Mixin(EventMixin)) {
64
73
  path,
65
74
  partNumber,
66
75
  chunkSize,
76
+ abortController: options.abortController,
67
77
  onUploadProgress: (e) => {
68
78
  partsUploaded[partNumber] = e.loaded;
69
79
  const uploaded = Object.values(partsUploaded).reduce((sum, a) => sum + a, 0);
@@ -78,6 +88,11 @@ class S3MultipartUploader extends (/* @__PURE__ */ Mixin(EventMixin)) {
78
88
  currentPart++;
79
89
  }
80
90
  await Promise.all(promises);
91
+ if (isCancel) {
92
+ const e = new Error("Upload cancelled");
93
+ e.name = "CanceledError";
94
+ throw e;
95
+ }
81
96
  const { url } = await this.request(
82
97
  "complete",
83
98
  {
@@ -90,8 +105,10 @@ class S3MultipartUploader extends (/* @__PURE__ */ Mixin(EventMixin)) {
90
105
  this.trigger("success", { id, path, url });
91
106
  return { url, id, path };
92
107
  } catch (e) {
93
- await this.abort(id, path);
94
- this.trigger("failure", { error: e, id, path });
108
+ if (uploadId) {
109
+ await this.abort(uploadId, path);
110
+ }
111
+ this.trigger("failure", { error: e, uploadId, path });
95
112
  throw e;
96
113
  } finally {
97
114
  if (this.options.leaveAlert === true) {
@@ -112,6 +129,9 @@ class S3MultipartUploader extends (/* @__PURE__ */ Mixin(EventMixin)) {
112
129
  path,
113
130
  partNumber,
114
131
  profile: this.options.profile
132
+ },
133
+ {
134
+ signal: payload.abortController?.signal
115
135
  }
116
136
  );
117
137
  const res = await http.put(
@@ -124,12 +144,12 @@ class S3MultipartUploader extends (/* @__PURE__ */ Mixin(EventMixin)) {
124
144
  const etag = String(res.headers.get("ETag") || "");
125
145
  return { blob, etag };
126
146
  }
127
- async request(action, body) {
147
+ async request(action, body, config = {}) {
128
148
  if (this.options.requestHandler) {
129
149
  return this.options.requestHandler(action, body);
130
150
  }
131
151
  const http = await useHttpClient();
132
- const res = await http.post(await this.resolveRoute(action), body);
152
+ const res = await http.post(await this.resolveRoute(action), body, config);
133
153
  return res.data.data;
134
154
  }
135
155
  // protected async abortBeacon(id: string, path: string): Promise<void> {
@@ -1 +1 @@
1
- {"version":3,"file":"s3-multipart-uploader.js","sources":["../../src/module/s3-multipart-uploader.ts"],"sourcesContent":["import { AxiosProgressEvent, AxiosResponseHeaders } from 'axios';\nimport { Mixin } from 'ts-mixer';\nimport { createQueue, useHttpClient } from '../composable';\nimport { EventHandler, EventMixin } from '../events';\nimport { route } from '../service';\nimport type { MaybePromise } from '../types';\nimport { mergeDeep } from '../utilities';\nimport { ApiReturn } from './http-client';\n\ndeclare type RoutingOptions = {\n init: string;\n sign: string;\n complete: string;\n abort: string;\n} | ((action: RouteActions) => MaybePromise<string>);\n\ndeclare type RouteActions = 'init' | 'sign' | 'complete' | 'abort';\ndeclare type RequestHandler = <T = Record<string, any>>(action: RouteActions, data: Record<string, any>) => Promise<T>;\n\nexport interface S3MultipartUploaderOptions {\n profile?: string;\n chunkSize: number;\n concurrency: number;\n leaveAlert?: boolean;\n routes: RoutingOptions;\n requestHandler?: RequestHandler;\n onProgress?: ProgressEventHandler;\n ACL?: string;\n extra?: Record<string, any>;\n\n // maxRetries?: number;\n // endpoint: string;\n // subfolder?: string;\n}\n\nconst defaultOptions: Partial<S3MultipartUploaderOptions> = {\n chunkSize: 5 * 1024 * 1024, // 5MB\n concurrency: 2,\n};\n\nexport interface S3MultipartUploaderRequestOptions {\n onProgress?: ProgressEventHandler;\n filename?: string;\n ContentType?: string;\n ContentDisposition?: string;\n ACL?: 'public-read' | 'private' | 'authenticated-read' | 'public-read-write' | string;\n extra?: Record<string, any>;\n}\n\nexport class S3MultipartUploader extends Mixin(EventMixin) {\n options: S3MultipartUploaderOptions;\n\n constructor(options: Partial<S3MultipartUploaderOptions>) {\n super();\n this.options = mergeDeep({}, defaultOptions, options);\n }\n\n async upload(\n file: string | File | Blob,\n path: string,\n options: S3MultipartUploaderRequestOptions = {}\n ): Promise<{ url: string; id: string; path: string; }> {\n const extra: Record<string, any> = { ...(this.options.extra ?? {}), ...(options.extra ?? {}) };\n\n if (typeof file === 'string') {\n file = new Blob([file], { type: options['ContentType'] || 'text/plain' });\n }\n\n if (file instanceof Blob && !(file instanceof File)) {\n if (path.endsWith('.{ext}')) {\n throw new Error('If using Blob or file data string, you must provide a valid file extension in the path.');\n }\n\n file = new File([file], 'blob', { type: file.type });\n }\n\n if (file instanceof File) {\n extra['ContentType'] = options['ContentType'] || file.type;\n }\n\n if (options.ACL || this.options.ACL) {\n extra.ACL = options.ACL || this.options.ACL;\n }\n\n path = this.replaceExt(path, file);\n\n const initData: Record<string, any> = { extra, path, profile: this.options.profile };\n\n if (options['filename']) {\n initData['filename'] = options['filename'];\n }\n\n this.trigger('start', file, initData);\n\n // Prepare unload\n const beforeUnloadHandler = (e: BeforeUnloadEvent) => {\n e.preventDefault();\n e.returnValue = '';\n };\n if (this.options.leaveAlert === true) {\n window.addEventListener('beforeunload', beforeUnloadHandler);\n }\n\n // @Request init\n const { id } = await this.request<{ id: string; }>(\n 'init',\n initData\n );\n\n this.trigger('inited', { id, path });\n\n try {\n const chunkSize = this.options.chunkSize;\n const chunks = Math.ceil(file.size / chunkSize);\n\n let uploadedBytes = 0;\n let parts: { ETag: string, PartNumber: number }[] = [];\n let currentPart = 1;\n const queue = createQueue(this.options.concurrency);\n const promises = [];\n const partsUploaded: Record<number, number> = {};\n\n // Loop from 1 to chunks\n while (currentPart <= chunks) {\n const partNumber = currentPart;\n\n // Push to queue\n const p = queue.push(async () => {\n const { blob, etag } = await this.uploadPart(\n file as File,\n {\n id,\n path,\n partNumber,\n chunkSize,\n onUploadProgress: (e) => {\n partsUploaded[partNumber] = e.loaded;\n\n const uploaded = Object.values(partsUploaded).reduce((sum, a) => sum + a, 0);\n\n this.updateProgress(uploaded, file.size, options);\n }\n }\n );\n\n uploadedBytes += blob.size;\n\n // Use parts progress, ignore the overall progress, which may be inaccurate due to retries or concurrency\n // this.updateProgress(uploadedBytes, file.size, options);\n\n parts.push({ ETag: etag, PartNumber: partNumber });\n });\n\n promises.push(p);\n\n currentPart++;\n }\n\n await Promise.all(promises);\n\n // @Request sign\n const { url } = await this.request<{ url: string }>(\n 'complete',\n {\n id,\n path,\n parts: parts.sort((a, b) => a.PartNumber - b.PartNumber),\n profile: this.options.profile,\n },\n );\n\n this.trigger('success', { id, path, url });\n\n return { url, id, path };\n } catch (e) {\n await this.abort(id, path);\n\n this.trigger('failure', { error: e as Error, id, path });\n\n throw e;\n } finally {\n if (this.options.leaveAlert === true) {\n window.removeEventListener('beforeunload', beforeUnloadHandler);\n }\n }\n }\n\n protected async uploadPart(\n file: File,\n payload: {\n id: string;\n path: string;\n partNumber: number;\n chunkSize: number;\n onUploadProgress: (e: AxiosProgressEvent) => void;\n }\n ) {\n const http = await useHttpClient();\n const { id, path, partNumber, chunkSize, onUploadProgress } = payload;\n\n const start = (partNumber - 1) * chunkSize;\n const end = Math.min(partNumber * chunkSize, file.size);\n\n const blob = file.slice(start, end);\n\n // @Request sign\n const { url } = await this.request<{ url: string; }>(\n 'sign',\n {\n id,\n path,\n partNumber,\n profile: this.options.profile,\n }\n );\n\n // PUT to S3\n const res = await http.put(\n url,\n blob,\n {\n onUploadProgress,\n }\n );\n\n const etag = String((res.headers as AxiosResponseHeaders).get('ETag') || '');\n\n return { blob, etag };\n }\n\n protected async request<T = Record<string, any>>(action: RouteActions, body: Record<string, any>): Promise<T> {\n if (this.options.requestHandler) {\n return this.options.requestHandler<T>(action, body);\n }\n\n const http = await useHttpClient();\n\n const res = await http.post<ApiReturn<T>>(await this.resolveRoute(action), body);\n\n return res.data.data;\n }\n\n // protected async abortBeacon(id: string, path: string): Promise<void> {\n // const data = new FormData();\n // data.append('id', id);\n // data.append('path', path);\n // data.append('profile', this.options.profile || '');\n //\n // await navigator.sendBeacon(route(await this.resolveRoute('abort')), data);\n // }\n\n async abort(id: string, path: string) {\n await this.request(\n 'abort',\n {\n id,\n path,\n profile: this.options.profile,\n }\n );\n }\n\n updateProgress(loaded: number, total: number, options: S3MultipartUploaderRequestOptions) {\n const percentage = (loaded / total) * 100;\n\n const event: ProgressEvent = { percentage, loaded, total };\n\n this.trigger('progress', event);\n\n this.options.onProgress?.(event);\n\n if (options.onProgress) {\n options.onProgress(event);\n }\n }\n\n async resolveRoute(action: RouteActions): Promise<string> {\n if (typeof this.options.routes === 'function') {\n return this.options.routes(action);\n }\n\n return this.options.routes[action];\n }\n\n setChunkSize(size: number): this {\n this.options.chunkSize = size;\n\n return this;\n }\n\n setChunkSizeInMiB(size: number): this {\n this.options.chunkSize = size * 1024 * 1024;\n\n return this;\n }\n\n replaceExt(path: string, file: File | Blob): string {\n if (file instanceof File) {\n const fileExt = file.name.split('.').pop();\n\n if (path.endsWith('.{ext}')) {\n return path.replace(/\\.{ext}$/, fileExt ? '.' + fileExt : '');\n }\n }\n\n return path;\n }\n\n on(\n event: 'start',\n handler: (file: File, data: { path: string; extra: Record<string, any>; [name: string]: any; }) => void\n ): this;\n on(event: 'inited', handler: (event: { id: string; path: string; }) => void): this;\n on(event: 'success', handler: (event: { url: string; id: string; path: string; }) => void): this;\n on(event: 'progress', handler: (event: ProgressEvent) => void): this;\n on(event: 'failure', handler: (event: { error: Error; id: string; path: string; }) => void): this;\n on(event: string | string[], handler: EventHandler): this {\n return super.on(event, handler);\n }\n}\n\ntype ProgressEvent = {\n percentage: number;\n loaded: number;\n total: number;\n};\ntype ProgressEventHandler = (e: ProgressEvent) => void;\n\nexport interface S3MultipartUploaderModule {\n S3MultipartUploader: typeof S3MultipartUploader;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"s3-multipart-uploader.js","sources":["../../src/module/s3-multipart-uploader.ts"],"sourcesContent":["import { AxiosProgressEvent, type AxiosRequestConfig, AxiosResponseHeaders } from 'axios';\nimport { Mixin } from 'ts-mixer';\nimport { createQueue, useHttpClient } from '../composable';\nimport { EventHandler, EventMixin } from '../events';\nimport type { MaybePromise } from '../types';\nimport { mergeDeep } from '../utilities';\nimport { ApiReturn } from './http-client';\n\ndeclare type RoutingOptions = {\n init: string;\n sign: string;\n complete: string;\n abort: string;\n} | ((action: RouteActions) => MaybePromise<string>);\n\ndeclare type RouteActions = 'init' | 'sign' | 'complete' | 'abort';\ndeclare type RequestHandler = <T = Record<string, any>>(action: RouteActions, data: Record<string, any>) => Promise<T>;\n\nexport interface S3MultipartUploaderOptions {\n profile?: string;\n chunkSize: number;\n concurrency: number;\n leaveAlert?: boolean;\n routes: RoutingOptions;\n requestHandler?: RequestHandler;\n onProgress?: ProgressEventHandler;\n ACL?: string;\n extra?: Record<string, any>;\n\n // maxRetries?: number;\n // endpoint: string;\n // subfolder?: string;\n}\n\nconst defaultOptions: Partial<S3MultipartUploaderOptions> = {\n chunkSize: 5 * 1024 * 1024, // 5MB\n concurrency: 2,\n};\n\nexport interface S3MultipartUploaderRequestOptions {\n onProgress?: ProgressEventHandler;\n filename?: string;\n ContentType?: string;\n ContentDisposition?: string;\n ACL?: 'public-read' | 'private' | 'authenticated-read' | 'public-read-write' | string;\n extra?: Record<string, any>;\n abortController?: AbortController;\n}\n\nexport class S3MultipartUploader extends Mixin(EventMixin) {\n options: S3MultipartUploaderOptions;\n\n constructor(options: Partial<S3MultipartUploaderOptions>) {\n super();\n this.options = mergeDeep({}, defaultOptions, options);\n }\n\n async upload(\n file: string | File | Blob,\n path: string,\n options: S3MultipartUploaderRequestOptions = {}\n ): Promise<{ url: string; id: string; path: string; }> {\n const extra: Record<string, any> = { ...(this.options.extra ?? {}), ...(options.extra ?? {}) };\n\n if (typeof file === 'string') {\n file = new Blob([file], { type: options['ContentType'] || 'text/plain' });\n }\n\n if (file instanceof Blob && !(file instanceof File)) {\n if (path.endsWith('.{ext}')) {\n throw new Error('If using Blob or file data string, you must provide a valid file extension in the path.');\n }\n\n file = new File([file], 'blob', { type: file.type });\n }\n\n if (file instanceof File) {\n extra['ContentType'] = options['ContentType'] || file.type;\n }\n\n if (options.ACL || this.options.ACL) {\n extra.ACL = options.ACL || this.options.ACL;\n }\n\n path = this.replaceExt(path, file);\n\n const initData: Record<string, any> = { extra, path, profile: this.options.profile };\n\n if (options['filename']) {\n initData['filename'] = options['filename'];\n }\n\n this.trigger('start', file, initData);\n\n // Prepare unload\n const beforeUnloadHandler = (e: BeforeUnloadEvent) => {\n e.preventDefault();\n e.returnValue = '';\n };\n\n if (this.options.leaveAlert === true) {\n window.addEventListener('beforeunload', beforeUnloadHandler);\n }\n\n let uploadId: string | null = null;\n let isCancel = false;\n let signal = options.abortController?.signal;\n\n if (signal) {\n signal.addEventListener('abort', (e) => {\n isCancel = true;\n });\n }\n\n try {\n // @Request init\n const { id } = await this.request<{ id: string; }>(\n 'init',\n initData\n );\n\n uploadId = id;\n\n this.trigger('inited', { id, path });\n\n const chunkSize = this.options.chunkSize;\n const chunks = Math.ceil(file.size / chunkSize);\n\n let uploadedBytes = 0;\n let parts: { ETag: string, PartNumber: number }[] = [];\n let currentPart = 1;\n const queue = createQueue(this.options.concurrency);\n const promises = [];\n const partsUploaded: Record<number, number> = {};\n\n // Loop from 1 to chunks\n while (currentPart <= chunks) {\n const partNumber = currentPart;\n\n // Push to queue\n const p = queue.push(async () => {\n const { blob, etag } = await this.uploadPart(\n file as File,\n {\n id,\n path,\n partNumber,\n chunkSize,\n abortController: options.abortController,\n onUploadProgress: (e) => {\n partsUploaded[partNumber] = e.loaded;\n\n const uploaded = Object.values(partsUploaded).reduce((sum, a) => sum + a, 0);\n\n this.updateProgress(uploaded, file.size, options);\n }\n }\n );\n\n uploadedBytes += blob.size;\n\n // Use parts progress, ignore the overall progress, which may be inaccurate due to retries or concurrency\n // this.updateProgress(uploadedBytes, file.size, options);\n\n parts.push({ ETag: etag, PartNumber: partNumber });\n });\n\n promises.push(p);\n\n currentPart++;\n }\n\n await Promise.all(promises);\n\n if (isCancel) {\n const e = new Error('Upload cancelled');\n e.name = 'CanceledError';\n\n throw e;\n }\n\n // @Request complete\n const { url } = await this.request<{ url: string }>(\n 'complete',\n {\n id,\n path,\n parts: parts.sort((a, b) => a.PartNumber - b.PartNumber),\n profile: this.options.profile,\n },\n );\n\n this.trigger('success', { id, path, url });\n\n return { url, id, path };\n } catch (e) {\n if (uploadId) {\n await this.abort(uploadId, path);\n }\n\n this.trigger('failure', { error: e as Error, uploadId, path });\n\n throw e;\n } finally {\n if (this.options.leaveAlert === true) {\n window.removeEventListener('beforeunload', beforeUnloadHandler);\n }\n }\n }\n\n protected async uploadPart(\n file: File,\n payload: {\n id: string;\n path: string;\n partNumber: number;\n chunkSize: number;\n abortController?: AbortController;\n onUploadProgress: (e: AxiosProgressEvent) => void;\n }\n ) {\n const http = await useHttpClient();\n const { id, path, partNumber, chunkSize, onUploadProgress } = payload;\n\n const start = (partNumber - 1) * chunkSize;\n const end = Math.min(partNumber * chunkSize, file.size);\n\n const blob = file.slice(start, end);\n\n // @Request sign\n const { url } = await this.request<{ url: string; }>(\n 'sign',\n {\n id,\n path,\n partNumber,\n profile: this.options.profile,\n },\n {\n signal: payload.abortController?.signal,\n }\n );\n\n // PUT to S3\n const res = await http.put(\n url,\n blob,\n {\n onUploadProgress,\n }\n );\n\n const etag = String((res.headers as AxiosResponseHeaders).get('ETag') || '');\n\n return { blob, etag };\n }\n\n protected async request<T = Record<string, any>>(\n action: RouteActions,\n body: Record<string, any>,\n config: Partial<AxiosRequestConfig> = {}\n ): Promise<T> {\n if (this.options.requestHandler) {\n return this.options.requestHandler<T>(action, body);\n }\n\n const http = await useHttpClient();\n\n const res = await http.post<ApiReturn<T>>(await this.resolveRoute(action), body, config);\n\n return res.data.data;\n }\n\n // protected async abortBeacon(id: string, path: string): Promise<void> {\n // const data = new FormData();\n // data.append('id', id);\n // data.append('path', path);\n // data.append('profile', this.options.profile || '');\n //\n // await navigator.sendBeacon(route(await this.resolveRoute('abort')), data);\n // }\n\n async abort(id: string, path: string) {\n await this.request(\n 'abort',\n {\n id,\n path,\n profile: this.options.profile,\n }\n );\n }\n\n updateProgress(loaded: number, total: number, options: S3MultipartUploaderRequestOptions) {\n const percentage = (loaded / total) * 100;\n\n const event: ProgressEvent = { percentage, loaded, total };\n\n this.trigger('progress', event);\n\n this.options.onProgress?.(event);\n\n if (options.onProgress) {\n options.onProgress(event);\n }\n }\n\n async resolveRoute(action: RouteActions): Promise<string> {\n if (typeof this.options.routes === 'function') {\n return this.options.routes(action);\n }\n\n return this.options.routes[action];\n }\n\n setChunkSize(size: number): this {\n this.options.chunkSize = size;\n\n return this;\n }\n\n setChunkSizeInMiB(size: number): this {\n this.options.chunkSize = size * 1024 * 1024;\n\n return this;\n }\n\n replaceExt(path: string, file: File | Blob): string {\n if (file instanceof File) {\n const fileExt = file.name.split('.').pop();\n\n if (path.endsWith('.{ext}')) {\n return path.replace(/\\.{ext}$/, fileExt ? '.' + fileExt : '');\n }\n }\n\n return path;\n }\n\n on(\n event: 'start',\n handler: (file: File, data: { path: string; extra: Record<string, any>; [name: string]: any; }) => void\n ): this;\n on(event: 'inited', handler: (event: { id: string; path: string; }) => void): this;\n on(event: 'success', handler: (event: { url: string; id: string; path: string; }) => void): this;\n on(event: 'progress', handler: (event: ProgressEvent) => void): this;\n on(event: 'failure', handler: (event: { error: Error; id: string; path: string; }) => void): this;\n on(event: string | string[], handler: EventHandler): this {\n return super.on(event, handler);\n }\n}\n\ntype ProgressEvent = {\n percentage: number;\n loaded: number;\n total: number;\n};\ntype ProgressEventHandler = (e: ProgressEvent) => void;\n\nexport interface S3MultipartUploaderModule {\n S3MultipartUploader: typeof S3MultipartUploader;\n}\n"],"names":[],"mappings":"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"}
@@ -77,6 +77,7 @@ class S3Uploader extends (/* @__PURE__ */ Mixin(EventMixin)) {
77
77
  this.options.endpoint || "",
78
78
  fileData,
79
79
  {
80
+ signal: options.signal,
80
81
  onUploadProgress: (e) => {
81
82
  if (options.onUploadProgress) {
82
83
  options.onUploadProgress(e);
@@ -1 +1 @@
1
- {"version":3,"file":"s3-uploader.js","sources":["../../src/module/s3-uploader.ts"],"sourcesContent":["import { useHttpClient } from '../composable';\nimport { data } from '../data';\nimport { EventAwareInterface, EventHandler, EventMixin } from '../events';\nimport type { UnicornHttpClient } from './http-client';\nimport { mergeDeep } from '../utilities';\nimport { AxiosProgressEvent, AxiosResponse } from 'axios';\nimport { Mixin } from 'ts-mixer';\n\nconst instances: Record<string, S3Uploader> = {};\nexport function get(name: string, options?: Partial<S3UploaderGlobalOptions>): S3Uploader;\nexport function get(\n name: string,\n options: Partial<S3UploaderGlobalOptions> = {}\n): S3Uploader | void {\n return instances[name] ??= create(name, options);\n}\n\nexport function create(name: string, options: Partial<S3UploaderGlobalOptions> = {}): S3Uploader {\n return new S3Uploader(name, options);\n}\n\nexport function destroy(name: string) {\n delete instances[name];\n}\n\nconst defaultOptions: S3UploaderGlobalOptions = {\n endpoint: '',\n subfolder: '',\n viewerHost: '',\n starts_with: [],\n formInputs: {\n acl: '',\n bucket: '',\n key: '',\n Policy: '',\n 'X-Amz-Algorithm': '',\n 'X-Amz-Credential': '',\n 'X-Amz-Date': '',\n 'X-Amz-Signature': '',\n }\n};\n\nexport class S3Uploader extends Mixin(EventMixin) implements EventAwareInterface {\n options: S3UploaderGlobalOptions;\n http?: UnicornHttpClient;\n\n constructor(protected name: string, options: Partial<S3UploaderGlobalOptions> = {}) {\n super();\n\n const awsOptions = data('@s3.uploader.' + name) || {};\n\n this.options = mergeDeep<S3UploaderGlobalOptions>({}, defaultOptions, awsOptions, options);\n }\n\n async getHttpClient() {\n return this.http ??= await useHttpClient();\n }\n\n /**\n * Do upload.\n */\n async upload(\n file: string | File | Blob,\n path: string,\n options: Partial<S3UploaderRequestOptions> = {}\n ): Promise<S3UploaderResponse> {\n const httpClient = await this.getHttpClient();\n\n const fileData = new FormData();\n const inputs = mergeDeep({}, this.options.formInputs, options.formInputs || {});\n\n if (typeof file === 'string') {\n file = new Blob([file], { type: options['Content-Type'] || 'text/plain' });\n }\n\n if (file instanceof Blob && path.endsWith('.{ext}')) {\n throw new Error('If using Blob or file data string, you must provide a valid file extension in the path.');\n }\n\n if ((file instanceof Blob) || (file as any) instanceof File) {\n options['Content-Type'] = options['Content-Type'] || file.type;\n }\n\n if (options['filename']) {\n const filename = this.replaceExt(options['filename'], file);\n options['Content-Disposition'] = 'attachment; filename*=UTF-8\\'\\'' + encodeURIComponent(filename);\n }\n\n path = this.replaceExt(path, file);\n\n options['key'] = trimSlashes(this.options.subfolder || '') + '/'\n + trimSlashes(path);\n options['key'] = trimSlashes(options['key']);\n options['Content-Type'] = options['Content-Type'] || undefined;\n options['Content-Disposition'] = options['Content-Disposition'] || undefined;\n\n // Prepare pre-signed data\n for (let key in inputs) {\n fileData.set(key, inputs[key]);\n }\n\n // Prepare custom data\n for (let key of Object.keys(this.options.starts_with)) {\n if (options[key]) {\n fileData.set(key, options[key]);\n }\n }\n\n fileData.append('file', file);\n\n this.trigger('start', fileData);\n\n try {\n let res = await httpClient.post(\n this.options.endpoint || '',\n fileData,\n {\n onUploadProgress: (e) => {\n if (options.onUploadProgress) {\n options.onUploadProgress(e);\n }\n\n this.trigger('upload-progress', e);\n\n if (e.total != null) {\n this.trigger('progress', e.loaded / e.total, e);\n }\n }\n }\n ) as S3UploaderResponse;\n\n const url = this.options.viewerHost + '/'\n + trimSlashes(path);\n\n this.trigger('success', url, res);\n\n res.url = url;\n\n return res;\n } finally {\n this.trigger('end');\n }\n }\n\n replaceExt(path: string, file: File | Blob): string {\n if (file instanceof File) {\n const fileExt = file.name.split('.').pop();\n\n if (path.endsWith('.{ext}')) {\n return path.replace(/\\.{ext}$/, fileExt ? '.' + fileExt : '');\n }\n }\n\n return path;\n }\n\n on(event: 'start', handler: StartEventHandler): this;\n on(event: 'success', handler: SuccessEventHandler): this;\n on(event: 'end', handler: EndEventHandler): this;\n on(event: 'upload-progress', handler: UploadProgressEventHandler): this;\n on(event: 'progress', handler: ProgressEventHandler): this;\n on(event: string | string[], handler: EventHandler): this {\n return super.on(event, handler);\n }\n\n onStart(handler: StartEventHandler): this {\n return this.on('start', handler);\n }\n\n onSuccess(handler: SuccessEventHandler): this {\n return this.on('success', handler);\n }\n\n onEnd(handler: EndEventHandler): this {\n return this.on('end', handler);\n }\n\n onProgress(handler: UploadProgressEventHandler): this {\n return this.on('upload-progress', handler);\n }\n\n onProgressWithTotal(handler: ProgressEventHandler): this {\n return this.on('progress', handler);\n }\n}\n\ntype EndEventHandler = () => void;\ntype SuccessEventHandler = (url: string, res: S3UploaderResponse) => void;\ntype StartEventHandler = (fileData: FormData) => void;\ntype UploadProgressEventHandler = (e: AxiosProgressEvent) => void;\ntype ProgressEventHandler = (total: number, e: AxiosProgressEvent) => void;\n\nfunction trimSlashes(str: string) {\n return str.replace(/^\\/+|\\/+$/g, '');\n}\n\nexport interface S3UploaderResponse extends AxiosResponse {\n url: string;\n}\n\nexport interface S3UploaderGlobalOptions {\n endpoint?: string;\n subfolder?: string;\n viewerHost?: string;\n starts_with: any[];\n formInputs?: {\n acl: string;\n bucket: string;\n key: string;\n Policy: string;\n 'X-Amz-Algorithm': string;\n 'X-Amz-Credential': string;\n 'X-Amz-Date': string;\n 'X-Amz-Signature': string;\n [name: string]: any\n },\n}\n\nexport interface S3UploaderRequestOptions {\n formInputs?: { [name: string]: any };\n onUploadProgress?: (e: AxiosProgressEvent) => void;\n 'Content-Type'?: string;\n 'Content-Disposition'?: string;\n key?: string;\n\n [name: string]: any;\n}\n\nexport interface S3UploaderModule {\n get(name: string, options?: Partial<S3UploaderGlobalOptions>): S3Uploader;\n create(name: string, options?: Partial<S3UploaderGlobalOptions>): S3Uploader;\n destroy(name: string): void;\n S3Uploader: typeof S3Uploader;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"s3-uploader.js","sources":["../../src/module/s3-uploader.ts"],"sourcesContent":["import { useHttpClient } from '../composable';\nimport { data } from '../data';\nimport { EventAwareInterface, EventHandler, EventMixin } from '../events';\nimport type { UnicornHttpClient } from './http-client';\nimport { mergeDeep } from '../utilities';\nimport { AxiosProgressEvent, AxiosResponse } from 'axios';\nimport { Mixin } from 'ts-mixer';\n\nconst instances: Record<string, S3Uploader> = {};\nexport function get(name: string, options?: Partial<S3UploaderGlobalOptions>): S3Uploader;\nexport function get(\n name: string,\n options: Partial<S3UploaderGlobalOptions> = {}\n): S3Uploader | void {\n return instances[name] ??= create(name, options);\n}\n\nexport function create(name: string, options: Partial<S3UploaderGlobalOptions> = {}): S3Uploader {\n return new S3Uploader(name, options);\n}\n\nexport function destroy(name: string) {\n delete instances[name];\n}\n\nconst defaultOptions: S3UploaderGlobalOptions = {\n endpoint: '',\n subfolder: '',\n viewerHost: '',\n starts_with: [],\n formInputs: {\n acl: '',\n bucket: '',\n key: '',\n Policy: '',\n 'X-Amz-Algorithm': '',\n 'X-Amz-Credential': '',\n 'X-Amz-Date': '',\n 'X-Amz-Signature': '',\n }\n};\n\nexport class S3Uploader extends Mixin(EventMixin) implements EventAwareInterface {\n options: S3UploaderGlobalOptions;\n http?: UnicornHttpClient;\n\n constructor(protected name: string, options: Partial<S3UploaderGlobalOptions> = {}) {\n super();\n\n const awsOptions = data('@s3.uploader.' + name) || {};\n\n this.options = mergeDeep<S3UploaderGlobalOptions>({}, defaultOptions, awsOptions, options);\n }\n\n async getHttpClient() {\n return this.http ??= await useHttpClient();\n }\n\n /**\n * Do upload.\n */\n async upload(\n file: string | File | Blob,\n path: string,\n options: Partial<S3UploaderRequestOptions> = {}\n ): Promise<S3UploaderResponse> {\n const httpClient = await this.getHttpClient();\n\n const fileData = new FormData();\n const inputs = mergeDeep({}, this.options.formInputs, options.formInputs || {});\n\n if (typeof file === 'string') {\n file = new Blob([file], { type: options['Content-Type'] || 'text/plain' });\n }\n\n if (file instanceof Blob && path.endsWith('.{ext}')) {\n throw new Error('If using Blob or file data string, you must provide a valid file extension in the path.');\n }\n\n if ((file instanceof Blob) || (file as any) instanceof File) {\n options['Content-Type'] = options['Content-Type'] || file.type;\n }\n\n if (options['filename']) {\n const filename = this.replaceExt(options['filename'], file);\n options['Content-Disposition'] = 'attachment; filename*=UTF-8\\'\\'' + encodeURIComponent(filename);\n }\n\n path = this.replaceExt(path, file);\n\n options['key'] = trimSlashes(this.options.subfolder || '') + '/'\n + trimSlashes(path);\n options['key'] = trimSlashes(options['key']);\n options['Content-Type'] = options['Content-Type'] || undefined;\n options['Content-Disposition'] = options['Content-Disposition'] || undefined;\n\n // Prepare pre-signed data\n for (let key in inputs) {\n fileData.set(key, inputs[key]);\n }\n\n // Prepare custom data\n for (let key of Object.keys(this.options.starts_with)) {\n if (options[key]) {\n fileData.set(key, options[key]);\n }\n }\n\n fileData.append('file', file);\n\n this.trigger('start', fileData);\n\n try {\n let res = await httpClient.post(\n this.options.endpoint || '',\n fileData,\n {\n signal: options.signal,\n onUploadProgress: (e) => {\n if (options.onUploadProgress) {\n options.onUploadProgress(e);\n }\n\n this.trigger('upload-progress', e);\n\n if (e.total != null) {\n this.trigger('progress', e.loaded / e.total, e);\n }\n }\n }\n ) as S3UploaderResponse;\n\n const url = this.options.viewerHost + '/'\n + trimSlashes(path);\n\n this.trigger('success', url, res);\n\n res.url = url;\n\n return res;\n } finally {\n this.trigger('end');\n }\n }\n\n replaceExt(path: string, file: File | Blob): string {\n if (file instanceof File) {\n const fileExt = file.name.split('.').pop();\n\n if (path.endsWith('.{ext}')) {\n return path.replace(/\\.{ext}$/, fileExt ? '.' + fileExt : '');\n }\n }\n\n return path;\n }\n\n on(event: 'start', handler: StartEventHandler): this;\n on(event: 'success', handler: SuccessEventHandler): this;\n on(event: 'end', handler: EndEventHandler): this;\n on(event: 'upload-progress', handler: UploadProgressEventHandler): this;\n on(event: 'progress', handler: ProgressEventHandler): this;\n on(event: string | string[], handler: EventHandler): this {\n return super.on(event, handler);\n }\n\n onStart(handler: StartEventHandler): this {\n return this.on('start', handler);\n }\n\n onSuccess(handler: SuccessEventHandler): this {\n return this.on('success', handler);\n }\n\n onEnd(handler: EndEventHandler): this {\n return this.on('end', handler);\n }\n\n onProgress(handler: UploadProgressEventHandler): this {\n return this.on('upload-progress', handler);\n }\n\n onProgressWithTotal(handler: ProgressEventHandler): this {\n return this.on('progress', handler);\n }\n}\n\ntype EndEventHandler = () => void;\ntype SuccessEventHandler = (url: string, res: S3UploaderResponse) => void;\ntype StartEventHandler = (fileData: FormData) => void;\ntype UploadProgressEventHandler = (e: AxiosProgressEvent) => void;\ntype ProgressEventHandler = (total: number, e: AxiosProgressEvent) => void;\n\nfunction trimSlashes(str: string) {\n return str.replace(/^\\/+|\\/+$/g, '');\n}\n\nexport interface S3UploaderResponse extends AxiosResponse {\n url: string;\n}\n\nexport interface S3UploaderGlobalOptions {\n endpoint?: string;\n subfolder?: string;\n viewerHost?: string;\n starts_with: any[];\n formInputs?: {\n acl: string;\n bucket: string;\n key: string;\n Policy: string;\n 'X-Amz-Algorithm': string;\n 'X-Amz-Credential': string;\n 'X-Amz-Date': string;\n 'X-Amz-Signature': string;\n [name: string]: any\n },\n}\n\nexport interface S3UploaderRequestOptions {\n formInputs?: { [name: string]: any };\n onUploadProgress?: (e: AxiosProgressEvent) => void;\n 'Content-Type'?: string;\n 'Content-Disposition'?: string;\n key?: string;\n signal?: AbortSignal;\n\n [name: string]: any;\n}\n\nexport interface S3UploaderModule {\n get(name: string, options?: Partial<S3UploaderGlobalOptions>): S3Uploader;\n create(name: string, options?: Partial<S3UploaderGlobalOptions>): S3Uploader;\n destroy(name: string): void;\n S3Uploader: typeof S3Uploader;\n}\n"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -626,12 +626,13 @@ declare class S3MultipartUploader extends S3MultipartUploader_base {
626
626
  path: string;
627
627
  partNumber: number;
628
628
  chunkSize: number;
629
+ abortController?: AbortController;
629
630
  onUploadProgress: (e: AxiosProgressEvent) => void;
630
631
  }): Promise<{
631
632
  blob: Blob;
632
633
  etag: string;
633
634
  }>;
634
- protected request<T = Record<string, any>>(action: RouteActions, body: Record<string, any>): Promise<T>;
635
+ protected request<T = Record<string, any>>(action: RouteActions, body: Record<string, any>, config?: Partial<AxiosRequestConfig>): Promise<T>;
635
636
  abort(id: string, path: string): Promise<void>;
636
637
  updateProgress(loaded: number, total: number, options: S3MultipartUploaderRequestOptions): void;
637
638
  resolveRoute(action: RouteActions): Promise<string>;
@@ -685,6 +686,7 @@ declare interface S3MultipartUploaderRequestOptions {
685
686
  ContentDisposition?: string;
686
687
  ACL?: 'public-read' | 'private' | 'authenticated-read' | 'public-read-write' | string;
687
688
  extra?: Record<string, any>;
689
+ abortController?: AbortController;
688
690
  }
689
691
 
690
692
  declare class S3Uploader extends S3Uploader_base implements EventAwareInterface {
@@ -760,6 +762,7 @@ declare interface S3UploaderRequestOptions {
760
762
  'Content-Type'?: string;
761
763
  'Content-Disposition'?: string;
762
764
  key?: string;
765
+ signal?: AbortSignal;
763
766
  [name: string]: any;
764
767
  }
765
768
 
@@ -1511,14 +1514,6 @@ declare global {
1511
1514
  }
1512
1515
 
1513
1516
 
1514
- declare module '@windwalker-io/unicorn-next' {
1515
- interface UnicornApp {
1516
- /** @deprecated Only for code generator use. */
1517
- $ui: typeof methods;
1518
- }
1519
- }
1520
-
1521
-
1522
1517
  declare global {
1523
1518
  var Alpine: AlpineGlobal;
1524
1519
  var TomSelect: typeof TomSelectGlobal;
@@ -1526,8 +1521,12 @@ declare global {
1526
1521
  var Mark: any;
1527
1522
  }
1528
1523
 
1529
- declare global {
1530
- var S: any;
1524
+
1525
+ declare module '@windwalker-io/unicorn-next' {
1526
+ interface UnicornApp {
1527
+ /** @deprecated Only for code generator use. */
1528
+ $ui: typeof methods;
1529
+ }
1531
1530
  }
1532
1531
 
1533
1532
 
@@ -1541,9 +1540,8 @@ declare module 'axios' {
1541
1540
  }
1542
1541
  }
1543
1542
 
1544
-
1545
1543
  declare global {
1546
- var tinymce: TinyMCE;
1544
+ var S: any;
1547
1545
  }
1548
1546
 
1549
1547
 
@@ -1552,3 +1550,8 @@ declare global {
1552
1550
  bootstrap: typeof bootstrap;
1553
1551
  }
1554
1552
  }
1553
+
1554
+
1555
+ declare global {
1556
+ var tinymce: TinyMCE;
1557
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windwalker-io/unicorn-next",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "Unicorn framework js library",
5
5
  "type": "module",
6
6
  "typings": "dist/index.d.ts",
@@ -63,7 +63,7 @@
63
63
  ]
64
64
  },
65
65
  "devDependencies": {
66
- "@lyrasoft/ts-toolkit": "^0.2.0",
66
+ "@lyrasoft/ts-toolkit": "^0.2.4",
67
67
  "@rubenbimmel/alpine-class-component": "^0.0.4",
68
68
  "@types/alpinejs": "^3",
69
69
  "@types/bootstrap": "^5",
@@ -1,8 +1,7 @@
1
- import { AxiosProgressEvent, AxiosResponseHeaders } from 'axios';
1
+ import { AxiosProgressEvent, type AxiosRequestConfig, AxiosResponseHeaders } from 'axios';
2
2
  import { Mixin } from 'ts-mixer';
3
3
  import { createQueue, useHttpClient } from '../composable';
4
4
  import { EventHandler, EventMixin } from '../events';
5
- import { route } from '../service';
6
5
  import type { MaybePromise } from '../types';
7
6
  import { mergeDeep } from '../utilities';
8
7
  import { ApiReturn } from './http-client';
@@ -45,6 +44,7 @@ export interface S3MultipartUploaderRequestOptions {
45
44
  ContentDisposition?: string;
46
45
  ACL?: 'public-read' | 'private' | 'authenticated-read' | 'public-read-write' | string;
47
46
  extra?: Record<string, any>;
47
+ abortController?: AbortController;
48
48
  }
49
49
 
50
50
  export class S3MultipartUploader extends Mixin(EventMixin) {
@@ -97,19 +97,32 @@ export class S3MultipartUploader extends Mixin(EventMixin) {
97
97
  e.preventDefault();
98
98
  e.returnValue = '';
99
99
  };
100
+
100
101
  if (this.options.leaveAlert === true) {
101
102
  window.addEventListener('beforeunload', beforeUnloadHandler);
102
103
  }
103
104
 
104
- // @Request init
105
- const { id } = await this.request<{ id: string; }>(
106
- 'init',
107
- initData
108
- );
105
+ let uploadId: string | null = null;
106
+ let isCancel = false;
107
+ let signal = options.abortController?.signal;
109
108
 
110
- this.trigger('inited', { id, path });
109
+ if (signal) {
110
+ signal.addEventListener('abort', (e) => {
111
+ isCancel = true;
112
+ });
113
+ }
111
114
 
112
115
  try {
116
+ // @Request init
117
+ const { id } = await this.request<{ id: string; }>(
118
+ 'init',
119
+ initData
120
+ );
121
+
122
+ uploadId = id;
123
+
124
+ this.trigger('inited', { id, path });
125
+
113
126
  const chunkSize = this.options.chunkSize;
114
127
  const chunks = Math.ceil(file.size / chunkSize);
115
128
 
@@ -133,6 +146,7 @@ export class S3MultipartUploader extends Mixin(EventMixin) {
133
146
  path,
134
147
  partNumber,
135
148
  chunkSize,
149
+ abortController: options.abortController,
136
150
  onUploadProgress: (e) => {
137
151
  partsUploaded[partNumber] = e.loaded;
138
152
 
@@ -158,7 +172,14 @@ export class S3MultipartUploader extends Mixin(EventMixin) {
158
172
 
159
173
  await Promise.all(promises);
160
174
 
161
- // @Request sign
175
+ if (isCancel) {
176
+ const e = new Error('Upload cancelled');
177
+ e.name = 'CanceledError';
178
+
179
+ throw e;
180
+ }
181
+
182
+ // @Request complete
162
183
  const { url } = await this.request<{ url: string }>(
163
184
  'complete',
164
185
  {
@@ -173,9 +194,11 @@ export class S3MultipartUploader extends Mixin(EventMixin) {
173
194
 
174
195
  return { url, id, path };
175
196
  } catch (e) {
176
- await this.abort(id, path);
197
+ if (uploadId) {
198
+ await this.abort(uploadId, path);
199
+ }
177
200
 
178
- this.trigger('failure', { error: e as Error, id, path });
201
+ this.trigger('failure', { error: e as Error, uploadId, path });
179
202
 
180
203
  throw e;
181
204
  } finally {
@@ -192,6 +215,7 @@ export class S3MultipartUploader extends Mixin(EventMixin) {
192
215
  path: string;
193
216
  partNumber: number;
194
217
  chunkSize: number;
218
+ abortController?: AbortController;
195
219
  onUploadProgress: (e: AxiosProgressEvent) => void;
196
220
  }
197
221
  ) {
@@ -211,6 +235,9 @@ export class S3MultipartUploader extends Mixin(EventMixin) {
211
235
  path,
212
236
  partNumber,
213
237
  profile: this.options.profile,
238
+ },
239
+ {
240
+ signal: payload.abortController?.signal,
214
241
  }
215
242
  );
216
243
 
@@ -228,14 +255,18 @@ export class S3MultipartUploader extends Mixin(EventMixin) {
228
255
  return { blob, etag };
229
256
  }
230
257
 
231
- protected async request<T = Record<string, any>>(action: RouteActions, body: Record<string, any>): Promise<T> {
258
+ protected async request<T = Record<string, any>>(
259
+ action: RouteActions,
260
+ body: Record<string, any>,
261
+ config: Partial<AxiosRequestConfig> = {}
262
+ ): Promise<T> {
232
263
  if (this.options.requestHandler) {
233
264
  return this.options.requestHandler<T>(action, body);
234
265
  }
235
266
 
236
267
  const http = await useHttpClient();
237
268
 
238
- const res = await http.post<ApiReturn<T>>(await this.resolveRoute(action), body);
269
+ const res = await http.post<ApiReturn<T>>(await this.resolveRoute(action), body, config);
239
270
 
240
271
  return res.data.data;
241
272
  }
@@ -115,6 +115,7 @@ export class S3Uploader extends Mixin(EventMixin) implements EventAwareInterface
115
115
  this.options.endpoint || '',
116
116
  fileData,
117
117
  {
118
+ signal: options.signal,
118
119
  onUploadProgress: (e) => {
119
120
  if (options.onUploadProgress) {
120
121
  options.onUploadProgress(e);
@@ -222,6 +223,7 @@ export interface S3UploaderRequestOptions {
222
223
  'Content-Type'?: string;
223
224
  'Content-Disposition'?: string;
224
225
  key?: string;
226
+ signal?: AbortSignal;
225
227
 
226
228
  [name: string]: any;
227
229
  }