@contello/uploader 8.21.3 → 8.21.5

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020-2026 entwico GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs CHANGED
@@ -1,211 +1,155 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- ContelloUploader: () => ContelloUploader,
24
- UploadAssetRetentionPolicy: () => UploadAssetRetentionPolicy
25
- });
26
- module.exports = __toCommonJS(index_exports);
27
-
28
- // src/upload-metadata.ts
29
- var UploadAssetRetentionPolicy = /* @__PURE__ */ ((UploadAssetRetentionPolicy2) => {
30
- UploadAssetRetentionPolicy2["retain"] = "retain";
31
- UploadAssetRetentionPolicy2["deleteIfNotUsed"] = "deleteIfNotUsed";
32
- return UploadAssetRetentionPolicy2;
33
- })(UploadAssetRetentionPolicy || {});
34
-
35
- // src/uploader.ts
36
- var import_rxjs3 = require("rxjs");
37
-
38
- // src/implementations/ws.ts
39
- var import_rxjs = require("rxjs");
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let rxjs = require("rxjs");
3
+ //#region src/upload-metadata.ts
4
+ let UploadAssetRetentionPolicy = /* @__PURE__ */ function(UploadAssetRetentionPolicy) {
5
+ UploadAssetRetentionPolicy["retain"] = "retain";
6
+ UploadAssetRetentionPolicy["deleteIfNotUsed"] = "deleteIfNotUsed";
7
+ return UploadAssetRetentionPolicy;
8
+ }({});
9
+ //#endregion
10
+ //#region src/implementations/ws.ts
11
+ /** Uploads a file via WebSocket using chunked binary transfer. Emits progress events and the asset ID on completion. */
40
12
  function uploadViaWebSocket({ url, project, token, chunkSize }, file, meta, options) {
41
- const metadata = { ...meta ?? {}, projectRef: project };
42
- return new import_rxjs.Observable((obs) => {
43
- const parsedUrl = new URL(url);
44
- const ws = new WebSocket(
45
- `${parsedUrl.protocol === "https:" ? "wss" : "ws"}://${parsedUrl.host}/api/v1/assets/ws`,
46
- "contello-file-upload-v1"
47
- );
48
- const reader = typeof FileReader !== "undefined" ? new FileReader() : null;
49
- let offset = 0;
50
- let done = false;
51
- let ackReceived = false;
52
- ws.onopen = async () => {
53
- const initFrame = {
54
- type: "init",
55
- metadata: {
56
- ...metadata,
57
- name: file.name,
58
- mimeType: file.type,
59
- size: file.size
60
- },
61
- token
62
- };
63
- ws.send(JSON.stringify(initFrame));
64
- function readSlice() {
65
- const slice = file.slice(offset, offset + chunkSize);
66
- if (reader) {
67
- reader.readAsArrayBuffer(slice);
68
- } else {
69
- slice.arrayBuffer().then((buffer) => {
70
- handleArrayBuffer(buffer);
71
- }).catch((err) => {
72
- obs.error(err);
73
- });
74
- }
75
- }
76
- function markAsDone() {
77
- reader?.abort();
78
- ws.send(JSON.stringify({ type: "done" }));
79
- }
80
- function startUpload() {
81
- if (ackReceived) {
82
- readSlice();
83
- }
84
- }
85
- function handleArrayBuffer(buffer) {
86
- if (ws.readyState === WebSocket.OPEN) {
87
- ws.send(buffer);
88
- offset += buffer.byteLength;
89
- if (offset < file.size) {
90
- readSlice();
91
- } else {
92
- markAsDone();
93
- }
94
- } else {
95
- obs.error(new Error("WebSocket is closed"));
96
- reader?.abort();
97
- }
98
- }
99
- if (reader) {
100
- reader.onload = () => {
101
- if (reader.result && reader.result instanceof ArrayBuffer) {
102
- handleArrayBuffer(reader.result);
103
- } else {
104
- markAsDone();
105
- }
106
- };
107
- }
108
- ws.onmessage = (event) => {
109
- const message = JSON.parse(event.data);
110
- if (message.type === "ack") {
111
- ackReceived = true;
112
- startUpload();
113
- return;
114
- }
115
- if (message.type === "progress") {
116
- return obs.next({ progress: message.bytesReceived / file.size * 100 });
117
- }
118
- if (message.type === "done") {
119
- if (message.id) {
120
- done = true;
121
- obs.next({ id: message.id });
122
- obs.complete();
123
- } else {
124
- obs.error(new Error("No asset id received"));
125
- }
126
- return ws.close();
127
- }
128
- obs.error(new Error(`WebSocket message with unknown type ${JSON.stringify(message)}`));
129
- ws.close();
130
- };
131
- };
132
- ws.onerror = (error) => obs.error(error);
133
- ws.onclose = () => {
134
- if (!done) {
135
- obs.error(new Error("Connection closed"));
136
- }
137
- };
138
- const abortHandler = () => {
139
- if (!done) {
140
- reader?.abort();
141
- ws.close();
142
- obs.error(new Error("Upload aborted"));
143
- }
144
- };
145
- options?.abort?.addEventListener("abort", abortHandler);
146
- return () => {
147
- options?.abort?.removeEventListener("abort", abortHandler);
148
- ws.close();
149
- };
150
- });
13
+ const metadata = {
14
+ ...meta,
15
+ projectRef: project
16
+ };
17
+ return new rxjs.Observable((obs) => {
18
+ const parsedUrl = new URL(url);
19
+ const ws = new WebSocket(`${parsedUrl.protocol === "https:" ? "wss" : "ws"}://${parsedUrl.host}/api/v1/assets/ws`, "contello-file-upload-v1");
20
+ let offset = 0;
21
+ let done = false;
22
+ let ackReceived = false;
23
+ ws.addEventListener("open", () => {
24
+ const initFrame = {
25
+ type: "init",
26
+ metadata: {
27
+ ...metadata,
28
+ name: file.name,
29
+ mimeType: file.type,
30
+ size: file.size
31
+ },
32
+ token
33
+ };
34
+ ws.send(JSON.stringify(initFrame));
35
+ function readSlice() {
36
+ (async () => {
37
+ try {
38
+ handleArrayBuffer(await file.slice(offset, offset + chunkSize).arrayBuffer());
39
+ } catch (error) {
40
+ obs.error(error);
41
+ }
42
+ })();
43
+ }
44
+ function markAsDone() {
45
+ ws.send(JSON.stringify({ type: "done" }));
46
+ }
47
+ function startUpload() {
48
+ if (ackReceived) readSlice();
49
+ }
50
+ function handleArrayBuffer(buffer) {
51
+ if (options?.abort?.aborted) return;
52
+ if (ws.readyState === WebSocket.OPEN) {
53
+ ws.send(buffer);
54
+ offset += buffer.byteLength;
55
+ if (offset < file.size) readSlice();
56
+ else markAsDone();
57
+ } else obs.error(/* @__PURE__ */ new Error("WebSocket is closed"));
58
+ }
59
+ ws.addEventListener("message", (event) => {
60
+ const message = JSON.parse(event.data);
61
+ if (message.type === "ack") {
62
+ ackReceived = true;
63
+ startUpload();
64
+ return;
65
+ }
66
+ if (message.type === "progress") return obs.next({ progress: message.bytesReceived / file.size * 100 });
67
+ if (message.type === "done") {
68
+ if (message.id) {
69
+ done = true;
70
+ obs.next({ id: message.id });
71
+ obs.complete();
72
+ } else obs.error(/* @__PURE__ */ new Error("No asset id received"));
73
+ return ws.close();
74
+ }
75
+ obs.error(/* @__PURE__ */ new Error(`WebSocket message with unknown type ${JSON.stringify(message)}`));
76
+ ws.close();
77
+ });
78
+ });
79
+ ws.addEventListener("error", (error) => obs.error(error));
80
+ ws.addEventListener("close", () => {
81
+ if (!done) obs.error(/* @__PURE__ */ new Error("Connection closed"));
82
+ });
83
+ const abortHandler = () => {
84
+ if (done) return;
85
+ ws.close();
86
+ obs.error(/* @__PURE__ */ new Error("Upload aborted"));
87
+ };
88
+ options?.abort?.addEventListener("abort", abortHandler);
89
+ return () => {
90
+ options?.abort?.removeEventListener("abort", abortHandler);
91
+ ws.close();
92
+ };
93
+ });
151
94
  }
152
-
153
- // src/implementations/xhr.ts
154
- var import_rxjs2 = require("rxjs");
95
+ //#endregion
96
+ //#region src/implementations/xhr.ts
97
+ /** Uploads a file via XMLHttpRequest with multipart form data. Emits progress events and the asset ID on completion. */
155
98
  function uploadViaXhr({ url, project, token }, file, meta, options) {
156
- return new import_rxjs2.Observable((obs) => {
157
- const data = new FormData();
158
- data.append("metadata", JSON.stringify({ ...meta ?? {}, projectRef: project }));
159
- data.append("file", file);
160
- const xhr = new XMLHttpRequest();
161
- xhr.setRequestHeader("Authorization", `Bearer ${token}`);
162
- xhr.open("POST", `${url}/api/v1/assets`, true);
163
- xhr.upload.onprogress = async (event) => {
164
- if (event.lengthComputable) {
165
- obs.next({ progress: event.loaded / event.total * 100 });
166
- }
167
- };
168
- xhr.onload = async () => {
169
- if (xhr.status >= 200 && xhr.status < 300) {
170
- const { id } = JSON.parse(xhr.responseText);
171
- obs.next({ id });
172
- return obs.complete();
173
- }
174
- obs.error(new Error(`${xhr.status}: ${xhr.statusText}`));
175
- };
176
- xhr.onerror = (error) => obs.error(error);
177
- xhr.send(data);
178
- options?.abort?.addEventListener("abort", () => xhr.abort());
179
- return () => xhr.abort();
180
- });
99
+ return new rxjs.Observable((obs) => {
100
+ const data = new FormData();
101
+ data.append("metadata", JSON.stringify({
102
+ ...meta,
103
+ projectRef: project
104
+ }));
105
+ data.append("file", file);
106
+ const xhr = new XMLHttpRequest();
107
+ xhr.setRequestHeader("Authorization", `Bearer ${token}`);
108
+ xhr.open("POST", `${url}/api/v1/assets`, true);
109
+ xhr.upload.addEventListener("progress", (event) => {
110
+ if (event.lengthComputable) obs.next({ progress: event.loaded / event.total * 100 });
111
+ });
112
+ xhr.addEventListener("load", () => {
113
+ if (xhr.status >= 200 && xhr.status < 300) {
114
+ const { id } = JSON.parse(xhr.responseText);
115
+ obs.next({ id });
116
+ return obs.complete();
117
+ }
118
+ obs.error(/* @__PURE__ */ new Error(`${xhr.status}: ${xhr.statusText}`));
119
+ });
120
+ xhr.addEventListener("error", (error) => obs.error(error));
121
+ xhr.send(data);
122
+ options?.abort?.addEventListener("abort", () => xhr.abort());
123
+ return () => xhr.abort();
124
+ });
181
125
  }
182
-
183
- // src/uploader.ts
126
+ //#endregion
127
+ //#region src/uploader.ts
128
+ /**
129
+ * ContelloUploader is a class for uploading files to a Contello server.
130
+ * It supports both WebSocket and HTTP transports.
131
+ */
184
132
  var ContelloUploader = class {
185
- params;
186
- constructor(params) {
187
- this.params = {
188
- url: params.url,
189
- project: params.project,
190
- token: params.token,
191
- transport: params.transport || "ws",
192
- chunkSize: params.chunkSize || 4 * 1024 * 1024
193
- };
194
- }
195
- /** Uploads a file and resolves with the asset ID once complete. */
196
- async upload(file, meta, options) {
197
- return (0, import_rxjs3.firstValueFrom)(this.uploadWithEvents(file, meta, options).pipe((0, import_rxjs3.filter)((r) => "id" in r)));
198
- }
199
- /** Uploads a file and returns an Observable emitting progress events and the final asset ID. */
200
- uploadWithEvents(file, meta, options) {
201
- if (this.params.transport === "http") {
202
- return uploadViaXhr(this.params, file, meta, options);
203
- }
204
- return uploadViaWebSocket(this.params, file, meta, options);
205
- }
133
+ params;
134
+ constructor(params) {
135
+ this.params = {
136
+ url: params.url,
137
+ project: params.project,
138
+ token: params.token,
139
+ transport: params.transport || "ws",
140
+ chunkSize: params.chunkSize || 4 * 1024 * 1024
141
+ };
142
+ }
143
+ /** Uploads a file and resolves with the asset ID once complete. */
144
+ async upload(file, meta, options) {
145
+ return (0, rxjs.firstValueFrom)(this.uploadWithEvents(file, meta, options).pipe((0, rxjs.filter)((r) => "id" in r)));
146
+ }
147
+ /** Uploads a file and returns an Observable emitting progress events and the final asset ID. */
148
+ uploadWithEvents(file, meta, options) {
149
+ if (this.params.transport === "http") return uploadViaXhr(this.params, file, meta, options);
150
+ return uploadViaWebSocket(this.params, file, meta, options);
151
+ }
206
152
  };
207
- // Annotate the CommonJS export names for ESM import in node:
208
- 0 && (module.exports = {
209
- ContelloUploader,
210
- UploadAssetRetentionPolicy
211
- });
153
+ //#endregion
154
+ exports.ContelloUploader = ContelloUploader;
155
+ exports.UploadAssetRetentionPolicy = UploadAssetRetentionPolicy;
package/dist/index.d.cts CHANGED
@@ -1,122 +1,123 @@
1
- import * as rxjs from 'rxjs';
2
-
1
+ //#region src/upload-metadata.d.ts
3
2
  type UploadAssetMetadata = {
4
- name?: string | undefined;
5
- mimeType?: string | undefined;
6
- size?: number | undefined;
7
- annotations?: UploadAssetAnnotation[] | undefined;
8
- collectionRefs?: string[] | undefined;
9
- image?: UploadAssetImageMetadata | undefined;
10
- retentionPolicy?: UploadAssetRetentionPolicy | undefined;
11
- generatePreview?: boolean | undefined;
12
- createdAt?: string | undefined;
13
- updatedAt?: string | undefined;
3
+ name?: string | undefined;
4
+ mimeType?: string | undefined;
5
+ size?: number | undefined;
6
+ annotations?: UploadAssetAnnotation[] | undefined;
7
+ collectionRefs?: string[] | undefined;
8
+ image?: UploadAssetImageMetadata | undefined;
9
+ retentionPolicy?: UploadAssetRetentionPolicy | undefined;
10
+ generatePreview?: boolean | undefined;
11
+ createdAt?: string | undefined;
12
+ updatedAt?: string | undefined;
14
13
  };
15
14
  type UploadAssetAnnotation = {
16
- key: string;
17
- value: string;
15
+ key: string;
16
+ value: string;
18
17
  };
19
18
  type UploadAssetProgress = {
20
- progress: number;
19
+ progress: number;
21
20
  };
22
21
  declare enum UploadAssetRetentionPolicy {
23
- retain = "retain",
24
- deleteIfNotUsed = "deleteIfNotUsed"
22
+ retain = "retain",
23
+ deleteIfNotUsed = "deleteIfNotUsed"
25
24
  }
26
25
  type UploadAssetImageMetadata = {
27
- transformations: UploadAssetImageTransformation[];
26
+ transformations: UploadAssetImageTransformation[];
28
27
  };
29
28
  type UploadAssetImageTransformation = UploadAssetExtractTransformation | UploadAssetFitTransformation | UploadAssetConvertTransformation;
30
29
  type UploadAssetExtractTransformation = {
31
- name: 'extract';
32
- options: {
33
- top: number;
34
- left: number;
35
- areaWidth: number;
36
- areaHeight: number;
37
- rotate?: 0 | 90 | 180 | 270 | undefined;
38
- };
30
+ name: 'extract';
31
+ options: {
32
+ top: number;
33
+ left: number;
34
+ areaWidth: number;
35
+ areaHeight: number;
36
+ rotate?: 0 | 90 | 180 | 270 | undefined;
37
+ };
39
38
  };
40
39
  type UploadAssetFitTransformation = {
41
- name: 'fit';
42
- options: {
43
- width: number;
44
- height: number;
45
- };
40
+ name: 'fit';
41
+ options: {
42
+ width: number;
43
+ height: number;
44
+ };
46
45
  };
47
46
  type UploadAssetConvertTransformation = {
48
- name: 'convert';
49
- options: UploadAssetConvertTransformationJpegOptions | UploadAssetConvertTransformationPngOptions | UploadAssetConvertTransformationAvifOptions;
47
+ name: 'convert';
48
+ options: UploadAssetConvertTransformationJpegOptions | UploadAssetConvertTransformationPngOptions | UploadAssetConvertTransformationAvifOptions;
50
49
  };
51
50
  type UploadAssetConvertTransformationJpegOptions = {
52
- format: 'jpeg';
53
- options: {
54
- quality: number;
55
- interlace: boolean;
56
- };
51
+ format: 'jpeg';
52
+ options: {
53
+ quality: number;
54
+ interlace: boolean;
55
+ };
57
56
  };
58
57
  type UploadAssetConvertTransformationPngOptions = {
59
- format: 'png';
60
- options: {
61
- compressionLevel: number;
62
- };
58
+ format: 'png';
59
+ options: {
60
+ compressionLevel: number;
61
+ };
63
62
  };
64
63
  type UploadAssetConvertTransformationAvifOptions = {
65
- format: 'avif';
66
- options: {
67
- quality: number;
68
- lossless: boolean;
69
- };
64
+ format: 'avif';
65
+ options: {
66
+ quality: number;
67
+ lossless: boolean;
68
+ };
70
69
  };
71
-
70
+ //#endregion
71
+ //#region src/uploader.d.ts
72
72
  type ContelloUploaderParams = {
73
- /**
74
- * Base URL of the Contello server.
75
- * Example: 'https://example.com'
76
- */
77
- url: string;
78
- /**
79
- * Project identifier for the Contello project.
80
- * Example: 'myProjectId'
81
- */
82
- project: string;
83
- /**
84
- * Authentication token for the Contello API.
85
- * Example: 'app-a123b456c7890d123456789'
86
- */
87
- token: string;
88
- /**
89
- * Transport protocol to use for uploading files.
90
- * 'http' uses XMLHttpRequest, 'ws' uses WebSocket.
91
- * @default 'ws'
92
- */
93
- transport?: 'http' | 'ws';
94
- /**
95
- * Size of each chunk in bytes when uploading files.
96
- * Default is 4MB.
97
- * @default 4_194_304
98
- */
99
- chunkSize?: number;
73
+ /**
74
+ * Base URL of the Contello server.
75
+ * Example: 'https://example.com'
76
+ */
77
+ url: string;
78
+ /**
79
+ * Project identifier for the Contello project.
80
+ * Example: 'myProjectId'
81
+ */
82
+ project: string;
83
+ /**
84
+ * Authentication token for the Contello API.
85
+ * Example: 'app-a123b456c7890d123456789'
86
+ */
87
+ token: string;
88
+ /**
89
+ * Transport protocol to use for uploading files.
90
+ * 'http' uses XMLHttpRequest, 'ws' uses WebSocket.
91
+ * @default 'ws'
92
+ */
93
+ transport?: 'http' | 'ws';
94
+ /**
95
+ * Size of each chunk in bytes when uploading files.
96
+ * Default is 4MB.
97
+ * @default 4_194_304
98
+ */
99
+ chunkSize?: number;
100
100
  };
101
101
  /**
102
102
  * ContelloUploader is a class for uploading files to a Contello server.
103
103
  * It supports both WebSocket and HTTP transports.
104
104
  */
105
105
  declare class ContelloUploader {
106
- private params;
107
- constructor(params: ContelloUploaderParams);
108
- /** Uploads a file and resolves with the asset ID once complete. */
109
- upload(file: File, meta?: UploadAssetMetadata | undefined, options?: {
110
- abort?: AbortSignal;
111
- } | undefined): Promise<{
112
- id: string;
113
- }>;
114
- /** Uploads a file and returns an Observable emitting progress events and the final asset ID. */
115
- uploadWithEvents(file: File, meta?: UploadAssetMetadata | undefined, options?: {
116
- abort?: AbortSignal;
117
- } | undefined): rxjs.Observable<UploadAssetProgress | {
118
- id: string;
119
- }>;
106
+ private params;
107
+ constructor(params: ContelloUploaderParams);
108
+ /** Uploads a file and resolves with the asset ID once complete. */
109
+ upload(file: File, meta?: UploadAssetMetadata | undefined, options?: {
110
+ abort?: AbortSignal;
111
+ } | undefined): Promise<{
112
+ id: string;
113
+ }>;
114
+ /** Uploads a file and returns an Observable emitting progress events and the final asset ID. */
115
+ uploadWithEvents(file: File, meta?: UploadAssetMetadata | undefined, options?: {
116
+ abort?: AbortSignal;
117
+ } | undefined): import("rxjs").Observable<UploadAssetProgress | {
118
+ id: string;
119
+ }>;
120
120
  }
121
-
122
- export { ContelloUploader, type ContelloUploaderParams, type UploadAssetAnnotation, type UploadAssetConvertTransformation, type UploadAssetConvertTransformationAvifOptions, type UploadAssetConvertTransformationJpegOptions, type UploadAssetConvertTransformationPngOptions, type UploadAssetExtractTransformation, type UploadAssetFitTransformation, type UploadAssetImageMetadata, type UploadAssetImageTransformation, type UploadAssetMetadata, type UploadAssetProgress, UploadAssetRetentionPolicy };
121
+ //#endregion
122
+ export { ContelloUploader, ContelloUploaderParams, UploadAssetAnnotation, UploadAssetConvertTransformation, UploadAssetConvertTransformationAvifOptions, UploadAssetConvertTransformationJpegOptions, UploadAssetConvertTransformationPngOptions, UploadAssetExtractTransformation, UploadAssetFitTransformation, UploadAssetImageMetadata, UploadAssetImageTransformation, UploadAssetMetadata, UploadAssetProgress, UploadAssetRetentionPolicy };
123
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/upload-metadata.ts","../src/uploader.ts"],"mappings":";KAAY,mBAAA;EACV,IAAA;EACA,QAAA;EACA,IAAA;EACA,WAAA,GAAc,qBAAA;EACd,cAAA;EACA,KAAA,GAAQ,wBAAA;EACR,eAAA,GAAkB,0BAAA;EAClB,eAAA;EACA,SAAA;EACA,SAAA;AAAA;AAAA,KAGU,qBAAA;EACV,GAAA;EACA,KAAK;AAAA;AAAA,KAGK,mBAAA;EACV,QAAQ;AAAA;AAAA,aAGE,0BAAA;EACV,MAAA;EACA,eAAe;AAAA;AAAA,KAGL,wBAAA;EACV,eAAA,EAAiB,8BAA8B;AAAA;AAAA,KAGrC,8BAAA,GACR,gCAAA,GACA,4BAAA,GACA,gCAAA;AAAA,KAEQ,gCAAA;EACV,IAAA;EACA,OAAA;IACE,GAAA;IACA,IAAA;IACA,SAAA;IACA,UAAA;IACA,MAAA;EAAA;AAAA;AAAA,KAIQ,4BAAA;EACV,IAAA;EACA,OAAA;IACE,KAAA;IACA,MAAA;EAAA;AAAA;AAAA,KAIQ,gCAAA;EACV,IAAA;EACA,OAAA,EACI,2CAAA,GACA,0CAAA,GACA,2CAAA;AAAA;AAAA,KAGM,2CAAA;EACV,MAAA;EACA,OAAA;IACE,OAAA;IACA,SAAA;EAAA;AAAA;AAAA,KAIQ,0CAAA;EACV,MAAA;EACA,OAAA;IACE,gBAAA;EAAA;AAAA;AAAA,KAIQ,2CAAA;EACV,MAAA;EACA,OAAA;IACE,OAAA;IACA,QAAA;EAAA;AAAA;;;KC7EQ,sBAAA;EDLmB;;;;ECU7B,GAAA;EDH4C;;;;ECQ5C,OAAA;EDXA;;;;ECgBA,KAAA;EDbA;;;;;ECmBA,SAAA;EDhBS;AAGX;;;;ECmBE,SAAA;AAAA;;;;ADbQ;cCoBG,gBAAA;EAAA,QACH,MAAA;cAEI,MAAA,EAAQ,sBAAA;EDnBpB;EC8BM,MAAA,CACJ,IAAA,EAAM,IAAA,EACN,IAAA,GAAO,mBAAA,cACP,OAAA;IAAY,KAAA,GAAQ,WAAA;EAAA,gBACnB,OAAA;IAAU,EAAA;EAAA;ED7BkC;ECkC/C,gBAAA,CAAiB,IAAA,EAAM,IAAA,EAAM,IAAA,GAAO,mBAAA,cAAiC,OAAA;IAAY,KAAA,GAAQ,WAAA;EAAA,+BAAyB,UAAA,CAAd,mBAAA"}
package/dist/index.d.ts CHANGED
@@ -1,122 +1,123 @@
1
- import * as rxjs from 'rxjs';
2
-
1
+ //#region src/upload-metadata.d.ts
3
2
  type UploadAssetMetadata = {
4
- name?: string | undefined;
5
- mimeType?: string | undefined;
6
- size?: number | undefined;
7
- annotations?: UploadAssetAnnotation[] | undefined;
8
- collectionRefs?: string[] | undefined;
9
- image?: UploadAssetImageMetadata | undefined;
10
- retentionPolicy?: UploadAssetRetentionPolicy | undefined;
11
- generatePreview?: boolean | undefined;
12
- createdAt?: string | undefined;
13
- updatedAt?: string | undefined;
3
+ name?: string | undefined;
4
+ mimeType?: string | undefined;
5
+ size?: number | undefined;
6
+ annotations?: UploadAssetAnnotation[] | undefined;
7
+ collectionRefs?: string[] | undefined;
8
+ image?: UploadAssetImageMetadata | undefined;
9
+ retentionPolicy?: UploadAssetRetentionPolicy | undefined;
10
+ generatePreview?: boolean | undefined;
11
+ createdAt?: string | undefined;
12
+ updatedAt?: string | undefined;
14
13
  };
15
14
  type UploadAssetAnnotation = {
16
- key: string;
17
- value: string;
15
+ key: string;
16
+ value: string;
18
17
  };
19
18
  type UploadAssetProgress = {
20
- progress: number;
19
+ progress: number;
21
20
  };
22
21
  declare enum UploadAssetRetentionPolicy {
23
- retain = "retain",
24
- deleteIfNotUsed = "deleteIfNotUsed"
22
+ retain = "retain",
23
+ deleteIfNotUsed = "deleteIfNotUsed"
25
24
  }
26
25
  type UploadAssetImageMetadata = {
27
- transformations: UploadAssetImageTransformation[];
26
+ transformations: UploadAssetImageTransformation[];
28
27
  };
29
28
  type UploadAssetImageTransformation = UploadAssetExtractTransformation | UploadAssetFitTransformation | UploadAssetConvertTransformation;
30
29
  type UploadAssetExtractTransformation = {
31
- name: 'extract';
32
- options: {
33
- top: number;
34
- left: number;
35
- areaWidth: number;
36
- areaHeight: number;
37
- rotate?: 0 | 90 | 180 | 270 | undefined;
38
- };
30
+ name: 'extract';
31
+ options: {
32
+ top: number;
33
+ left: number;
34
+ areaWidth: number;
35
+ areaHeight: number;
36
+ rotate?: 0 | 90 | 180 | 270 | undefined;
37
+ };
39
38
  };
40
39
  type UploadAssetFitTransformation = {
41
- name: 'fit';
42
- options: {
43
- width: number;
44
- height: number;
45
- };
40
+ name: 'fit';
41
+ options: {
42
+ width: number;
43
+ height: number;
44
+ };
46
45
  };
47
46
  type UploadAssetConvertTransformation = {
48
- name: 'convert';
49
- options: UploadAssetConvertTransformationJpegOptions | UploadAssetConvertTransformationPngOptions | UploadAssetConvertTransformationAvifOptions;
47
+ name: 'convert';
48
+ options: UploadAssetConvertTransformationJpegOptions | UploadAssetConvertTransformationPngOptions | UploadAssetConvertTransformationAvifOptions;
50
49
  };
51
50
  type UploadAssetConvertTransformationJpegOptions = {
52
- format: 'jpeg';
53
- options: {
54
- quality: number;
55
- interlace: boolean;
56
- };
51
+ format: 'jpeg';
52
+ options: {
53
+ quality: number;
54
+ interlace: boolean;
55
+ };
57
56
  };
58
57
  type UploadAssetConvertTransformationPngOptions = {
59
- format: 'png';
60
- options: {
61
- compressionLevel: number;
62
- };
58
+ format: 'png';
59
+ options: {
60
+ compressionLevel: number;
61
+ };
63
62
  };
64
63
  type UploadAssetConvertTransformationAvifOptions = {
65
- format: 'avif';
66
- options: {
67
- quality: number;
68
- lossless: boolean;
69
- };
64
+ format: 'avif';
65
+ options: {
66
+ quality: number;
67
+ lossless: boolean;
68
+ };
70
69
  };
71
-
70
+ //#endregion
71
+ //#region src/uploader.d.ts
72
72
  type ContelloUploaderParams = {
73
- /**
74
- * Base URL of the Contello server.
75
- * Example: 'https://example.com'
76
- */
77
- url: string;
78
- /**
79
- * Project identifier for the Contello project.
80
- * Example: 'myProjectId'
81
- */
82
- project: string;
83
- /**
84
- * Authentication token for the Contello API.
85
- * Example: 'app-a123b456c7890d123456789'
86
- */
87
- token: string;
88
- /**
89
- * Transport protocol to use for uploading files.
90
- * 'http' uses XMLHttpRequest, 'ws' uses WebSocket.
91
- * @default 'ws'
92
- */
93
- transport?: 'http' | 'ws';
94
- /**
95
- * Size of each chunk in bytes when uploading files.
96
- * Default is 4MB.
97
- * @default 4_194_304
98
- */
99
- chunkSize?: number;
73
+ /**
74
+ * Base URL of the Contello server.
75
+ * Example: 'https://example.com'
76
+ */
77
+ url: string;
78
+ /**
79
+ * Project identifier for the Contello project.
80
+ * Example: 'myProjectId'
81
+ */
82
+ project: string;
83
+ /**
84
+ * Authentication token for the Contello API.
85
+ * Example: 'app-a123b456c7890d123456789'
86
+ */
87
+ token: string;
88
+ /**
89
+ * Transport protocol to use for uploading files.
90
+ * 'http' uses XMLHttpRequest, 'ws' uses WebSocket.
91
+ * @default 'ws'
92
+ */
93
+ transport?: 'http' | 'ws';
94
+ /**
95
+ * Size of each chunk in bytes when uploading files.
96
+ * Default is 4MB.
97
+ * @default 4_194_304
98
+ */
99
+ chunkSize?: number;
100
100
  };
101
101
  /**
102
102
  * ContelloUploader is a class for uploading files to a Contello server.
103
103
  * It supports both WebSocket and HTTP transports.
104
104
  */
105
105
  declare class ContelloUploader {
106
- private params;
107
- constructor(params: ContelloUploaderParams);
108
- /** Uploads a file and resolves with the asset ID once complete. */
109
- upload(file: File, meta?: UploadAssetMetadata | undefined, options?: {
110
- abort?: AbortSignal;
111
- } | undefined): Promise<{
112
- id: string;
113
- }>;
114
- /** Uploads a file and returns an Observable emitting progress events and the final asset ID. */
115
- uploadWithEvents(file: File, meta?: UploadAssetMetadata | undefined, options?: {
116
- abort?: AbortSignal;
117
- } | undefined): rxjs.Observable<UploadAssetProgress | {
118
- id: string;
119
- }>;
106
+ private params;
107
+ constructor(params: ContelloUploaderParams);
108
+ /** Uploads a file and resolves with the asset ID once complete. */
109
+ upload(file: File, meta?: UploadAssetMetadata | undefined, options?: {
110
+ abort?: AbortSignal;
111
+ } | undefined): Promise<{
112
+ id: string;
113
+ }>;
114
+ /** Uploads a file and returns an Observable emitting progress events and the final asset ID. */
115
+ uploadWithEvents(file: File, meta?: UploadAssetMetadata | undefined, options?: {
116
+ abort?: AbortSignal;
117
+ } | undefined): import("rxjs").Observable<UploadAssetProgress | {
118
+ id: string;
119
+ }>;
120
120
  }
121
-
122
- export { ContelloUploader, type ContelloUploaderParams, type UploadAssetAnnotation, type UploadAssetConvertTransformation, type UploadAssetConvertTransformationAvifOptions, type UploadAssetConvertTransformationJpegOptions, type UploadAssetConvertTransformationPngOptions, type UploadAssetExtractTransformation, type UploadAssetFitTransformation, type UploadAssetImageMetadata, type UploadAssetImageTransformation, type UploadAssetMetadata, type UploadAssetProgress, UploadAssetRetentionPolicy };
121
+ //#endregion
122
+ export { ContelloUploader, ContelloUploaderParams, UploadAssetAnnotation, UploadAssetConvertTransformation, UploadAssetConvertTransformationAvifOptions, UploadAssetConvertTransformationJpegOptions, UploadAssetConvertTransformationPngOptions, UploadAssetExtractTransformation, UploadAssetFitTransformation, UploadAssetImageMetadata, UploadAssetImageTransformation, UploadAssetMetadata, UploadAssetProgress, UploadAssetRetentionPolicy };
123
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/upload-metadata.ts","../src/uploader.ts"],"mappings":";KAAY,mBAAA;EACV,IAAA;EACA,QAAA;EACA,IAAA;EACA,WAAA,GAAc,qBAAA;EACd,cAAA;EACA,KAAA,GAAQ,wBAAA;EACR,eAAA,GAAkB,0BAAA;EAClB,eAAA;EACA,SAAA;EACA,SAAA;AAAA;AAAA,KAGU,qBAAA;EACV,GAAA;EACA,KAAK;AAAA;AAAA,KAGK,mBAAA;EACV,QAAQ;AAAA;AAAA,aAGE,0BAAA;EACV,MAAA;EACA,eAAe;AAAA;AAAA,KAGL,wBAAA;EACV,eAAA,EAAiB,8BAA8B;AAAA;AAAA,KAGrC,8BAAA,GACR,gCAAA,GACA,4BAAA,GACA,gCAAA;AAAA,KAEQ,gCAAA;EACV,IAAA;EACA,OAAA;IACE,GAAA;IACA,IAAA;IACA,SAAA;IACA,UAAA;IACA,MAAA;EAAA;AAAA;AAAA,KAIQ,4BAAA;EACV,IAAA;EACA,OAAA;IACE,KAAA;IACA,MAAA;EAAA;AAAA;AAAA,KAIQ,gCAAA;EACV,IAAA;EACA,OAAA,EACI,2CAAA,GACA,0CAAA,GACA,2CAAA;AAAA;AAAA,KAGM,2CAAA;EACV,MAAA;EACA,OAAA;IACE,OAAA;IACA,SAAA;EAAA;AAAA;AAAA,KAIQ,0CAAA;EACV,MAAA;EACA,OAAA;IACE,gBAAA;EAAA;AAAA;AAAA,KAIQ,2CAAA;EACV,MAAA;EACA,OAAA;IACE,OAAA;IACA,QAAA;EAAA;AAAA;;;KC7EQ,sBAAA;EDLmB;;;;ECU7B,GAAA;EDH4C;;;;ECQ5C,OAAA;EDXA;;;;ECgBA,KAAA;EDbA;;;;;ECmBA,SAAA;EDhBS;AAGX;;;;ECmBE,SAAA;AAAA;;;;ADbQ;cCoBG,gBAAA;EAAA,QACH,MAAA;cAEI,MAAA,EAAQ,sBAAA;EDnBpB;EC8BM,MAAA,CACJ,IAAA,EAAM,IAAA,EACN,IAAA,GAAO,mBAAA,cACP,OAAA;IAAY,KAAA,GAAQ,WAAA;EAAA,gBACnB,OAAA;IAAU,EAAA;EAAA;ED7BkC;ECkC/C,gBAAA,CAAiB,IAAA,EAAM,IAAA,EAAM,IAAA,GAAO,mBAAA,cAAiC,OAAA;IAAY,KAAA,GAAQ,WAAA;EAAA,+BAAyB,UAAA,CAAd,mBAAA"}
package/dist/index.js CHANGED
@@ -1,183 +1,155 @@
1
- // src/upload-metadata.ts
2
- var UploadAssetRetentionPolicy = /* @__PURE__ */ ((UploadAssetRetentionPolicy2) => {
3
- UploadAssetRetentionPolicy2["retain"] = "retain";
4
- UploadAssetRetentionPolicy2["deleteIfNotUsed"] = "deleteIfNotUsed";
5
- return UploadAssetRetentionPolicy2;
6
- })(UploadAssetRetentionPolicy || {});
7
-
8
- // src/uploader.ts
9
- import { filter, firstValueFrom } from "rxjs";
10
-
11
- // src/implementations/ws.ts
12
- import { Observable } from "rxjs";
1
+ import { Observable, filter, firstValueFrom } from "rxjs";
2
+ //#region src/upload-metadata.ts
3
+ let UploadAssetRetentionPolicy = /* @__PURE__ */ function(UploadAssetRetentionPolicy) {
4
+ UploadAssetRetentionPolicy["retain"] = "retain";
5
+ UploadAssetRetentionPolicy["deleteIfNotUsed"] = "deleteIfNotUsed";
6
+ return UploadAssetRetentionPolicy;
7
+ }({});
8
+ //#endregion
9
+ //#region src/implementations/ws.ts
10
+ /** Uploads a file via WebSocket using chunked binary transfer. Emits progress events and the asset ID on completion. */
13
11
  function uploadViaWebSocket({ url, project, token, chunkSize }, file, meta, options) {
14
- const metadata = { ...meta ?? {}, projectRef: project };
15
- return new Observable((obs) => {
16
- const parsedUrl = new URL(url);
17
- const ws = new WebSocket(
18
- `${parsedUrl.protocol === "https:" ? "wss" : "ws"}://${parsedUrl.host}/api/v1/assets/ws`,
19
- "contello-file-upload-v1"
20
- );
21
- const reader = typeof FileReader !== "undefined" ? new FileReader() : null;
22
- let offset = 0;
23
- let done = false;
24
- let ackReceived = false;
25
- ws.onopen = async () => {
26
- const initFrame = {
27
- type: "init",
28
- metadata: {
29
- ...metadata,
30
- name: file.name,
31
- mimeType: file.type,
32
- size: file.size
33
- },
34
- token
35
- };
36
- ws.send(JSON.stringify(initFrame));
37
- function readSlice() {
38
- const slice = file.slice(offset, offset + chunkSize);
39
- if (reader) {
40
- reader.readAsArrayBuffer(slice);
41
- } else {
42
- slice.arrayBuffer().then((buffer) => {
43
- handleArrayBuffer(buffer);
44
- }).catch((err) => {
45
- obs.error(err);
46
- });
47
- }
48
- }
49
- function markAsDone() {
50
- reader?.abort();
51
- ws.send(JSON.stringify({ type: "done" }));
52
- }
53
- function startUpload() {
54
- if (ackReceived) {
55
- readSlice();
56
- }
57
- }
58
- function handleArrayBuffer(buffer) {
59
- if (ws.readyState === WebSocket.OPEN) {
60
- ws.send(buffer);
61
- offset += buffer.byteLength;
62
- if (offset < file.size) {
63
- readSlice();
64
- } else {
65
- markAsDone();
66
- }
67
- } else {
68
- obs.error(new Error("WebSocket is closed"));
69
- reader?.abort();
70
- }
71
- }
72
- if (reader) {
73
- reader.onload = () => {
74
- if (reader.result && reader.result instanceof ArrayBuffer) {
75
- handleArrayBuffer(reader.result);
76
- } else {
77
- markAsDone();
78
- }
79
- };
80
- }
81
- ws.onmessage = (event) => {
82
- const message = JSON.parse(event.data);
83
- if (message.type === "ack") {
84
- ackReceived = true;
85
- startUpload();
86
- return;
87
- }
88
- if (message.type === "progress") {
89
- return obs.next({ progress: message.bytesReceived / file.size * 100 });
90
- }
91
- if (message.type === "done") {
92
- if (message.id) {
93
- done = true;
94
- obs.next({ id: message.id });
95
- obs.complete();
96
- } else {
97
- obs.error(new Error("No asset id received"));
98
- }
99
- return ws.close();
100
- }
101
- obs.error(new Error(`WebSocket message with unknown type ${JSON.stringify(message)}`));
102
- ws.close();
103
- };
104
- };
105
- ws.onerror = (error) => obs.error(error);
106
- ws.onclose = () => {
107
- if (!done) {
108
- obs.error(new Error("Connection closed"));
109
- }
110
- };
111
- const abortHandler = () => {
112
- if (!done) {
113
- reader?.abort();
114
- ws.close();
115
- obs.error(new Error("Upload aborted"));
116
- }
117
- };
118
- options?.abort?.addEventListener("abort", abortHandler);
119
- return () => {
120
- options?.abort?.removeEventListener("abort", abortHandler);
121
- ws.close();
122
- };
123
- });
12
+ const metadata = {
13
+ ...meta,
14
+ projectRef: project
15
+ };
16
+ return new Observable((obs) => {
17
+ const parsedUrl = new URL(url);
18
+ const ws = new WebSocket(`${parsedUrl.protocol === "https:" ? "wss" : "ws"}://${parsedUrl.host}/api/v1/assets/ws`, "contello-file-upload-v1");
19
+ let offset = 0;
20
+ let done = false;
21
+ let ackReceived = false;
22
+ ws.addEventListener("open", () => {
23
+ const initFrame = {
24
+ type: "init",
25
+ metadata: {
26
+ ...metadata,
27
+ name: file.name,
28
+ mimeType: file.type,
29
+ size: file.size
30
+ },
31
+ token
32
+ };
33
+ ws.send(JSON.stringify(initFrame));
34
+ function readSlice() {
35
+ (async () => {
36
+ try {
37
+ handleArrayBuffer(await file.slice(offset, offset + chunkSize).arrayBuffer());
38
+ } catch (error) {
39
+ obs.error(error);
40
+ }
41
+ })();
42
+ }
43
+ function markAsDone() {
44
+ ws.send(JSON.stringify({ type: "done" }));
45
+ }
46
+ function startUpload() {
47
+ if (ackReceived) readSlice();
48
+ }
49
+ function handleArrayBuffer(buffer) {
50
+ if (options?.abort?.aborted) return;
51
+ if (ws.readyState === WebSocket.OPEN) {
52
+ ws.send(buffer);
53
+ offset += buffer.byteLength;
54
+ if (offset < file.size) readSlice();
55
+ else markAsDone();
56
+ } else obs.error(/* @__PURE__ */ new Error("WebSocket is closed"));
57
+ }
58
+ ws.addEventListener("message", (event) => {
59
+ const message = JSON.parse(event.data);
60
+ if (message.type === "ack") {
61
+ ackReceived = true;
62
+ startUpload();
63
+ return;
64
+ }
65
+ if (message.type === "progress") return obs.next({ progress: message.bytesReceived / file.size * 100 });
66
+ if (message.type === "done") {
67
+ if (message.id) {
68
+ done = true;
69
+ obs.next({ id: message.id });
70
+ obs.complete();
71
+ } else obs.error(/* @__PURE__ */ new Error("No asset id received"));
72
+ return ws.close();
73
+ }
74
+ obs.error(/* @__PURE__ */ new Error(`WebSocket message with unknown type ${JSON.stringify(message)}`));
75
+ ws.close();
76
+ });
77
+ });
78
+ ws.addEventListener("error", (error) => obs.error(error));
79
+ ws.addEventListener("close", () => {
80
+ if (!done) obs.error(/* @__PURE__ */ new Error("Connection closed"));
81
+ });
82
+ const abortHandler = () => {
83
+ if (done) return;
84
+ ws.close();
85
+ obs.error(/* @__PURE__ */ new Error("Upload aborted"));
86
+ };
87
+ options?.abort?.addEventListener("abort", abortHandler);
88
+ return () => {
89
+ options?.abort?.removeEventListener("abort", abortHandler);
90
+ ws.close();
91
+ };
92
+ });
124
93
  }
125
-
126
- // src/implementations/xhr.ts
127
- import { Observable as Observable2 } from "rxjs";
94
+ //#endregion
95
+ //#region src/implementations/xhr.ts
96
+ /** Uploads a file via XMLHttpRequest with multipart form data. Emits progress events and the asset ID on completion. */
128
97
  function uploadViaXhr({ url, project, token }, file, meta, options) {
129
- return new Observable2((obs) => {
130
- const data = new FormData();
131
- data.append("metadata", JSON.stringify({ ...meta ?? {}, projectRef: project }));
132
- data.append("file", file);
133
- const xhr = new XMLHttpRequest();
134
- xhr.setRequestHeader("Authorization", `Bearer ${token}`);
135
- xhr.open("POST", `${url}/api/v1/assets`, true);
136
- xhr.upload.onprogress = async (event) => {
137
- if (event.lengthComputable) {
138
- obs.next({ progress: event.loaded / event.total * 100 });
139
- }
140
- };
141
- xhr.onload = async () => {
142
- if (xhr.status >= 200 && xhr.status < 300) {
143
- const { id } = JSON.parse(xhr.responseText);
144
- obs.next({ id });
145
- return obs.complete();
146
- }
147
- obs.error(new Error(`${xhr.status}: ${xhr.statusText}`));
148
- };
149
- xhr.onerror = (error) => obs.error(error);
150
- xhr.send(data);
151
- options?.abort?.addEventListener("abort", () => xhr.abort());
152
- return () => xhr.abort();
153
- });
98
+ return new Observable((obs) => {
99
+ const data = new FormData();
100
+ data.append("metadata", JSON.stringify({
101
+ ...meta,
102
+ projectRef: project
103
+ }));
104
+ data.append("file", file);
105
+ const xhr = new XMLHttpRequest();
106
+ xhr.setRequestHeader("Authorization", `Bearer ${token}`);
107
+ xhr.open("POST", `${url}/api/v1/assets`, true);
108
+ xhr.upload.addEventListener("progress", (event) => {
109
+ if (event.lengthComputable) obs.next({ progress: event.loaded / event.total * 100 });
110
+ });
111
+ xhr.addEventListener("load", () => {
112
+ if (xhr.status >= 200 && xhr.status < 300) {
113
+ const { id } = JSON.parse(xhr.responseText);
114
+ obs.next({ id });
115
+ return obs.complete();
116
+ }
117
+ obs.error(/* @__PURE__ */ new Error(`${xhr.status}: ${xhr.statusText}`));
118
+ });
119
+ xhr.addEventListener("error", (error) => obs.error(error));
120
+ xhr.send(data);
121
+ options?.abort?.addEventListener("abort", () => xhr.abort());
122
+ return () => xhr.abort();
123
+ });
154
124
  }
155
-
156
- // src/uploader.ts
125
+ //#endregion
126
+ //#region src/uploader.ts
127
+ /**
128
+ * ContelloUploader is a class for uploading files to a Contello server.
129
+ * It supports both WebSocket and HTTP transports.
130
+ */
157
131
  var ContelloUploader = class {
158
- params;
159
- constructor(params) {
160
- this.params = {
161
- url: params.url,
162
- project: params.project,
163
- token: params.token,
164
- transport: params.transport || "ws",
165
- chunkSize: params.chunkSize || 4 * 1024 * 1024
166
- };
167
- }
168
- /** Uploads a file and resolves with the asset ID once complete. */
169
- async upload(file, meta, options) {
170
- return firstValueFrom(this.uploadWithEvents(file, meta, options).pipe(filter((r) => "id" in r)));
171
- }
172
- /** Uploads a file and returns an Observable emitting progress events and the final asset ID. */
173
- uploadWithEvents(file, meta, options) {
174
- if (this.params.transport === "http") {
175
- return uploadViaXhr(this.params, file, meta, options);
176
- }
177
- return uploadViaWebSocket(this.params, file, meta, options);
178
- }
179
- };
180
- export {
181
- ContelloUploader,
182
- UploadAssetRetentionPolicy
132
+ params;
133
+ constructor(params) {
134
+ this.params = {
135
+ url: params.url,
136
+ project: params.project,
137
+ token: params.token,
138
+ transport: params.transport || "ws",
139
+ chunkSize: params.chunkSize || 4 * 1024 * 1024
140
+ };
141
+ }
142
+ /** Uploads a file and resolves with the asset ID once complete. */
143
+ async upload(file, meta, options) {
144
+ return firstValueFrom(this.uploadWithEvents(file, meta, options).pipe(filter((r) => "id" in r)));
145
+ }
146
+ /** Uploads a file and returns an Observable emitting progress events and the final asset ID. */
147
+ uploadWithEvents(file, meta, options) {
148
+ if (this.params.transport === "http") return uploadViaXhr(this.params, file, meta, options);
149
+ return uploadViaWebSocket(this.params, file, meta, options);
150
+ }
183
151
  };
152
+ //#endregion
153
+ export { ContelloUploader, UploadAssetRetentionPolicy };
154
+
155
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/upload-metadata.ts","../src/implementations/ws.ts","../src/implementations/xhr.ts","../src/uploader.ts"],"sourcesContent":["export type UploadAssetMetadata = {\n name?: string | undefined;\n mimeType?: string | undefined;\n size?: number | undefined;\n annotations?: UploadAssetAnnotation[] | undefined;\n collectionRefs?: string[] | undefined;\n image?: UploadAssetImageMetadata | undefined;\n retentionPolicy?: UploadAssetRetentionPolicy | undefined;\n generatePreview?: boolean | undefined;\n createdAt?: string | undefined;\n updatedAt?: string | undefined;\n};\n\nexport type UploadAssetAnnotation = {\n key: string;\n value: string;\n};\n\nexport type UploadAssetProgress = {\n progress: number;\n};\n\nexport enum UploadAssetRetentionPolicy {\n retain = 'retain',\n deleteIfNotUsed = 'deleteIfNotUsed',\n}\n\nexport type UploadAssetImageMetadata = {\n transformations: UploadAssetImageTransformation[];\n};\n\nexport type UploadAssetImageTransformation =\n | UploadAssetExtractTransformation\n | UploadAssetFitTransformation\n | UploadAssetConvertTransformation;\n\nexport type UploadAssetExtractTransformation = {\n name: 'extract';\n options: {\n top: number;\n left: number;\n areaWidth: number;\n areaHeight: number;\n rotate?: 0 | 90 | 180 | 270 | undefined;\n };\n};\n\nexport type UploadAssetFitTransformation = {\n name: 'fit';\n options: {\n width: number;\n height: number;\n };\n};\n\nexport type UploadAssetConvertTransformation = {\n name: 'convert';\n options:\n | UploadAssetConvertTransformationJpegOptions\n | UploadAssetConvertTransformationPngOptions\n | UploadAssetConvertTransformationAvifOptions;\n};\n\nexport type UploadAssetConvertTransformationJpegOptions = {\n format: 'jpeg';\n options: {\n quality: number; // 0-100\n interlace: boolean;\n };\n};\n\nexport type UploadAssetConvertTransformationPngOptions = {\n format: 'png';\n options: {\n compressionLevel: number; // 0-9\n };\n};\n\nexport type UploadAssetConvertTransformationAvifOptions = {\n format: 'avif';\n options: {\n quality: number; // 0-100\n lossless: boolean;\n };\n};\n","import { Observable } from 'rxjs';\nimport type { UploadAssetMetadata, UploadAssetProgress } from '../upload-metadata';\nimport type { ContelloUploaderParams } from '../uploader';\n\ntype InitFrame = {\n type: 'init';\n metadata: UploadAssetMetadata & { projectRef: string };\n token: string;\n};\n\ntype AckMessage = { type: 'ack' };\n\ntype ProgressMessage = { type: 'progress'; bytesReceived: number };\n\ntype DoneMessage = { type: 'done'; id: string };\n\n/** Uploads a file via WebSocket using chunked binary transfer. Emits progress events and the asset ID on completion. */\nexport function uploadViaWebSocket(\n { url, project, token, chunkSize }: Required<ContelloUploaderParams>,\n file: File,\n meta: UploadAssetMetadata | undefined,\n options: { abort?: AbortSignal } | undefined,\n) {\n const metadata = { ...meta, projectRef: project };\n\n return new Observable<UploadAssetProgress | { id: string }>((obs) => {\n const parsedUrl = new URL(url);\n\n const ws = new WebSocket(\n `${parsedUrl.protocol === 'https:' ? 'wss' : 'ws'}://${parsedUrl.host}/api/v1/assets/ws`,\n 'contello-file-upload-v1',\n );\n\n let offset = 0;\n\n let done = false;\n let ackReceived = false;\n\n ws.addEventListener('open', () => {\n const initFrame: InitFrame = {\n type: 'init',\n metadata: {\n ...metadata,\n name: file.name,\n mimeType: file.type,\n size: file.size,\n },\n token,\n };\n\n ws.send(JSON.stringify(initFrame));\n\n function readSlice() {\n void (async () => {\n try {\n const buffer = await file.slice(offset, offset + chunkSize).arrayBuffer();\n\n handleArrayBuffer(buffer);\n } catch (error) {\n obs.error(error);\n }\n })();\n }\n\n function markAsDone() {\n ws.send(JSON.stringify({ type: 'done' }));\n }\n\n function startUpload() {\n if (ackReceived) {\n readSlice();\n }\n }\n\n function handleArrayBuffer(buffer: ArrayBuffer) {\n // bail between chunks if the upload was aborted — abortHandler already closed the socket and errored\n if (options?.abort?.aborted) {\n return;\n }\n\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(buffer);\n offset += buffer.byteLength;\n\n if (offset < file.size) {\n readSlice();\n } else {\n markAsDone();\n }\n } else {\n obs.error(new Error('WebSocket is closed'));\n }\n }\n\n ws.addEventListener('message', (event) => {\n const message: AckMessage | ProgressMessage | DoneMessage = JSON.parse(event.data);\n\n if (message.type === 'ack') {\n ackReceived = true;\n startUpload();\n return;\n }\n\n if (message.type === 'progress') {\n return obs.next({ progress: (message.bytesReceived / file.size) * 100 });\n }\n\n if (message.type === 'done') {\n if (message.id) {\n done = true;\n\n obs.next({ id: message.id });\n obs.complete();\n } else {\n obs.error(new Error('No asset id received'));\n }\n\n return ws.close();\n }\n\n obs.error(new Error(`WebSocket message with unknown type ${JSON.stringify(message)}`));\n ws.close();\n });\n });\n\n ws.addEventListener('error', (error) => obs.error(error));\n\n ws.addEventListener('close', () => {\n if (!done) {\n obs.error(new Error('Connection closed'));\n }\n });\n\n const abortHandler = () => {\n if (done) {\n return;\n }\n\n ws.close();\n obs.error(new Error('Upload aborted'));\n };\n\n options?.abort?.addEventListener('abort', abortHandler);\n\n return () => {\n options?.abort?.removeEventListener('abort', abortHandler);\n ws.close();\n };\n });\n}\n","import { Observable } from 'rxjs';\nimport type { UploadAssetMetadata, UploadAssetProgress } from '../upload-metadata';\nimport type { ContelloUploaderParams } from '../uploader';\n\n/** Uploads a file via XMLHttpRequest with multipart form data. Emits progress events and the asset ID on completion. */\nexport function uploadViaXhr(\n { url, project, token }: Required<ContelloUploaderParams>,\n file: File,\n meta: UploadAssetMetadata | undefined,\n options: { abort?: AbortSignal } | undefined,\n) {\n return new Observable<UploadAssetProgress | { id: string }>((obs) => {\n const data = new FormData();\n\n data.append('metadata', JSON.stringify({ ...meta, projectRef: project }));\n data.append('file', file);\n\n const xhr = new XMLHttpRequest();\n\n xhr.setRequestHeader('Authorization', `Bearer ${token}`);\n\n xhr.open('POST', `${url}/api/v1/assets`, true);\n\n xhr.upload.addEventListener('progress', (event) => {\n if (event.lengthComputable) {\n obs.next({ progress: (event.loaded / event.total) * 100 });\n }\n });\n\n xhr.addEventListener('load', () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n const { id }: { id: string } = JSON.parse(xhr.responseText);\n\n obs.next({ id });\n\n return obs.complete();\n }\n\n obs.error(new Error(`${xhr.status}: ${xhr.statusText}`));\n });\n\n xhr.addEventListener('error', (error) => obs.error(error));\n xhr.send(data);\n\n options?.abort?.addEventListener('abort', () => xhr.abort());\n\n return () => xhr.abort();\n });\n}\n","import { filter, firstValueFrom } from 'rxjs';\nimport { uploadViaWebSocket } from './implementations/ws';\nimport { uploadViaXhr } from './implementations/xhr';\nimport type { UploadAssetMetadata } from './upload-metadata';\n\nexport type ContelloUploaderParams = {\n /**\n * Base URL of the Contello server.\n * Example: 'https://example.com'\n */\n url: string;\n /**\n * Project identifier for the Contello project.\n * Example: 'myProjectId'\n */\n project: string;\n /**\n * Authentication token for the Contello API.\n * Example: 'app-a123b456c7890d123456789'\n */\n token: string;\n /**\n * Transport protocol to use for uploading files.\n * 'http' uses XMLHttpRequest, 'ws' uses WebSocket.\n * @default 'ws'\n */\n transport?: 'http' | 'ws';\n /**\n * Size of each chunk in bytes when uploading files.\n * Default is 4MB.\n * @default 4_194_304\n */\n chunkSize?: number;\n};\n\n/**\n * ContelloUploader is a class for uploading files to a Contello server.\n * It supports both WebSocket and HTTP transports.\n */\nexport class ContelloUploader {\n private params: Required<ContelloUploaderParams>;\n\n constructor(params: ContelloUploaderParams) {\n this.params = {\n url: params.url,\n project: params.project,\n token: params.token,\n transport: params.transport || 'ws',\n chunkSize: params.chunkSize || 4 * 1024 * 1024,\n };\n }\n\n /** Uploads a file and resolves with the asset ID once complete. */\n async upload(\n file: File,\n meta?: UploadAssetMetadata | undefined,\n options?: { abort?: AbortSignal } | undefined,\n ): Promise<{ id: string }> {\n return firstValueFrom(this.uploadWithEvents(file, meta, options).pipe(filter((r) => 'id' in r)));\n }\n\n /** Uploads a file and returns an Observable emitting progress events and the final asset ID. */\n uploadWithEvents(file: File, meta?: UploadAssetMetadata | undefined, options?: { abort?: AbortSignal } | undefined) {\n if (this.params.transport === 'http') {\n return uploadViaXhr(this.params, file, meta, options);\n }\n\n return uploadViaWebSocket(this.params, file, meta, options);\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contello/uploader",
3
- "version": "8.21.3",
3
+ "version": "8.21.5",
4
4
  "description": "File uploader for Contello CMS with WebSocket and HTTP transports",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -10,12 +10,6 @@
10
10
  "url": "https://github.com/entwico/contello-js",
11
11
  "directory": "packages/uploader"
12
12
  },
13
- "scripts": {
14
- "build": "tsup",
15
- "lint": "eslint .",
16
- "lint:fix": "eslint . --fix",
17
- "typecheck": "tsc --noEmit"
18
- },
19
13
  "peerDependencies": {
20
14
  "rxjs": "^7.0.0"
21
15
  },
@@ -35,5 +29,11 @@
35
29
  "publishConfig": {
36
30
  "access": "public",
37
31
  "registry": "https://registry.npmjs.org"
32
+ },
33
+ "scripts": {
34
+ "build": "tsdown",
35
+ "lint": "eslint . --cache",
36
+ "lint:fix": "eslint . --fix",
37
+ "typecheck": "tsc --noEmit"
38
38
  }
39
- }
39
+ }