@capgo/capacitor-uploader 8.1.21 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -142,6 +142,25 @@ uploadToCustomServer(filePath, serverUrl);
142
142
 
143
143
  ```
144
144
 
145
+ ### Example multi-file multipart upload
146
+
147
+ ```typescript
148
+ import { Uploader } from '@capgo/capacitor-uploader';
149
+
150
+ const { id } = await Uploader.startUpload({
151
+ serverUrl: 'https://api.example.com/upload',
152
+ method: 'POST',
153
+ uploadType: 'multipart',
154
+ files: [
155
+ { filePath: 'file:///...photo1.jpg', fieldName: 'images[]', mimeType: 'image/jpeg' },
156
+ { filePath: 'file:///...photo2.jpg', fieldName: 'images[]', mimeType: 'image/jpeg' },
157
+ ],
158
+ parameters: { albumId: '7' },
159
+ headers: { Authorization: 'Bearer token' },
160
+ });
161
+ console.log('Upload started with ID:', id);
162
+ ```
163
+
145
164
  ### Example with Capacitor Camera preview
146
165
 
147
166
  Documentation for the [Capacitor Camera preview](https://github.com/Cap-go/camera-preview)
@@ -295,20 +314,30 @@ Get the native Capacitor plugin version.
295
314
 
296
315
  #### uploadOption
297
316
 
317
+ | Prop | Type | Description | Default | Since |
318
+ | ----------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----- |
319
+ | **`filePath`** | <code>string</code> | The local file path of the file to upload. Can be a file:// URL or an absolute path. If you need to upload multiple files in a single multipart request, use `files`. | | 0.0.1 |
320
+ | **`files`** | <code>UploadFileOption[]</code> | Multiple files to upload in a single request. When provided, uploads are sent as `multipart/form-data` with one part per file. Use `fieldName` to control each part name (e.g. `images[]`). Note: `PUT` uploads (e.g. presigned S3 URLs) only support a single file. | | 0.0.3 |
321
+ | **`serverUrl`** | <code>string</code> | The server URL endpoint where the file should be uploaded. | | 0.0.1 |
322
+ | **`notificationTitle`** | <code>string</code> | The title of the upload notification shown to the user. Android only. | <code>'Uploading'</code> | 0.0.1 |
323
+ | **`headers`** | <code>{ [key: string]: string; }</code> | HTTP headers to send with the upload request. Useful for authentication tokens, content types, etc. | | 0.0.1 |
324
+ | **`method`** | <code>'PUT' \| 'POST'</code> | The HTTP method to use for the upload request. | <code>'POST'</code> | 0.0.1 |
325
+ | **`mimeType`** | <code>string</code> | The MIME type of the file being uploaded. If not specified, the plugin will attempt to determine it automatically. | | 0.0.1 |
326
+ | **`parameters`** | <code>{ [key: string]: string; }</code> | Additional form parameters to send with the upload request. These will be included as form data in multipart uploads. | | 0.0.1 |
327
+ | **`maxRetries`** | <code>number</code> | The maximum number of times to retry the upload if it fails. | <code>0</code> | 0.0.1 |
328
+ | **`uploadType`** | <code>'binary' \| 'multipart'</code> | The type of upload to perform. - 'binary': Uploads the file as raw binary data in the request body - 'multipart': Uploads the file as multipart/form-data | <code>'binary' when `method` is `'PUT'`, otherwise `'multipart'`</code> | 0.0.2 |
329
+ | **`fileField`** | <code>string</code> | The form field name for the file when using multipart upload type. Only used when uploadType is 'multipart'. For multi-file uploads via `files`, this is used as the default field name when a file entry does not specify `fieldName`. | <code>'file'</code> | 0.0.2 |
330
+
331
+
332
+ #### UploadFileOption
333
+
298
334
  Configuration options for uploading a file.
299
335
 
300
- | Prop | Type | Description | Default | Since |
301
- | ----------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ----- |
302
- | **`filePath`** | <code>string</code> | The local file path of the file to upload. Can be a file:// URL or an absolute path. | | 0.0.1 |
303
- | **`serverUrl`** | <code>string</code> | The server URL endpoint where the file should be uploaded. | | 0.0.1 |
304
- | **`notificationTitle`** | <code>string</code> | The title of the upload notification shown to the user. Android only. | <code>'Uploading'</code> | 0.0.1 |
305
- | **`headers`** | <code>{ [key: string]: string; }</code> | HTTP headers to send with the upload request. Useful for authentication tokens, content types, etc. | | 0.0.1 |
306
- | **`method`** | <code>'PUT' \| 'POST'</code> | The HTTP method to use for the upload request. | <code>'POST'</code> | 0.0.1 |
307
- | **`mimeType`** | <code>string</code> | The MIME type of the file being uploaded. If not specified, the plugin will attempt to determine it automatically. | | 0.0.1 |
308
- | **`parameters`** | <code>{ [key: string]: string; }</code> | Additional form parameters to send with the upload request. These will be included as form data in multipart uploads. | | 0.0.1 |
309
- | **`maxRetries`** | <code>number</code> | The maximum number of times to retry the upload if it fails. | <code>0</code> | 0.0.1 |
310
- | **`uploadType`** | <code>'binary' \| 'multipart'</code> | The type of upload to perform. - 'binary': Uploads the file as raw binary data in the request body - 'multipart': Uploads the file as multipart/form-data | <code>'binary'</code> | 0.0.2 |
311
- | **`fileField`** | <code>string</code> | The form field name for the file when using multipart upload type. Only used when uploadType is 'multipart'. | <code>'file'</code> | 0.0.2 |
336
+ | Prop | Type | Description | Since |
337
+ | --------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
338
+ | **`filePath`** | <code>string</code> | The local file path of the file to upload. Can be a file:// URL or an absolute path. | 0.0.3 |
339
+ | **`fieldName`** | <code>string</code> | The form field name for the file part when using multipart upload. If omitted, <a href="#uploadoption">`uploadOption.fileField`</a> is used (defaults to `'file'`). | 0.0.3 |
340
+ | **`mimeType`** | <code>string</code> | The MIME type of this file. If not specified, the plugin will attempt to determine it automatically. | 0.0.3 |
312
341
 
313
342
 
314
343
  #### PluginListenerHandle
@@ -334,4 +363,3 @@ Event emitted during the upload lifecycle.
334
363
 
335
364
  For the inspiration and the code on ios: https://github.com/Vydia/react-native-background-upload/tree/master
336
365
  For the API definition: https://www.npmjs.com/package/cordova-plugin-background-upload-put-s3
337
-
@@ -5,6 +5,7 @@ import android.content.Context;
5
5
  import android.database.Cursor;
6
6
  import android.net.Uri;
7
7
  import android.provider.OpenableColumns;
8
+ import java.util.List;
8
9
  import java.util.Map;
9
10
  import net.gotev.uploadservice.UploadServiceConfig;
10
11
  import net.gotev.uploadservice.data.UploadNotificationConfig;
@@ -16,6 +17,19 @@ public class Uploader {
16
17
 
17
18
  private final Context context;
18
19
 
20
+ public static class UploadFile {
21
+
22
+ public final String filePath;
23
+ public final String fieldName;
24
+ public final String mimeType;
25
+
26
+ public UploadFile(String filePath, String fieldName, String mimeType) {
27
+ this.filePath = filePath;
28
+ this.fieldName = fieldName;
29
+ this.mimeType = mimeType;
30
+ }
31
+ }
32
+
19
33
  public Uploader(Context context) {
20
34
  this.context = context;
21
35
  initializeUploadService(context);
@@ -41,26 +55,33 @@ public class Uploader {
41
55
  }
42
56
 
43
57
  public String startUpload(
44
- String filePath,
58
+ List<UploadFile> files,
45
59
  String serverUrl,
46
60
  Map<String, String> headers,
47
61
  Map<String, String> parameters,
48
62
  String httpMethod,
49
63
  String notificationTitle,
50
64
  int maxRetries,
51
- String mimeType,
52
- String uploadType,
53
- String fileField
65
+ String uploadType
54
66
  ) throws Exception {
55
67
  UploadNotificationConfig notificationConfig = createNotificationConfig(notificationTitle);
56
68
 
57
69
  if ("multipart".equals(uploadType)) {
70
+ if (files == null || files.isEmpty()) {
71
+ throw new IllegalArgumentException("Missing required parameter: files");
72
+ }
58
73
  MultipartUploadRequest request = new MultipartUploadRequest(context, serverUrl)
59
74
  .setMethod(httpMethod)
60
75
  .setNotificationConfig((ctx, uploadId) -> notificationConfig)
61
76
  .setMaxRetries(maxRetries);
62
77
 
63
- request.addFileToUpload(filePath, fileField, getFileNameFromUri(Uri.parse(filePath)), mimeType);
78
+ for (UploadFile file : files) {
79
+ if (file == null || file.filePath == null || file.filePath.isEmpty()) {
80
+ throw new IllegalArgumentException("Invalid file entry in files");
81
+ }
82
+ String fieldName = (file.fieldName == null || file.fieldName.isEmpty()) ? "file" : file.fieldName;
83
+ request.addFileToUpload(file.filePath, fieldName, getFileNameFromUri(Uri.parse(file.filePath)), file.mimeType);
84
+ }
64
85
 
65
86
  for (Map.Entry<String, String> entry : headers.entrySet()) {
66
87
  if (entry.getKey() != null && entry.getValue() != null) {
@@ -75,7 +96,23 @@ public class Uploader {
75
96
 
76
97
  return request.startUpload();
77
98
  } else {
78
- return startBinaryUpload(filePath, serverUrl, headers, parameters, httpMethod, notificationConfig, maxRetries, mimeType);
99
+ if (files == null || files.isEmpty()) {
100
+ throw new IllegalArgumentException("Missing required parameter: filePath or files");
101
+ }
102
+ if (files.size() != 1) {
103
+ throw new IllegalArgumentException("Binary uploads only support a single file");
104
+ }
105
+ UploadFile file = files.get(0);
106
+ return startBinaryUpload(
107
+ file.filePath,
108
+ serverUrl,
109
+ headers,
110
+ parameters,
111
+ httpMethod,
112
+ notificationConfig,
113
+ maxRetries,
114
+ file.mimeType
115
+ );
79
116
  }
80
117
  }
81
118
 
@@ -6,11 +6,13 @@ import android.content.Context;
6
6
  import android.net.Uri;
7
7
  import android.os.Build;
8
8
  import android.webkit.MimeTypeMap;
9
+ import com.getcapacitor.JSArray;
9
10
  import com.getcapacitor.JSObject;
10
11
  import com.getcapacitor.Plugin;
11
12
  import com.getcapacitor.PluginCall;
12
13
  import com.getcapacitor.PluginMethod;
13
14
  import com.getcapacitor.annotation.CapacitorPlugin;
15
+ import java.util.ArrayList;
14
16
  import java.util.HashMap;
15
17
  import java.util.Iterator;
16
18
  import java.util.Map;
@@ -18,6 +20,7 @@ import net.gotev.uploadservice.data.UploadInfo;
18
20
  import net.gotev.uploadservice.network.ServerResponse;
19
21
  import net.gotev.uploadservice.observer.request.RequestObserver;
20
22
  import net.gotev.uploadservice.observer.request.RequestObserverDelegate;
23
+ import org.json.JSONObject;
21
24
 
22
25
  @CapacitorPlugin(name = "Uploader")
23
26
  public class UploaderPlugin extends Plugin {
@@ -26,7 +29,7 @@ public class UploaderPlugin extends Plugin {
26
29
 
27
30
  private static final String CAPACITOR_CONTENT_PATH_PREFIX = "/_capacitor_content_";
28
31
 
29
- private final String pluginVersion = "8.1.21";
32
+ private final String pluginVersion = "8.2.0";
30
33
 
31
34
  private Uploader implementation;
32
35
 
@@ -117,46 +120,76 @@ public class UploaderPlugin extends Plugin {
117
120
  @PluginMethod
118
121
  public void startUpload(PluginCall call) {
119
122
  String filePath = call.getString("filePath");
123
+ JSArray filesArray = call.getArray("files");
120
124
  String serverUrl = call.getString("serverUrl");
121
125
 
122
- if (filePath == null || filePath.isEmpty()) {
123
- call.reject("Missing required parameter: filePath");
124
- return;
125
- }
126
126
  if (serverUrl == null || serverUrl.isEmpty()) {
127
127
  call.reject("Missing required parameter: serverUrl");
128
128
  return;
129
129
  }
130
130
 
131
- // Convert Capacitor web-accessible URLs to paths native code can open.
132
- // Capacitor 8+ removed Bridge.getLocalUrl(String); mirror AndroidProtocolHandler logic.
133
- String localFilePath = resolveCapacitorPath(filePath);
134
-
135
131
  JSObject headersObj = call.getObject("headers", new JSObject());
136
132
  JSObject parametersObj = call.getObject("parameters", new JSObject());
137
133
  String httpMethod = call.getString("method", "POST");
138
134
  String notificationTitle = call.getString("notificationTitle", "File Upload");
139
135
  int maxRetries = call.getInt("maxRetries", 2);
140
- String uploadType = call.getString("uploadType", "binary");
136
+ String uploadType = call.getString("uploadType");
137
+ if (uploadType == null || uploadType.isEmpty()) {
138
+ uploadType = "PUT".equalsIgnoreCase(httpMethod) ? "binary" : "multipart";
139
+ }
141
140
  String fileField = call.getString("fileField", "file");
142
141
 
143
142
  Map<String, String> headers = JSObjectToMap(headersObj);
144
143
  Map<String, String> parameters = JSObjectToMap(parametersObj);
145
144
 
146
145
  try {
147
- String mimeType = call.getString("mimeType", getMimeType(localFilePath));
146
+ ArrayList<Uploader.UploadFile> filesToUpload = new ArrayList<>();
147
+
148
+ if (filesArray != null && filesArray.length() > 0) {
149
+ for (int i = 0; i < filesArray.length(); i++) {
150
+ JSONObject fileObj = filesArray.getJSONObject(i);
151
+ String rawPath = fileObj.optString("filePath", null);
152
+ if (rawPath == null || rawPath.isEmpty()) {
153
+ call.reject("Missing required parameter: files[" + i + "].filePath");
154
+ return;
155
+ }
156
+
157
+ // Convert Capacitor web-accessible URLs to paths native code can open.
158
+ // Capacitor 8+ removed Bridge.getLocalUrl(String); mirror AndroidProtocolHandler logic.
159
+ String localPath = resolveCapacitorPath(rawPath);
160
+ String fieldName = fileObj.optString("fieldName", fileField);
161
+
162
+ String mimeType = null;
163
+ if (fileObj.has("mimeType")) {
164
+ mimeType = fileObj.optString("mimeType", null);
165
+ } else {
166
+ mimeType = call.getString("mimeType", null);
167
+ }
168
+ if (mimeType == null || mimeType.isEmpty()) {
169
+ mimeType = getMimeType(localPath);
170
+ }
171
+
172
+ filesToUpload.add(new Uploader.UploadFile(localPath, fieldName, mimeType));
173
+ }
174
+ } else {
175
+ if (filePath == null || filePath.isEmpty()) {
176
+ call.reject("Missing required parameter: filePath or files");
177
+ return;
178
+ }
179
+ String localFilePath = resolveCapacitorPath(filePath);
180
+ String mimeType = call.getString("mimeType", getMimeType(localFilePath));
181
+ filesToUpload.add(new Uploader.UploadFile(localFilePath, fileField, mimeType));
182
+ }
148
183
 
149
184
  String id = implementation.startUpload(
150
- localFilePath,
185
+ filesToUpload,
151
186
  serverUrl,
152
187
  headers,
153
188
  parameters,
154
189
  httpMethod,
155
190
  notificationTitle,
156
191
  maxRetries,
157
- mimeType,
158
- uploadType,
159
- fileField
192
+ uploadType
160
193
  );
161
194
  JSObject result = new JSObject();
162
195
  result.put("id", id);
package/dist/docs.json CHANGED
@@ -41,6 +41,10 @@
41
41
  {
42
42
  "name": "example",
43
43
  "text": "```typescript\nconst { id } = await Uploader.startUpload({\n filePath: 'file:///path/to/file.jpg',\n serverUrl: 'https://example.com/upload',\n headers: {\n 'Authorization': 'Bearer token'\n },\n method: 'POST',\n uploadType: 'multipart',\n fileField: 'photo'\n});\nconsole.log('Upload started with ID:', id);\n```"
44
+ },
45
+ {
46
+ "name": "example",
47
+ "text": "```typescript\nconst { id } = await Uploader.startUpload({\n serverUrl: 'https://api.example.com/upload',\n method: 'POST',\n uploadType: 'multipart',\n files: [\n { filePath: 'file:///...photo1.jpg', fieldName: 'images[]', mimeType: 'image/jpeg' },\n { filePath: 'file:///...photo2.jpg', fieldName: 'images[]', mimeType: 'image/jpeg' },\n ],\n parameters: { albumId: '7' },\n headers: { Authorization: 'Bearer token' },\n});\nconsole.log('Upload started with ID:', id);\n```"
44
48
  }
45
49
  ],
46
50
  "docs": "Start uploading a file to a server.\n\nThe upload will continue in the background even if the app is closed or backgrounded.\nListen to upload events to track progress, completion, or failure.",
@@ -165,13 +169,8 @@
165
169
  {
166
170
  "name": "uploadOption",
167
171
  "slug": "uploadoption",
168
- "docs": "Configuration options for uploading a file.",
169
- "tags": [
170
- {
171
- "text": "0.0.1",
172
- "name": "since"
173
- }
174
- ],
172
+ "docs": "",
173
+ "tags": [],
175
174
  "methods": [],
176
175
  "properties": [
177
176
  {
@@ -182,9 +181,23 @@
182
181
  "name": "since"
183
182
  }
184
183
  ],
185
- "docs": "The local file path of the file to upload.\nCan be a file:// URL or an absolute path.",
184
+ "docs": "The local file path of the file to upload.\nCan be a file:// URL or an absolute path.\n\nIf you need to upload multiple files in a single multipart request, use `files`.",
186
185
  "complexTypes": [],
187
- "type": "string"
186
+ "type": "string | undefined"
187
+ },
188
+ {
189
+ "name": "files",
190
+ "tags": [
191
+ {
192
+ "text": "0.0.3",
193
+ "name": "since"
194
+ }
195
+ ],
196
+ "docs": "Multiple files to upload in a single request.\n\nWhen provided, uploads are sent as `multipart/form-data` with one part per file.\nUse `fieldName` to control each part name (e.g. `images[]`).\n\nNote: `PUT` uploads (e.g. presigned S3 URLs) only support a single file.",
197
+ "complexTypes": [
198
+ "UploadFileOption"
199
+ ],
200
+ "type": "UploadFileOption[] | undefined"
188
201
  },
189
202
  {
190
203
  "name": "serverUrl",
@@ -294,7 +307,7 @@
294
307
  "name": "uploadType",
295
308
  "tags": [
296
309
  {
297
- "text": "'binary'",
310
+ "text": "'binary' when `method` is `'PUT'`, otherwise `'multipart'`",
298
311
  "name": "default"
299
312
  },
300
313
  {
@@ -318,7 +331,57 @@
318
331
  "name": "since"
319
332
  }
320
333
  ],
321
- "docs": "The form field name for the file when using multipart upload type.\nOnly used when uploadType is 'multipart'.",
334
+ "docs": "The form field name for the file when using multipart upload type.\nOnly used when uploadType is 'multipart'.\n\nFor multi-file uploads via `files`, this is used as the default field name\nwhen a file entry does not specify `fieldName`.",
335
+ "complexTypes": [],
336
+ "type": "string | undefined"
337
+ }
338
+ ]
339
+ },
340
+ {
341
+ "name": "UploadFileOption",
342
+ "slug": "uploadfileoption",
343
+ "docs": "Configuration options for uploading a file.",
344
+ "tags": [
345
+ {
346
+ "text": "0.0.1",
347
+ "name": "since"
348
+ }
349
+ ],
350
+ "methods": [],
351
+ "properties": [
352
+ {
353
+ "name": "filePath",
354
+ "tags": [
355
+ {
356
+ "text": "0.0.3",
357
+ "name": "since"
358
+ }
359
+ ],
360
+ "docs": "The local file path of the file to upload.\nCan be a file:// URL or an absolute path.",
361
+ "complexTypes": [],
362
+ "type": "string"
363
+ },
364
+ {
365
+ "name": "fieldName",
366
+ "tags": [
367
+ {
368
+ "text": "0.0.3",
369
+ "name": "since"
370
+ }
371
+ ],
372
+ "docs": "The form field name for the file part when using multipart upload.\n\nIf omitted, `uploadOption.fileField` is used (defaults to `'file'`).",
373
+ "complexTypes": [],
374
+ "type": "string | undefined"
375
+ },
376
+ {
377
+ "name": "mimeType",
378
+ "tags": [
379
+ {
380
+ "text": "0.0.3",
381
+ "name": "since"
382
+ }
383
+ ],
384
+ "docs": "The MIME type of this file.\nIf not specified, the plugin will attempt to determine it automatically.",
322
385
  "complexTypes": [],
323
386
  "type": "string | undefined"
324
387
  }
@@ -4,14 +4,51 @@ import type { PluginListenerHandle } from '@capacitor/core';
4
4
  *
5
5
  * @since 0.0.1
6
6
  */
7
+ export interface UploadFileOption {
8
+ /**
9
+ * The local file path of the file to upload.
10
+ * Can be a file:// URL or an absolute path.
11
+ *
12
+ * @since 0.0.3
13
+ */
14
+ filePath: string;
15
+ /**
16
+ * The form field name for the file part when using multipart upload.
17
+ *
18
+ * If omitted, `uploadOption.fileField` is used (defaults to `'file'`).
19
+ *
20
+ * @since 0.0.3
21
+ */
22
+ fieldName?: string;
23
+ /**
24
+ * The MIME type of this file.
25
+ * If not specified, the plugin will attempt to determine it automatically.
26
+ *
27
+ * @since 0.0.3
28
+ */
29
+ mimeType?: string;
30
+ }
7
31
  export interface uploadOption {
8
32
  /**
9
33
  * The local file path of the file to upload.
10
34
  * Can be a file:// URL or an absolute path.
11
35
  *
36
+ * If you need to upload multiple files in a single multipart request, use `files`.
37
+ *
12
38
  * @since 0.0.1
13
39
  */
14
- filePath: string;
40
+ filePath?: string;
41
+ /**
42
+ * Multiple files to upload in a single request.
43
+ *
44
+ * When provided, uploads are sent as `multipart/form-data` with one part per file.
45
+ * Use `fieldName` to control each part name (e.g. `images[]`).
46
+ *
47
+ * Note: `PUT` uploads (e.g. presigned S3 URLs) only support a single file.
48
+ *
49
+ * @since 0.0.3
50
+ */
51
+ files?: UploadFileOption[];
15
52
  /**
16
53
  * The server URL endpoint where the file should be uploaded.
17
54
  *
@@ -78,7 +115,7 @@ export interface uploadOption {
78
115
  * - 'binary': Uploads the file as raw binary data in the request body
79
116
  * - 'multipart': Uploads the file as multipart/form-data
80
117
  *
81
- * @default 'binary'
118
+ * @default 'binary' when `method` is `'PUT'`, otherwise `'multipart'`
82
119
  * @since 0.0.2
83
120
  */
84
121
  uploadType?: 'binary' | 'multipart';
@@ -86,6 +123,9 @@ export interface uploadOption {
86
123
  * The form field name for the file when using multipart upload type.
87
124
  * Only used when uploadType is 'multipart'.
88
125
  *
126
+ * For multi-file uploads via `files`, this is used as the default field name
127
+ * when a file entry does not specify `fieldName`.
128
+ *
89
129
  * @default 'file'
90
130
  * @since 0.0.2
91
131
  */
@@ -171,6 +211,22 @@ export interface UploaderPlugin {
171
211
  * });
172
212
  * console.log('Upload started with ID:', id);
173
213
  * ```
214
+ *
215
+ * @example
216
+ * ```typescript
217
+ * const { id } = await Uploader.startUpload({
218
+ * serverUrl: 'https://api.example.com/upload',
219
+ * method: 'POST',
220
+ * uploadType: 'multipart',
221
+ * files: [
222
+ * { filePath: 'file:///...photo1.jpg', fieldName: 'images[]', mimeType: 'image/jpeg' },
223
+ * { filePath: 'file:///...photo2.jpg', fieldName: 'images[]', mimeType: 'image/jpeg' },
224
+ * ],
225
+ * parameters: { albumId: '7' },
226
+ * headers: { Authorization: 'Bearer token' },
227
+ * });
228
+ * console.log('Upload started with ID:', id);
229
+ * ```
174
230
  */
175
231
  startUpload(options: uploadOption): Promise<{
176
232
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n/**\n * Configuration options for uploading a file.\n *\n * @since 0.0.1\n */\nexport interface uploadOption {\n /**\n * The local file path of the file to upload.\n * Can be a file:// URL or an absolute path.\n *\n * @since 0.0.1\n */\n filePath: string;\n\n /**\n * The server URL endpoint where the file should be uploaded.\n *\n * @since 0.0.1\n */\n serverUrl: string;\n\n /**\n * The title of the upload notification shown to the user.\n * Android only.\n *\n * @default 'Uploading'\n * @since 0.0.1\n */\n notificationTitle?: string;\n\n /**\n * HTTP headers to send with the upload request.\n * Useful for authentication tokens, content types, etc.\n *\n * @since 0.0.1\n * @example\n * ```typescript\n * headers: {\n * 'Authorization': 'Bearer token123',\n * 'X-Custom-Header': 'value'\n * }\n * ```\n */\n headers: {\n [key: string]: string;\n };\n\n /**\n * The HTTP method to use for the upload request.\n *\n * @default 'POST'\n * @since 0.0.1\n */\n method?: 'PUT' | 'POST';\n\n /**\n * The MIME type of the file being uploaded.\n * If not specified, the plugin will attempt to determine it automatically.\n *\n * @since 0.0.1\n * @example 'image/jpeg', 'application/pdf', 'video/mp4'\n */\n mimeType?: string;\n\n /**\n * Additional form parameters to send with the upload request.\n * These will be included as form data in multipart uploads.\n *\n * @since 0.0.1\n */\n parameters?: { [key: string]: string };\n\n /**\n * The maximum number of times to retry the upload if it fails.\n *\n * @since 0.0.1\n * @default 0\n */\n maxRetries?: number;\n\n /**\n * The type of upload to perform.\n * - 'binary': Uploads the file as raw binary data in the request body\n * - 'multipart': Uploads the file as multipart/form-data\n *\n * @default 'binary'\n * @since 0.0.2\n */\n uploadType?: 'binary' | 'multipart';\n\n /**\n * The form field name for the file when using multipart upload type.\n * Only used when uploadType is 'multipart'.\n *\n * @default 'file'\n * @since 0.0.2\n */\n fileField?: string;\n}\n\n/**\n * Event emitted during the upload lifecycle.\n *\n * @since 0.0.1\n */\nexport interface UploadEvent {\n /**\n * The current status of the upload.\n * - 'uploading': Upload is in progress\n * - 'completed': Upload finished successfully\n * - 'failed': Upload encountered an error\n *\n * @since 0.0.1\n */\n name: 'uploading' | 'completed' | 'failed';\n\n /**\n * Additional data about the upload event.\n *\n * @since 0.0.1\n */\n payload: {\n /**\n * Upload progress percentage from 0 to 100.\n * Only present during 'uploading' events.\n *\n * @since 0.0.1\n */\n percent?: number;\n\n /**\n * Error message if the upload failed.\n * Only present during 'failed' events.\n *\n * @since 0.0.1\n */\n error?: string;\n\n /**\n * HTTP status code returned by the server.\n * Present during 'completed' and 'failed' events.\n *\n * @since 0.0.1\n */\n statusCode?: number;\n };\n\n /**\n * Unique identifier for this upload task.\n *\n * @since 0.0.1\n */\n id: string;\n}\n\n/**\n * Capacitor Uploader Plugin for uploading files with background support and progress tracking.\n *\n * @since 0.0.1\n */\nexport interface UploaderPlugin {\n /**\n * Start uploading a file to a server.\n *\n * The upload will continue in the background even if the app is closed or backgrounded.\n * Listen to upload events to track progress, completion, or failure.\n *\n * @param options - Configuration for the upload\n * @returns Promise that resolves with the upload ID\n * @throws Error if the upload fails to start\n * @since 0.0.1\n * @example\n * ```typescript\n * const { id } = await Uploader.startUpload({\n * filePath: 'file:///path/to/file.jpg',\n * serverUrl: 'https://example.com/upload',\n * headers: {\n * 'Authorization': 'Bearer token'\n * },\n * method: 'POST',\n * uploadType: 'multipart',\n * fileField: 'photo'\n * });\n * console.log('Upload started with ID:', id);\n * ```\n */\n startUpload(options: uploadOption): Promise<{ id: string }>;\n\n /**\n * Cancel and remove an ongoing upload.\n *\n * This will stop the upload if it's in progress and clean up resources.\n *\n * @param options - Object containing the upload ID to remove\n * @returns Promise that resolves when the upload is removed\n * @throws Error if the upload ID is not found\n * @since 0.0.1\n * @example\n * ```typescript\n * await Uploader.removeUpload({ id: 'upload-123' });\n * ```\n */\n removeUpload(options: { id: string }): Promise<void>;\n\n /**\n * Listen for upload progress and status events.\n *\n * Events are fired for:\n * - Upload progress updates (with percent)\n * - Upload completion (with statusCode)\n * - Upload failure (with error and statusCode)\n *\n * @param eventName - Must be 'events'\n * @param listenerFunc - Callback function to handle upload events\n * @returns Promise that resolves with a listener handle for removal\n * @since 0.0.1\n * @example\n * ```typescript\n * const listener = await Uploader.addListener('events', (event) => {\n * if (event.name === 'uploading') {\n * console.log(`Upload ${event.id}: ${event.payload.percent}%`);\n * } else if (event.name === 'completed') {\n * console.log(`Upload ${event.id} completed`);\n * } else if (event.name === 'failed') {\n * console.error(`Upload ${event.id} failed:`, event.payload.error);\n * }\n * });\n *\n * // Remove listener when done\n * await listener.remove();\n * ```\n */\n addListener(eventName: 'events', listenerFunc: (state: UploadEvent) => void): Promise<PluginListenerHandle>;\n\n /**\n * Get the native Capacitor plugin version.\n *\n * @returns Promise that resolves with the plugin version\n * @throws Error if getting the version fails\n * @since 0.0.1\n * @example\n * ```typescript\n * const { version } = await Uploader.getPluginVersion();\n * console.log('Plugin version:', version);\n * ```\n */\n getPluginVersion(): Promise<{ version: string }>;\n}\n"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n/**\n * Configuration options for uploading a file.\n *\n * @since 0.0.1\n */\nexport interface UploadFileOption {\n /**\n * The local file path of the file to upload.\n * Can be a file:// URL or an absolute path.\n *\n * @since 0.0.3\n */\n filePath: string;\n\n /**\n * The form field name for the file part when using multipart upload.\n *\n * If omitted, `uploadOption.fileField` is used (defaults to `'file'`).\n *\n * @since 0.0.3\n */\n fieldName?: string;\n\n /**\n * The MIME type of this file.\n * If not specified, the plugin will attempt to determine it automatically.\n *\n * @since 0.0.3\n */\n mimeType?: string;\n}\n\nexport interface uploadOption {\n /**\n * The local file path of the file to upload.\n * Can be a file:// URL or an absolute path.\n *\n * If you need to upload multiple files in a single multipart request, use `files`.\n *\n * @since 0.0.1\n */\n filePath?: string;\n\n /**\n * Multiple files to upload in a single request.\n *\n * When provided, uploads are sent as `multipart/form-data` with one part per file.\n * Use `fieldName` to control each part name (e.g. `images[]`).\n *\n * Note: `PUT` uploads (e.g. presigned S3 URLs) only support a single file.\n *\n * @since 0.0.3\n */\n files?: UploadFileOption[];\n\n /**\n * The server URL endpoint where the file should be uploaded.\n *\n * @since 0.0.1\n */\n serverUrl: string;\n\n /**\n * The title of the upload notification shown to the user.\n * Android only.\n *\n * @default 'Uploading'\n * @since 0.0.1\n */\n notificationTitle?: string;\n\n /**\n * HTTP headers to send with the upload request.\n * Useful for authentication tokens, content types, etc.\n *\n * @since 0.0.1\n * @example\n * ```typescript\n * headers: {\n * 'Authorization': 'Bearer token123',\n * 'X-Custom-Header': 'value'\n * }\n * ```\n */\n headers: {\n [key: string]: string;\n };\n\n /**\n * The HTTP method to use for the upload request.\n *\n * @default 'POST'\n * @since 0.0.1\n */\n method?: 'PUT' | 'POST';\n\n /**\n * The MIME type of the file being uploaded.\n * If not specified, the plugin will attempt to determine it automatically.\n *\n * @since 0.0.1\n * @example 'image/jpeg', 'application/pdf', 'video/mp4'\n */\n mimeType?: string;\n\n /**\n * Additional form parameters to send with the upload request.\n * These will be included as form data in multipart uploads.\n *\n * @since 0.0.1\n */\n parameters?: { [key: string]: string };\n\n /**\n * The maximum number of times to retry the upload if it fails.\n *\n * @since 0.0.1\n * @default 0\n */\n maxRetries?: number;\n\n /**\n * The type of upload to perform.\n * - 'binary': Uploads the file as raw binary data in the request body\n * - 'multipart': Uploads the file as multipart/form-data\n *\n * @default 'binary' when `method` is `'PUT'`, otherwise `'multipart'`\n * @since 0.0.2\n */\n uploadType?: 'binary' | 'multipart';\n\n /**\n * The form field name for the file when using multipart upload type.\n * Only used when uploadType is 'multipart'.\n *\n * For multi-file uploads via `files`, this is used as the default field name\n * when a file entry does not specify `fieldName`.\n *\n * @default 'file'\n * @since 0.0.2\n */\n fileField?: string;\n}\n\n/**\n * Event emitted during the upload lifecycle.\n *\n * @since 0.0.1\n */\nexport interface UploadEvent {\n /**\n * The current status of the upload.\n * - 'uploading': Upload is in progress\n * - 'completed': Upload finished successfully\n * - 'failed': Upload encountered an error\n *\n * @since 0.0.1\n */\n name: 'uploading' | 'completed' | 'failed';\n\n /**\n * Additional data about the upload event.\n *\n * @since 0.0.1\n */\n payload: {\n /**\n * Upload progress percentage from 0 to 100.\n * Only present during 'uploading' events.\n *\n * @since 0.0.1\n */\n percent?: number;\n\n /**\n * Error message if the upload failed.\n * Only present during 'failed' events.\n *\n * @since 0.0.1\n */\n error?: string;\n\n /**\n * HTTP status code returned by the server.\n * Present during 'completed' and 'failed' events.\n *\n * @since 0.0.1\n */\n statusCode?: number;\n };\n\n /**\n * Unique identifier for this upload task.\n *\n * @since 0.0.1\n */\n id: string;\n}\n\n/**\n * Capacitor Uploader Plugin for uploading files with background support and progress tracking.\n *\n * @since 0.0.1\n */\nexport interface UploaderPlugin {\n /**\n * Start uploading a file to a server.\n *\n * The upload will continue in the background even if the app is closed or backgrounded.\n * Listen to upload events to track progress, completion, or failure.\n *\n * @param options - Configuration for the upload\n * @returns Promise that resolves with the upload ID\n * @throws Error if the upload fails to start\n * @since 0.0.1\n * @example\n * ```typescript\n * const { id } = await Uploader.startUpload({\n * filePath: 'file:///path/to/file.jpg',\n * serverUrl: 'https://example.com/upload',\n * headers: {\n * 'Authorization': 'Bearer token'\n * },\n * method: 'POST',\n * uploadType: 'multipart',\n * fileField: 'photo'\n * });\n * console.log('Upload started with ID:', id);\n * ```\n *\n * @example\n * ```typescript\n * const { id } = await Uploader.startUpload({\n * serverUrl: 'https://api.example.com/upload',\n * method: 'POST',\n * uploadType: 'multipart',\n * files: [\n * { filePath: 'file:///...photo1.jpg', fieldName: 'images[]', mimeType: 'image/jpeg' },\n * { filePath: 'file:///...photo2.jpg', fieldName: 'images[]', mimeType: 'image/jpeg' },\n * ],\n * parameters: { albumId: '7' },\n * headers: { Authorization: 'Bearer token' },\n * });\n * console.log('Upload started with ID:', id);\n * ```\n */\n startUpload(options: uploadOption): Promise<{ id: string }>;\n\n /**\n * Cancel and remove an ongoing upload.\n *\n * This will stop the upload if it's in progress and clean up resources.\n *\n * @param options - Object containing the upload ID to remove\n * @returns Promise that resolves when the upload is removed\n * @throws Error if the upload ID is not found\n * @since 0.0.1\n * @example\n * ```typescript\n * await Uploader.removeUpload({ id: 'upload-123' });\n * ```\n */\n removeUpload(options: { id: string }): Promise<void>;\n\n /**\n * Listen for upload progress and status events.\n *\n * Events are fired for:\n * - Upload progress updates (with percent)\n * - Upload completion (with statusCode)\n * - Upload failure (with error and statusCode)\n *\n * @param eventName - Must be 'events'\n * @param listenerFunc - Callback function to handle upload events\n * @returns Promise that resolves with a listener handle for removal\n * @since 0.0.1\n * @example\n * ```typescript\n * const listener = await Uploader.addListener('events', (event) => {\n * if (event.name === 'uploading') {\n * console.log(`Upload ${event.id}: ${event.payload.percent}%`);\n * } else if (event.name === 'completed') {\n * console.log(`Upload ${event.id} completed`);\n * } else if (event.name === 'failed') {\n * console.error(`Upload ${event.id} failed:`, event.payload.error);\n * }\n * });\n *\n * // Remove listener when done\n * await listener.remove();\n * ```\n */\n addListener(eventName: 'events', listenerFunc: (state: UploadEvent) => void): Promise<PluginListenerHandle>;\n\n /**\n * Get the native Capacitor plugin version.\n *\n * @returns Promise that resolves with the plugin version\n * @throws Error if getting the version fails\n * @since 0.0.1\n * @example\n * ```typescript\n * const { version } = await Uploader.getPluginVersion();\n * console.log('Plugin version:', version);\n * ```\n */\n getPluginVersion(): Promise<{ version: string }>;\n}\n"]}
package/dist/esm/web.d.ts CHANGED
@@ -9,6 +9,7 @@ export declare class UploaderWeb extends WebPlugin implements UploaderPlugin {
9
9
  id: string;
10
10
  }): Promise<void>;
11
11
  private doUpload;
12
+ private normalizeFiles;
12
13
  private getFileFromPath;
13
14
  private getFileFromIndexedDB;
14
15
  private getFileFromSystem;
package/dist/esm/web.js CHANGED
@@ -29,23 +29,44 @@ export class UploaderWeb extends WebPlugin {
29
29
  }
30
30
  }
31
31
  async doUpload(id, options) {
32
- const { filePath, serverUrl, headers = {}, method = 'POST', parameters = {} } = options;
32
+ var _a, _b, _c;
33
+ const { serverUrl, headers = {}, method = 'POST', parameters = {} } = options;
33
34
  const upload = this.uploads.get(id);
34
35
  if (!upload)
35
36
  return;
36
37
  try {
37
- const file = await this.getFileFromPath(filePath);
38
- if (!file)
39
- throw new Error('File not found');
40
- const formData = new FormData();
41
- formData.append('file', file);
42
- for (const [key, value] of Object.entries(parameters)) {
43
- formData.append(key, value);
38
+ const files = this.normalizeFiles(options);
39
+ if (files.length === 0)
40
+ throw new Error('Missing required parameter: filePath or files');
41
+ const resolvedMethod = method.toUpperCase();
42
+ const uploadType = (_a = options.uploadType) !== null && _a !== void 0 ? _a : (resolvedMethod === 'PUT' ? 'binary' : 'multipart');
43
+ let body;
44
+ if (resolvedMethod === 'PUT' || uploadType === 'binary') {
45
+ if (files.length !== 1)
46
+ throw new Error('Binary uploads only support a single file');
47
+ const file = await this.getFileFromPath(files[0].filePath);
48
+ if (!file)
49
+ throw new Error('File not found');
50
+ body = file;
51
+ }
52
+ else {
53
+ const formData = new FormData();
54
+ for (const fileOption of files) {
55
+ const file = await this.getFileFromPath(fileOption.filePath);
56
+ if (!file)
57
+ throw new Error('File not found');
58
+ const fieldName = (_c = (_b = fileOption.fieldName) !== null && _b !== void 0 ? _b : options.fileField) !== null && _c !== void 0 ? _c : 'file';
59
+ formData.append(fieldName, file);
60
+ }
61
+ for (const [key, value] of Object.entries(parameters)) {
62
+ formData.append(key, value);
63
+ }
64
+ body = formData;
44
65
  }
45
66
  const response = await fetch(serverUrl, {
46
- method,
67
+ method: resolvedMethod,
47
68
  headers,
48
- body: method === 'PUT' ? file : formData,
69
+ body,
49
70
  signal: upload.controller.signal,
50
71
  });
51
72
  if (!response.ok)
@@ -75,6 +96,20 @@ export class UploaderWeb extends WebPlugin {
75
96
  }
76
97
  }
77
98
  }
99
+ normalizeFiles(options) {
100
+ if (options.files && options.files.length > 0)
101
+ return options.files;
102
+ if (options.filePath) {
103
+ return [
104
+ {
105
+ filePath: options.filePath,
106
+ fieldName: options.fileField,
107
+ mimeType: options.mimeType,
108
+ },
109
+ ];
110
+ }
111
+ return [];
112
+ }
78
113
  async getFileFromPath(filePath) {
79
114
  // Check if the path is an IndexedDB path
80
115
  if (PathHelper.isIndexedDBPath(filePath)) {
@@ -1 +1 @@
1
- {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"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","sourcesContent":["import { WebPlugin } from '@capacitor/core';\nimport { openDB } from 'idb';\n\nimport { PathHelper } from './PathHelper';\nimport type { UploaderPlugin, uploadOption } from './definitions';\n\nexport class UploaderWeb extends WebPlugin implements UploaderPlugin {\n private uploads: Map<string, { controller: AbortController; retries: number }> = new Map();\n\n async startUpload(options: uploadOption): Promise<{ id: string }> {\n console.log('startUpload', options);\n\n const id = Math.random().toString(36).substring(2, 15);\n const controller = new AbortController();\n const maxRetries = options.maxRetries || 3;\n this.uploads.set(id, { controller, retries: maxRetries });\n\n this.doUpload(id, options);\n\n return { id };\n }\n\n async removeUpload(options: { id: string }): Promise<void> {\n console.log('removeUpload', options);\n const upload = this.uploads.get(options.id);\n if (upload) {\n upload.controller.abort();\n this.uploads.delete(options.id);\n this.notifyListeners('events', {\n name: 'cancelled',\n id: options.id,\n payload: {},\n });\n }\n }\n\n private async doUpload(id: string, options: uploadOption) {\n const { filePath, serverUrl, headers = {}, method = 'POST', parameters = {} } = options;\n const upload = this.uploads.get(id);\n\n if (!upload) return;\n\n try {\n const file = await this.getFileFromPath(filePath);\n if (!file) throw new Error('File not found');\n\n const formData = new FormData();\n formData.append('file', file);\n\n for (const [key, value] of Object.entries(parameters)) {\n formData.append(key, value);\n }\n\n const response = await fetch(serverUrl, {\n method,\n headers,\n body: method === 'PUT' ? file : formData,\n signal: upload.controller.signal,\n });\n\n if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n\n this.notifyListeners('events', {\n name: 'completed',\n id,\n payload: { statusCode: response.status },\n });\n\n this.uploads.delete(id);\n } catch (error) {\n if ((error as Error).name === 'AbortError') return;\n\n if (upload.retries > 0) {\n upload.retries--;\n console.log(`Retrying upload (retries left: ${upload.retries})`);\n setTimeout(() => this.doUpload(id, options), 1000);\n } else {\n this.notifyListeners('events', {\n name: 'failed',\n id,\n payload: { error: (error as Error).message },\n });\n this.uploads.delete(id);\n }\n }\n }\n\n private async getFileFromPath(filePath: string): Promise<File | null> {\n // Check if the path is an IndexedDB path\n if (PathHelper.isIndexedDBPath(filePath)) {\n return this.getFileFromIndexedDB(filePath);\n }\n\n // Otherwise, treat it as a file path from the system\n return this.getFileFromSystem(filePath);\n }\n\n // Retrieve the file from IndexedDB\n private async getFileFromIndexedDB(filePath: string): Promise<File | null> {\n // Parse the path to get the database, store name, and key\n const { database, storeName, key } = PathHelper.parseIndexedDBPath(filePath);\n\n try {\n // Open the IndexedDB database and access the object store\n const db = await openDB(database, 1, {\n upgrade(db) {\n if (!db.objectStoreNames.contains(storeName)) {\n db.createObjectStore(storeName);\n }\n },\n });\n\n // Get the blob from the store\n const blob = await db.get(storeName, key);\n if (!blob) {\n console.error(`File with key \"${key}\" not found in store \"${storeName}\" in database \"${database}\"`);\n return null;\n }\n\n // Convert the Blob to a File object\n return new File([blob], key, { type: blob.type });\n } catch (error) {\n console.error('Error retrieving file from IndexedDB:', error);\n return null;\n }\n }\n\n // Retrieve the file from the system (local file)\n private async getFileFromSystem(filePath: string): Promise<File | null> {\n try {\n // This is a simplified version. In a real-world scenario,\n // you might need to handle different types of paths or use a file system API.\n const response = await fetch(filePath);\n const blob = await response.blob();\n return new File([blob], filePath.split('/').pop() || 'file', {\n type: blob.type,\n });\n } catch (error) {\n console.error('Error getting file from system:', error);\n return null;\n }\n }\n\n async getPluginVersion(): Promise<{ version: string }> {\n return { version: 'web' };\n }\n}\n"]}
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"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","sourcesContent":["import { WebPlugin } from '@capacitor/core';\nimport { openDB } from 'idb';\n\nimport { PathHelper } from './PathHelper';\nimport type { UploadFileOption, UploaderPlugin, uploadOption } from './definitions';\n\nexport class UploaderWeb extends WebPlugin implements UploaderPlugin {\n private uploads: Map<string, { controller: AbortController; retries: number }> = new Map();\n\n async startUpload(options: uploadOption): Promise<{ id: string }> {\n console.log('startUpload', options);\n\n const id = Math.random().toString(36).substring(2, 15);\n const controller = new AbortController();\n const maxRetries = options.maxRetries || 3;\n this.uploads.set(id, { controller, retries: maxRetries });\n\n this.doUpload(id, options);\n\n return { id };\n }\n\n async removeUpload(options: { id: string }): Promise<void> {\n console.log('removeUpload', options);\n const upload = this.uploads.get(options.id);\n if (upload) {\n upload.controller.abort();\n this.uploads.delete(options.id);\n this.notifyListeners('events', {\n name: 'cancelled',\n id: options.id,\n payload: {},\n });\n }\n }\n\n private async doUpload(id: string, options: uploadOption) {\n const { serverUrl, headers = {}, method = 'POST', parameters = {} } = options;\n const upload = this.uploads.get(id);\n\n if (!upload) return;\n\n try {\n const files = this.normalizeFiles(options);\n if (files.length === 0) throw new Error('Missing required parameter: filePath or files');\n\n const resolvedMethod = method.toUpperCase();\n const uploadType = options.uploadType ?? (resolvedMethod === 'PUT' ? 'binary' : 'multipart');\n\n let body: BodyInit;\n if (resolvedMethod === 'PUT' || uploadType === 'binary') {\n if (files.length !== 1) throw new Error('Binary uploads only support a single file');\n const file = await this.getFileFromPath(files[0].filePath);\n if (!file) throw new Error('File not found');\n body = file;\n } else {\n const formData = new FormData();\n\n for (const fileOption of files) {\n const file = await this.getFileFromPath(fileOption.filePath);\n if (!file) throw new Error('File not found');\n const fieldName = fileOption.fieldName ?? options.fileField ?? 'file';\n formData.append(fieldName, file);\n }\n\n for (const [key, value] of Object.entries(parameters)) {\n formData.append(key, value);\n }\n\n body = formData;\n }\n\n const response = await fetch(serverUrl, {\n method: resolvedMethod,\n headers,\n body,\n signal: upload.controller.signal,\n });\n\n if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n\n this.notifyListeners('events', {\n name: 'completed',\n id,\n payload: { statusCode: response.status },\n });\n\n this.uploads.delete(id);\n } catch (error) {\n if ((error as Error).name === 'AbortError') return;\n\n if (upload.retries > 0) {\n upload.retries--;\n console.log(`Retrying upload (retries left: ${upload.retries})`);\n setTimeout(() => this.doUpload(id, options), 1000);\n } else {\n this.notifyListeners('events', {\n name: 'failed',\n id,\n payload: { error: (error as Error).message },\n });\n this.uploads.delete(id);\n }\n }\n }\n\n private normalizeFiles(options: uploadOption): UploadFileOption[] {\n if (options.files && options.files.length > 0) return options.files;\n if (options.filePath) {\n return [\n {\n filePath: options.filePath,\n fieldName: options.fileField,\n mimeType: options.mimeType,\n },\n ];\n }\n return [];\n }\n\n private async getFileFromPath(filePath: string): Promise<File | null> {\n // Check if the path is an IndexedDB path\n if (PathHelper.isIndexedDBPath(filePath)) {\n return this.getFileFromIndexedDB(filePath);\n }\n\n // Otherwise, treat it as a file path from the system\n return this.getFileFromSystem(filePath);\n }\n\n // Retrieve the file from IndexedDB\n private async getFileFromIndexedDB(filePath: string): Promise<File | null> {\n // Parse the path to get the database, store name, and key\n const { database, storeName, key } = PathHelper.parseIndexedDBPath(filePath);\n\n try {\n // Open the IndexedDB database and access the object store\n const db = await openDB(database, 1, {\n upgrade(db) {\n if (!db.objectStoreNames.contains(storeName)) {\n db.createObjectStore(storeName);\n }\n },\n });\n\n // Get the blob from the store\n const blob = await db.get(storeName, key);\n if (!blob) {\n console.error(`File with key \"${key}\" not found in store \"${storeName}\" in database \"${database}\"`);\n return null;\n }\n\n // Convert the Blob to a File object\n return new File([blob], key, { type: blob.type });\n } catch (error) {\n console.error('Error retrieving file from IndexedDB:', error);\n return null;\n }\n }\n\n // Retrieve the file from the system (local file)\n private async getFileFromSystem(filePath: string): Promise<File | null> {\n try {\n // This is a simplified version. In a real-world scenario,\n // you might need to handle different types of paths or use a file system API.\n const response = await fetch(filePath);\n const blob = await response.blob();\n return new File([blob], filePath.split('/').pop() || 'file', {\n type: blob.type,\n });\n } catch (error) {\n console.error('Error getting file from system:', error);\n return null;\n }\n }\n\n async getPluginVersion(): Promise<{ version: string }> {\n return { version: 'web' };\n }\n}\n"]}
@@ -56,23 +56,44 @@ class UploaderWeb extends core.WebPlugin {
56
56
  }
57
57
  }
58
58
  async doUpload(id, options) {
59
- const { filePath, serverUrl, headers = {}, method = 'POST', parameters = {} } = options;
59
+ var _a, _b, _c;
60
+ const { serverUrl, headers = {}, method = 'POST', parameters = {} } = options;
60
61
  const upload = this.uploads.get(id);
61
62
  if (!upload)
62
63
  return;
63
64
  try {
64
- const file = await this.getFileFromPath(filePath);
65
- if (!file)
66
- throw new Error('File not found');
67
- const formData = new FormData();
68
- formData.append('file', file);
69
- for (const [key, value] of Object.entries(parameters)) {
70
- formData.append(key, value);
65
+ const files = this.normalizeFiles(options);
66
+ if (files.length === 0)
67
+ throw new Error('Missing required parameter: filePath or files');
68
+ const resolvedMethod = method.toUpperCase();
69
+ const uploadType = (_a = options.uploadType) !== null && _a !== void 0 ? _a : (resolvedMethod === 'PUT' ? 'binary' : 'multipart');
70
+ let body;
71
+ if (resolvedMethod === 'PUT' || uploadType === 'binary') {
72
+ if (files.length !== 1)
73
+ throw new Error('Binary uploads only support a single file');
74
+ const file = await this.getFileFromPath(files[0].filePath);
75
+ if (!file)
76
+ throw new Error('File not found');
77
+ body = file;
78
+ }
79
+ else {
80
+ const formData = new FormData();
81
+ for (const fileOption of files) {
82
+ const file = await this.getFileFromPath(fileOption.filePath);
83
+ if (!file)
84
+ throw new Error('File not found');
85
+ const fieldName = (_c = (_b = fileOption.fieldName) !== null && _b !== void 0 ? _b : options.fileField) !== null && _c !== void 0 ? _c : 'file';
86
+ formData.append(fieldName, file);
87
+ }
88
+ for (const [key, value] of Object.entries(parameters)) {
89
+ formData.append(key, value);
90
+ }
91
+ body = formData;
71
92
  }
72
93
  const response = await fetch(serverUrl, {
73
- method,
94
+ method: resolvedMethod,
74
95
  headers,
75
- body: method === 'PUT' ? file : formData,
96
+ body,
76
97
  signal: upload.controller.signal,
77
98
  });
78
99
  if (!response.ok)
@@ -102,6 +123,20 @@ class UploaderWeb extends core.WebPlugin {
102
123
  }
103
124
  }
104
125
  }
126
+ normalizeFiles(options) {
127
+ if (options.files && options.files.length > 0)
128
+ return options.files;
129
+ if (options.filePath) {
130
+ return [
131
+ {
132
+ filePath: options.filePath,
133
+ fieldName: options.fileField,
134
+ mimeType: options.mimeType,
135
+ },
136
+ ];
137
+ }
138
+ return [];
139
+ }
105
140
  async getFileFromPath(filePath) {
106
141
  // Check if the path is an IndexedDB path
107
142
  if (PathHelper.isIndexedDBPath(filePath)) {
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/PathHelper.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst Uploader = registerPlugin('Uploader', {\n web: () => import('./web').then((m) => new m.UploaderWeb()),\n});\nexport * from './definitions';\nexport { Uploader };\n//# sourceMappingURL=index.js.map","export class PathHelper {\n // Check if the path follows the idb://[databaseName]/[storeName]/[key] format\n static isIndexedDBPath(path) {\n const regex = /^idb:\\/\\/([^/]+)\\/([^/]+)\\/(.+)$/;\n return regex.test(path);\n }\n // Parse the path to extract database, storeName, and key\n static parseIndexedDBPath(path) {\n const regex = /^idb:\\/\\/([^/]+)\\/([^/]+)\\/(.+)$/;\n const match = path.match(regex);\n if (!match) {\n throw new Error('Invalid IndexedDB path format');\n }\n return {\n database: match[1],\n storeName: match[2],\n key: match[3],\n };\n }\n}\n//# sourceMappingURL=PathHelper.js.map","import { WebPlugin } from '@capacitor/core';\nimport { openDB } from 'idb';\nimport { PathHelper } from './PathHelper';\nexport class UploaderWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.uploads = new Map();\n }\n async startUpload(options) {\n console.log('startUpload', options);\n const id = Math.random().toString(36).substring(2, 15);\n const controller = new AbortController();\n const maxRetries = options.maxRetries || 3;\n this.uploads.set(id, { controller, retries: maxRetries });\n this.doUpload(id, options);\n return { id };\n }\n async removeUpload(options) {\n console.log('removeUpload', options);\n const upload = this.uploads.get(options.id);\n if (upload) {\n upload.controller.abort();\n this.uploads.delete(options.id);\n this.notifyListeners('events', {\n name: 'cancelled',\n id: options.id,\n payload: {},\n });\n }\n }\n async doUpload(id, options) {\n const { filePath, serverUrl, headers = {}, method = 'POST', parameters = {} } = options;\n const upload = this.uploads.get(id);\n if (!upload)\n return;\n try {\n const file = await this.getFileFromPath(filePath);\n if (!file)\n throw new Error('File not found');\n const formData = new FormData();\n formData.append('file', file);\n for (const [key, value] of Object.entries(parameters)) {\n formData.append(key, value);\n }\n const response = await fetch(serverUrl, {\n method,\n headers,\n body: method === 'PUT' ? file : formData,\n signal: upload.controller.signal,\n });\n if (!response.ok)\n throw new Error(`HTTP error! status: ${response.status}`);\n this.notifyListeners('events', {\n name: 'completed',\n id,\n payload: { statusCode: response.status },\n });\n this.uploads.delete(id);\n }\n catch (error) {\n if (error.name === 'AbortError')\n return;\n if (upload.retries > 0) {\n upload.retries--;\n console.log(`Retrying upload (retries left: ${upload.retries})`);\n setTimeout(() => this.doUpload(id, options), 1000);\n }\n else {\n this.notifyListeners('events', {\n name: 'failed',\n id,\n payload: { error: error.message },\n });\n this.uploads.delete(id);\n }\n }\n }\n async getFileFromPath(filePath) {\n // Check if the path is an IndexedDB path\n if (PathHelper.isIndexedDBPath(filePath)) {\n return this.getFileFromIndexedDB(filePath);\n }\n // Otherwise, treat it as a file path from the system\n return this.getFileFromSystem(filePath);\n }\n // Retrieve the file from IndexedDB\n async getFileFromIndexedDB(filePath) {\n // Parse the path to get the database, store name, and key\n const { database, storeName, key } = PathHelper.parseIndexedDBPath(filePath);\n try {\n // Open the IndexedDB database and access the object store\n const db = await openDB(database, 1, {\n upgrade(db) {\n if (!db.objectStoreNames.contains(storeName)) {\n db.createObjectStore(storeName);\n }\n },\n });\n // Get the blob from the store\n const blob = await db.get(storeName, key);\n if (!blob) {\n console.error(`File with key \"${key}\" not found in store \"${storeName}\" in database \"${database}\"`);\n return null;\n }\n // Convert the Blob to a File object\n return new File([blob], key, { type: blob.type });\n }\n catch (error) {\n console.error('Error retrieving file from IndexedDB:', error);\n return null;\n }\n }\n // Retrieve the file from the system (local file)\n async getFileFromSystem(filePath) {\n try {\n // This is a simplified version. In a real-world scenario,\n // you might need to handle different types of paths or use a file system API.\n const response = await fetch(filePath);\n const blob = await response.blob();\n return new File([blob], filePath.split('/').pop() || 'file', {\n type: blob.type,\n });\n }\n catch (error) {\n console.error('Error getting file from system:', error);\n return null;\n }\n }\n async getPluginVersion() {\n return { version: 'web' };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin","openDB"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/PathHelper.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst Uploader = registerPlugin('Uploader', {\n web: () => import('./web').then((m) => new m.UploaderWeb()),\n});\nexport * from './definitions';\nexport { Uploader };\n//# sourceMappingURL=index.js.map","export class PathHelper {\n // Check if the path follows the idb://[databaseName]/[storeName]/[key] format\n static isIndexedDBPath(path) {\n const regex = /^idb:\\/\\/([^/]+)\\/([^/]+)\\/(.+)$/;\n return regex.test(path);\n }\n // Parse the path to extract database, storeName, and key\n static parseIndexedDBPath(path) {\n const regex = /^idb:\\/\\/([^/]+)\\/([^/]+)\\/(.+)$/;\n const match = path.match(regex);\n if (!match) {\n throw new Error('Invalid IndexedDB path format');\n }\n return {\n database: match[1],\n storeName: match[2],\n key: match[3],\n };\n }\n}\n//# sourceMappingURL=PathHelper.js.map","import { WebPlugin } from '@capacitor/core';\nimport { openDB } from 'idb';\nimport { PathHelper } from './PathHelper';\nexport class UploaderWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.uploads = new Map();\n }\n async startUpload(options) {\n console.log('startUpload', options);\n const id = Math.random().toString(36).substring(2, 15);\n const controller = new AbortController();\n const maxRetries = options.maxRetries || 3;\n this.uploads.set(id, { controller, retries: maxRetries });\n this.doUpload(id, options);\n return { id };\n }\n async removeUpload(options) {\n console.log('removeUpload', options);\n const upload = this.uploads.get(options.id);\n if (upload) {\n upload.controller.abort();\n this.uploads.delete(options.id);\n this.notifyListeners('events', {\n name: 'cancelled',\n id: options.id,\n payload: {},\n });\n }\n }\n async doUpload(id, options) {\n var _a, _b, _c;\n const { serverUrl, headers = {}, method = 'POST', parameters = {} } = options;\n const upload = this.uploads.get(id);\n if (!upload)\n return;\n try {\n const files = this.normalizeFiles(options);\n if (files.length === 0)\n throw new Error('Missing required parameter: filePath or files');\n const resolvedMethod = method.toUpperCase();\n const uploadType = (_a = options.uploadType) !== null && _a !== void 0 ? _a : (resolvedMethod === 'PUT' ? 'binary' : 'multipart');\n let body;\n if (resolvedMethod === 'PUT' || uploadType === 'binary') {\n if (files.length !== 1)\n throw new Error('Binary uploads only support a single file');\n const file = await this.getFileFromPath(files[0].filePath);\n if (!file)\n throw new Error('File not found');\n body = file;\n }\n else {\n const formData = new FormData();\n for (const fileOption of files) {\n const file = await this.getFileFromPath(fileOption.filePath);\n if (!file)\n throw new Error('File not found');\n const fieldName = (_c = (_b = fileOption.fieldName) !== null && _b !== void 0 ? _b : options.fileField) !== null && _c !== void 0 ? _c : 'file';\n formData.append(fieldName, file);\n }\n for (const [key, value] of Object.entries(parameters)) {\n formData.append(key, value);\n }\n body = formData;\n }\n const response = await fetch(serverUrl, {\n method: resolvedMethod,\n headers,\n body,\n signal: upload.controller.signal,\n });\n if (!response.ok)\n throw new Error(`HTTP error! status: ${response.status}`);\n this.notifyListeners('events', {\n name: 'completed',\n id,\n payload: { statusCode: response.status },\n });\n this.uploads.delete(id);\n }\n catch (error) {\n if (error.name === 'AbortError')\n return;\n if (upload.retries > 0) {\n upload.retries--;\n console.log(`Retrying upload (retries left: ${upload.retries})`);\n setTimeout(() => this.doUpload(id, options), 1000);\n }\n else {\n this.notifyListeners('events', {\n name: 'failed',\n id,\n payload: { error: error.message },\n });\n this.uploads.delete(id);\n }\n }\n }\n normalizeFiles(options) {\n if (options.files && options.files.length > 0)\n return options.files;\n if (options.filePath) {\n return [\n {\n filePath: options.filePath,\n fieldName: options.fileField,\n mimeType: options.mimeType,\n },\n ];\n }\n return [];\n }\n async getFileFromPath(filePath) {\n // Check if the path is an IndexedDB path\n if (PathHelper.isIndexedDBPath(filePath)) {\n return this.getFileFromIndexedDB(filePath);\n }\n // Otherwise, treat it as a file path from the system\n return this.getFileFromSystem(filePath);\n }\n // Retrieve the file from IndexedDB\n async getFileFromIndexedDB(filePath) {\n // Parse the path to get the database, store name, and key\n const { database, storeName, key } = PathHelper.parseIndexedDBPath(filePath);\n try {\n // Open the IndexedDB database and access the object store\n const db = await openDB(database, 1, {\n upgrade(db) {\n if (!db.objectStoreNames.contains(storeName)) {\n db.createObjectStore(storeName);\n }\n },\n });\n // Get the blob from the store\n const blob = await db.get(storeName, key);\n if (!blob) {\n console.error(`File with key \"${key}\" not found in store \"${storeName}\" in database \"${database}\"`);\n return null;\n }\n // Convert the Blob to a File object\n return new File([blob], key, { type: blob.type });\n }\n catch (error) {\n console.error('Error retrieving file from IndexedDB:', error);\n return null;\n }\n }\n // Retrieve the file from the system (local file)\n async getFileFromSystem(filePath) {\n try {\n // This is a simplified version. In a real-world scenario,\n // you might need to handle different types of paths or use a file system API.\n const response = await fetch(filePath);\n const blob = await response.blob();\n return new File([blob], filePath.split('/').pop() || 'file', {\n type: blob.type,\n });\n }\n catch (error) {\n console.error('Error getting file from system:', error);\n return null;\n }\n }\n async getPluginVersion() {\n return { version: 'web' };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin","openDB"],"mappings":"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"}
package/dist/plugin.js CHANGED
@@ -54,23 +54,44 @@ var capacitorCapacitorUpdater = (function (exports, core, idb) {
54
54
  }
55
55
  }
56
56
  async doUpload(id, options) {
57
- const { filePath, serverUrl, headers = {}, method = 'POST', parameters = {} } = options;
57
+ var _a, _b, _c;
58
+ const { serverUrl, headers = {}, method = 'POST', parameters = {} } = options;
58
59
  const upload = this.uploads.get(id);
59
60
  if (!upload)
60
61
  return;
61
62
  try {
62
- const file = await this.getFileFromPath(filePath);
63
- if (!file)
64
- throw new Error('File not found');
65
- const formData = new FormData();
66
- formData.append('file', file);
67
- for (const [key, value] of Object.entries(parameters)) {
68
- formData.append(key, value);
63
+ const files = this.normalizeFiles(options);
64
+ if (files.length === 0)
65
+ throw new Error('Missing required parameter: filePath or files');
66
+ const resolvedMethod = method.toUpperCase();
67
+ const uploadType = (_a = options.uploadType) !== null && _a !== void 0 ? _a : (resolvedMethod === 'PUT' ? 'binary' : 'multipart');
68
+ let body;
69
+ if (resolvedMethod === 'PUT' || uploadType === 'binary') {
70
+ if (files.length !== 1)
71
+ throw new Error('Binary uploads only support a single file');
72
+ const file = await this.getFileFromPath(files[0].filePath);
73
+ if (!file)
74
+ throw new Error('File not found');
75
+ body = file;
76
+ }
77
+ else {
78
+ const formData = new FormData();
79
+ for (const fileOption of files) {
80
+ const file = await this.getFileFromPath(fileOption.filePath);
81
+ if (!file)
82
+ throw new Error('File not found');
83
+ const fieldName = (_c = (_b = fileOption.fieldName) !== null && _b !== void 0 ? _b : options.fileField) !== null && _c !== void 0 ? _c : 'file';
84
+ formData.append(fieldName, file);
85
+ }
86
+ for (const [key, value] of Object.entries(parameters)) {
87
+ formData.append(key, value);
88
+ }
89
+ body = formData;
69
90
  }
70
91
  const response = await fetch(serverUrl, {
71
- method,
92
+ method: resolvedMethod,
72
93
  headers,
73
- body: method === 'PUT' ? file : formData,
94
+ body,
74
95
  signal: upload.controller.signal,
75
96
  });
76
97
  if (!response.ok)
@@ -100,6 +121,20 @@ var capacitorCapacitorUpdater = (function (exports, core, idb) {
100
121
  }
101
122
  }
102
123
  }
124
+ normalizeFiles(options) {
125
+ if (options.files && options.files.length > 0)
126
+ return options.files;
127
+ if (options.filePath) {
128
+ return [
129
+ {
130
+ filePath: options.filePath,
131
+ fieldName: options.fileField,
132
+ mimeType: options.mimeType,
133
+ },
134
+ ];
135
+ }
136
+ return [];
137
+ }
103
138
  async getFileFromPath(filePath) {
104
139
  // Check if the path is an IndexedDB path
105
140
  if (PathHelper.isIndexedDBPath(filePath)) {
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/PathHelper.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst Uploader = registerPlugin('Uploader', {\n web: () => import('./web').then((m) => new m.UploaderWeb()),\n});\nexport * from './definitions';\nexport { Uploader };\n//# sourceMappingURL=index.js.map","export class PathHelper {\n // Check if the path follows the idb://[databaseName]/[storeName]/[key] format\n static isIndexedDBPath(path) {\n const regex = /^idb:\\/\\/([^/]+)\\/([^/]+)\\/(.+)$/;\n return regex.test(path);\n }\n // Parse the path to extract database, storeName, and key\n static parseIndexedDBPath(path) {\n const regex = /^idb:\\/\\/([^/]+)\\/([^/]+)\\/(.+)$/;\n const match = path.match(regex);\n if (!match) {\n throw new Error('Invalid IndexedDB path format');\n }\n return {\n database: match[1],\n storeName: match[2],\n key: match[3],\n };\n }\n}\n//# sourceMappingURL=PathHelper.js.map","import { WebPlugin } from '@capacitor/core';\nimport { openDB } from 'idb';\nimport { PathHelper } from './PathHelper';\nexport class UploaderWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.uploads = new Map();\n }\n async startUpload(options) {\n console.log('startUpload', options);\n const id = Math.random().toString(36).substring(2, 15);\n const controller = new AbortController();\n const maxRetries = options.maxRetries || 3;\n this.uploads.set(id, { controller, retries: maxRetries });\n this.doUpload(id, options);\n return { id };\n }\n async removeUpload(options) {\n console.log('removeUpload', options);\n const upload = this.uploads.get(options.id);\n if (upload) {\n upload.controller.abort();\n this.uploads.delete(options.id);\n this.notifyListeners('events', {\n name: 'cancelled',\n id: options.id,\n payload: {},\n });\n }\n }\n async doUpload(id, options) {\n const { filePath, serverUrl, headers = {}, method = 'POST', parameters = {} } = options;\n const upload = this.uploads.get(id);\n if (!upload)\n return;\n try {\n const file = await this.getFileFromPath(filePath);\n if (!file)\n throw new Error('File not found');\n const formData = new FormData();\n formData.append('file', file);\n for (const [key, value] of Object.entries(parameters)) {\n formData.append(key, value);\n }\n const response = await fetch(serverUrl, {\n method,\n headers,\n body: method === 'PUT' ? file : formData,\n signal: upload.controller.signal,\n });\n if (!response.ok)\n throw new Error(`HTTP error! status: ${response.status}`);\n this.notifyListeners('events', {\n name: 'completed',\n id,\n payload: { statusCode: response.status },\n });\n this.uploads.delete(id);\n }\n catch (error) {\n if (error.name === 'AbortError')\n return;\n if (upload.retries > 0) {\n upload.retries--;\n console.log(`Retrying upload (retries left: ${upload.retries})`);\n setTimeout(() => this.doUpload(id, options), 1000);\n }\n else {\n this.notifyListeners('events', {\n name: 'failed',\n id,\n payload: { error: error.message },\n });\n this.uploads.delete(id);\n }\n }\n }\n async getFileFromPath(filePath) {\n // Check if the path is an IndexedDB path\n if (PathHelper.isIndexedDBPath(filePath)) {\n return this.getFileFromIndexedDB(filePath);\n }\n // Otherwise, treat it as a file path from the system\n return this.getFileFromSystem(filePath);\n }\n // Retrieve the file from IndexedDB\n async getFileFromIndexedDB(filePath) {\n // Parse the path to get the database, store name, and key\n const { database, storeName, key } = PathHelper.parseIndexedDBPath(filePath);\n try {\n // Open the IndexedDB database and access the object store\n const db = await openDB(database, 1, {\n upgrade(db) {\n if (!db.objectStoreNames.contains(storeName)) {\n db.createObjectStore(storeName);\n }\n },\n });\n // Get the blob from the store\n const blob = await db.get(storeName, key);\n if (!blob) {\n console.error(`File with key \"${key}\" not found in store \"${storeName}\" in database \"${database}\"`);\n return null;\n }\n // Convert the Blob to a File object\n return new File([blob], key, { type: blob.type });\n }\n catch (error) {\n console.error('Error retrieving file from IndexedDB:', error);\n return null;\n }\n }\n // Retrieve the file from the system (local file)\n async getFileFromSystem(filePath) {\n try {\n // This is a simplified version. In a real-world scenario,\n // you might need to handle different types of paths or use a file system API.\n const response = await fetch(filePath);\n const blob = await response.blob();\n return new File([blob], filePath.split('/').pop() || 'file', {\n type: blob.type,\n });\n }\n catch (error) {\n console.error('Error getting file from system:', error);\n return null;\n }\n }\n async getPluginVersion() {\n return { version: 'web' };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin","openDB"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/PathHelper.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst Uploader = registerPlugin('Uploader', {\n web: () => import('./web').then((m) => new m.UploaderWeb()),\n});\nexport * from './definitions';\nexport { Uploader };\n//# sourceMappingURL=index.js.map","export class PathHelper {\n // Check if the path follows the idb://[databaseName]/[storeName]/[key] format\n static isIndexedDBPath(path) {\n const regex = /^idb:\\/\\/([^/]+)\\/([^/]+)\\/(.+)$/;\n return regex.test(path);\n }\n // Parse the path to extract database, storeName, and key\n static parseIndexedDBPath(path) {\n const regex = /^idb:\\/\\/([^/]+)\\/([^/]+)\\/(.+)$/;\n const match = path.match(regex);\n if (!match) {\n throw new Error('Invalid IndexedDB path format');\n }\n return {\n database: match[1],\n storeName: match[2],\n key: match[3],\n };\n }\n}\n//# sourceMappingURL=PathHelper.js.map","import { WebPlugin } from '@capacitor/core';\nimport { openDB } from 'idb';\nimport { PathHelper } from './PathHelper';\nexport class UploaderWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.uploads = new Map();\n }\n async startUpload(options) {\n console.log('startUpload', options);\n const id = Math.random().toString(36).substring(2, 15);\n const controller = new AbortController();\n const maxRetries = options.maxRetries || 3;\n this.uploads.set(id, { controller, retries: maxRetries });\n this.doUpload(id, options);\n return { id };\n }\n async removeUpload(options) {\n console.log('removeUpload', options);\n const upload = this.uploads.get(options.id);\n if (upload) {\n upload.controller.abort();\n this.uploads.delete(options.id);\n this.notifyListeners('events', {\n name: 'cancelled',\n id: options.id,\n payload: {},\n });\n }\n }\n async doUpload(id, options) {\n var _a, _b, _c;\n const { serverUrl, headers = {}, method = 'POST', parameters = {} } = options;\n const upload = this.uploads.get(id);\n if (!upload)\n return;\n try {\n const files = this.normalizeFiles(options);\n if (files.length === 0)\n throw new Error('Missing required parameter: filePath or files');\n const resolvedMethod = method.toUpperCase();\n const uploadType = (_a = options.uploadType) !== null && _a !== void 0 ? _a : (resolvedMethod === 'PUT' ? 'binary' : 'multipart');\n let body;\n if (resolvedMethod === 'PUT' || uploadType === 'binary') {\n if (files.length !== 1)\n throw new Error('Binary uploads only support a single file');\n const file = await this.getFileFromPath(files[0].filePath);\n if (!file)\n throw new Error('File not found');\n body = file;\n }\n else {\n const formData = new FormData();\n for (const fileOption of files) {\n const file = await this.getFileFromPath(fileOption.filePath);\n if (!file)\n throw new Error('File not found');\n const fieldName = (_c = (_b = fileOption.fieldName) !== null && _b !== void 0 ? _b : options.fileField) !== null && _c !== void 0 ? _c : 'file';\n formData.append(fieldName, file);\n }\n for (const [key, value] of Object.entries(parameters)) {\n formData.append(key, value);\n }\n body = formData;\n }\n const response = await fetch(serverUrl, {\n method: resolvedMethod,\n headers,\n body,\n signal: upload.controller.signal,\n });\n if (!response.ok)\n throw new Error(`HTTP error! status: ${response.status}`);\n this.notifyListeners('events', {\n name: 'completed',\n id,\n payload: { statusCode: response.status },\n });\n this.uploads.delete(id);\n }\n catch (error) {\n if (error.name === 'AbortError')\n return;\n if (upload.retries > 0) {\n upload.retries--;\n console.log(`Retrying upload (retries left: ${upload.retries})`);\n setTimeout(() => this.doUpload(id, options), 1000);\n }\n else {\n this.notifyListeners('events', {\n name: 'failed',\n id,\n payload: { error: error.message },\n });\n this.uploads.delete(id);\n }\n }\n }\n normalizeFiles(options) {\n if (options.files && options.files.length > 0)\n return options.files;\n if (options.filePath) {\n return [\n {\n filePath: options.filePath,\n fieldName: options.fileField,\n mimeType: options.mimeType,\n },\n ];\n }\n return [];\n }\n async getFileFromPath(filePath) {\n // Check if the path is an IndexedDB path\n if (PathHelper.isIndexedDBPath(filePath)) {\n return this.getFileFromIndexedDB(filePath);\n }\n // Otherwise, treat it as a file path from the system\n return this.getFileFromSystem(filePath);\n }\n // Retrieve the file from IndexedDB\n async getFileFromIndexedDB(filePath) {\n // Parse the path to get the database, store name, and key\n const { database, storeName, key } = PathHelper.parseIndexedDBPath(filePath);\n try {\n // Open the IndexedDB database and access the object store\n const db = await openDB(database, 1, {\n upgrade(db) {\n if (!db.objectStoreNames.contains(storeName)) {\n db.createObjectStore(storeName);\n }\n },\n });\n // Get the blob from the store\n const blob = await db.get(storeName, key);\n if (!blob) {\n console.error(`File with key \"${key}\" not found in store \"${storeName}\" in database \"${database}\"`);\n return null;\n }\n // Convert the Blob to a File object\n return new File([blob], key, { type: blob.type });\n }\n catch (error) {\n console.error('Error retrieving file from IndexedDB:', error);\n return null;\n }\n }\n // Retrieve the file from the system (local file)\n async getFileFromSystem(filePath) {\n try {\n // This is a simplified version. In a real-world scenario,\n // you might need to handle different types of paths or use a file system API.\n const response = await fetch(filePath);\n const blob = await response.blob();\n return new File([blob], filePath.split('/').pop() || 'file', {\n type: blob.type,\n });\n }\n catch (error) {\n console.error('Error getting file from system:', error);\n return null;\n }\n }\n async getPluginVersion() {\n return { version: 'web' };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin","openDB"],"mappings":"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"}
@@ -10,8 +10,14 @@ import MobileCoreServices
10
10
  private var retries: [String: Int] = [:]
11
11
  private var tempBodyFiles: [String: URL] = [:]
12
12
 
13
+ private struct UploadFilePart {
14
+ let fileUrl: URL
15
+ let fieldName: String
16
+ let mimeType: String
17
+ }
18
+
13
19
  private struct UploadConfig {
14
- let filePath: String
20
+ let filePath: String?
15
21
  let serverUrl: String
16
22
  let options: [String: Any]
17
23
  }
@@ -20,7 +26,7 @@ import MobileCoreServices
20
26
 
21
27
  var eventHandler: (([String: Any]) -> Void)?
22
28
 
23
- @objc public func startUpload(_ filePath: String, _ serverUrl: String, _ options: [String: Any], maxRetries: Int = 3) async throws -> String {
29
+ @objc public func startUpload(_ filePath: String?, _ serverUrl: String, _ options: [String: Any], maxRetries: Int = 3) async throws -> String {
24
30
  let id = UUID().uuidString
25
31
  print("startUpload: \(id)")
26
32
 
@@ -53,25 +59,25 @@ import MobileCoreServices
53
59
  request.setValue(value, forHTTPHeaderField: key)
54
60
  }
55
61
 
56
- let filePath = config.filePath
57
- let fileUrl: URL
58
- if let candidateUrl = URL(string: filePath), candidateUrl.isFileURL {
59
- fileUrl = candidateUrl
60
- } else {
61
- fileUrl = URL(fileURLWithPath: filePath)
62
- }
63
- guard fileUrl.isFileURL else {
64
- throw NSError(domain: "UploaderPlugin", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid file URL"])
65
- }
66
- let mimeType = config.options["mimeType"] as? String ?? guessMIMEType(from: filePath)
62
+ let defaultFieldName = (config.options["fileField"] as? String).flatMap { $0.isEmpty ? nil : $0 } ?? "file"
63
+ let fileParts = try resolveUploadFiles(config: config, defaultFieldName: defaultFieldName)
64
+
65
+ let uploadType = (config.options["uploadType"] as? String)?.lowercased() ?? (request.httpMethod == "PUT" ? "binary" : "multipart")
67
66
 
68
67
  let task: URLSessionTask
69
- if request.httpMethod == "PUT" {
70
- // For S3 presigned URL uploads
71
- request.setValue(mimeType, forHTTPHeaderField: "Content-Type")
72
- task = self.getUrlSession().uploadTask(with: request, fromFile: fileUrl)
68
+ if request.httpMethod == "PUT" || uploadType == "binary" {
69
+ // Binary uploads (e.g. S3 presigned URL uploads)
70
+ guard fileParts.count == 1, let filePart = fileParts.first else {
71
+ throw NSError(
72
+ domain: "UploaderPlugin",
73
+ code: 2,
74
+ userInfo: [NSLocalizedDescriptionKey: "Binary uploads only support a single file"]
75
+ )
76
+ }
77
+ request.setValue(filePart.mimeType, forHTTPHeaderField: "Content-Type")
78
+ task = self.getUrlSession().uploadTask(with: request, fromFile: filePart.fileUrl)
73
79
  } else {
74
- // For POST uploads
80
+ // Multipart uploads
75
81
  let boundary = UUID().uuidString
76
82
  request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
77
83
 
@@ -82,8 +88,8 @@ import MobileCoreServices
82
88
  }
83
89
  let tempDir = FileManager.default.temporaryDirectory
84
90
  let tempFile = tempDir.appendingPathComponent("upload-\(id).tmp")
85
- try writeMultipartBodyToFile(at: tempFile, parameters: parameters, fileUrl: fileUrl, mimeType: mimeType, boundary: boundary)
86
91
  tempBodyFiles[id] = tempFile
92
+ try writeMultipartBodyToFile(at: tempFile, parameters: parameters, fileParts: fileParts, boundary: boundary)
87
93
 
88
94
  task = self.getUrlSession().uploadTask(with: request, fromFile: tempFile)
89
95
  }
@@ -120,6 +126,51 @@ import MobileCoreServices
120
126
  }
121
127
  return "application/octet-stream"
122
128
  }
129
+
130
+ private func resolveUploadFiles(config: UploadConfig, defaultFieldName: String) throws -> [UploadFilePart] {
131
+ let fallbackMimeType = (config.options["mimeType"] as? String).flatMap { $0.isEmpty ? nil : $0 }
132
+
133
+ if let files = config.options["files"] as? [[String: String]], !files.isEmpty {
134
+ return try files.enumerated().map { (index, file) in
135
+ guard let filePath = file["filePath"], !filePath.isEmpty else {
136
+ throw NSError(
137
+ domain: "UploaderPlugin",
138
+ code: 3,
139
+ userInfo: [NSLocalizedDescriptionKey: "Missing required parameter: files[\(index)].filePath"]
140
+ )
141
+ }
142
+ let fileUrl = try resolveFileUrl(from: filePath)
143
+ let fieldName = (file["fieldName"]?.isEmpty == false ? file["fieldName"]! : defaultFieldName)
144
+ let mimeType = (file["mimeType"]?.isEmpty == false ? file["mimeType"]! : (fallbackMimeType ?? guessMIMEType(from: fileUrl.path)))
145
+ return UploadFilePart(fileUrl: fileUrl, fieldName: fieldName, mimeType: mimeType)
146
+ }
147
+ }
148
+
149
+ if let filePath = config.filePath, !filePath.isEmpty {
150
+ let fileUrl = try resolveFileUrl(from: filePath)
151
+ let mimeType = fallbackMimeType ?? guessMIMEType(from: fileUrl.path)
152
+ return [UploadFilePart(fileUrl: fileUrl, fieldName: defaultFieldName, mimeType: mimeType)]
153
+ }
154
+
155
+ throw NSError(
156
+ domain: "UploaderPlugin",
157
+ code: 4,
158
+ userInfo: [NSLocalizedDescriptionKey: "Missing required parameter: filePath or files"]
159
+ )
160
+ }
161
+
162
+ private func resolveFileUrl(from filePath: String) throws -> URL {
163
+ let fileUrl: URL
164
+ if let candidateUrl = URL(string: filePath), candidateUrl.isFileURL {
165
+ fileUrl = candidateUrl
166
+ } else {
167
+ fileUrl = URL(fileURLWithPath: filePath)
168
+ }
169
+ guard fileUrl.isFileURL else {
170
+ throw NSError(domain: "UploaderPlugin", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid file URL"])
171
+ }
172
+ return fileUrl
173
+ }
123
174
  public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
124
175
  guard let id = task.taskDescription else { return }
125
176
 
@@ -179,7 +230,12 @@ import MobileCoreServices
179
230
  }
180
231
 
181
232
  // Writes multipart/form-data body directly to a file, streaming the file content in chunks
182
- private func writeMultipartBodyToFile(at tempFile: URL, parameters: [String: String], fileUrl: URL, mimeType: String, boundary: String) throws {
233
+ private func writeMultipartBodyToFile(
234
+ at tempFile: URL,
235
+ parameters: [String: String],
236
+ fileParts: [UploadFilePart],
237
+ boundary: String
238
+ ) throws {
183
239
  FileManager.default.createFile(atPath: tempFile.path, contents: nil)
184
240
  let writeHandle = try FileHandle(forWritingTo: tempFile)
185
241
  defer { try? writeHandle.close() }
@@ -194,19 +250,23 @@ import MobileCoreServices
194
250
  try write("\(value)\r\n")
195
251
  }
196
252
 
197
- try write("--\(boundary)\r\n")
198
- try write("Content-Disposition: form-data; name=\"file\"; filename=\"\(fileUrl.lastPathComponent)\"\r\n")
199
- try write("Content-Type: \(mimeType)\r\n\r\n")
253
+ for filePart in fileParts {
254
+ try write("--\(boundary)\r\n")
255
+ try write("Content-Disposition: form-data; name=\"\(filePart.fieldName)\"; filename=\"\(filePart.fileUrl.lastPathComponent)\"\r\n")
256
+ try write("Content-Type: \(filePart.mimeType)\r\n\r\n")
257
+
258
+ let readHandle = try FileHandle(forReadingFrom: filePart.fileUrl)
259
+ defer { try? readHandle.close() }
260
+ let chunkSize = 64 * 1024
261
+ while true {
262
+ guard let chunk = try readHandle.read(upToCount: chunkSize), !chunk.isEmpty else { break }
263
+ try writeHandle.write(contentsOf: chunk)
264
+ }
200
265
 
201
- let readHandle = try FileHandle(forReadingFrom: fileUrl)
202
- defer { try? readHandle.close() }
203
- let chunkSize = 64 * 1024
204
- while true {
205
- guard let chunk = try readHandle.read(upToCount: chunkSize), !chunk.isEmpty else { break }
206
- try writeHandle.write(contentsOf: chunk)
266
+ try write("\r\n")
207
267
  }
208
268
 
209
- try write("\r\n--\(boundary)--")
269
+ try write("--\(boundary)--\r\n")
210
270
  }
211
271
  }
212
272
 
@@ -3,7 +3,7 @@ import Capacitor
3
3
 
4
4
  @objc(UploaderPlugin)
5
5
  public class UploaderPlugin: CAPPlugin, CAPBridgedPlugin {
6
- private let pluginVersion: String = "8.1.21"
6
+ private let pluginVersion: String = "8.2.0"
7
7
  public let identifier = "UploaderPlugin"
8
8
  public let jsName = "Uploader"
9
9
  public let pluginMethods: [CAPPluginMethod] = [
@@ -20,22 +20,42 @@ public class UploaderPlugin: CAPPlugin, CAPBridgedPlugin {
20
20
  }
21
21
 
22
22
  @objc func startUpload(_ call: CAPPluginCall) {
23
- guard let filePath = call.getString("filePath") else {
24
- call.reject("Missing required parameter: filePath")
25
- return
26
- }
23
+ let filePath = call.getString("filePath")
24
+ let files = call.getArray("files")
27
25
  guard let serverUrl = call.getString("serverUrl") else {
28
26
  call.reject("Missing required parameter: serverUrl")
29
27
  return
30
28
  }
29
+ if (filePath == nil || filePath?.isEmpty == true) && (files == nil || files?.isEmpty == true) {
30
+ call.reject("Missing required parameter: filePath or files")
31
+ return
32
+ }
33
+
34
+ let headers = (call.getObject("headers") ?? [:]).compactMapValues { $0 as? String }
35
+ let parameters = (call.getObject("parameters") ?? [:]).compactMapValues { $0 as? String }
31
36
 
32
- let options: [String: Any] = [
33
- "headers": call.getObject("headers") as Any,
34
- "method": call.getString("method") as Any,
35
- "mimeType": call.getString("mimeType") as Any,
36
- "parameters": call.getObject("parameters") as Any
37
+ var options: [String: Any] = [
38
+ "headers": headers,
39
+ "parameters": parameters
37
40
  ]
38
41
 
42
+ if let method = call.getString("method") { options["method"] = method }
43
+ if let mimeType = call.getString("mimeType") { options["mimeType"] = mimeType }
44
+ if let uploadType = call.getString("uploadType") { options["uploadType"] = uploadType }
45
+ if let fileField = call.getString("fileField") { options["fileField"] = fileField }
46
+
47
+ if let files {
48
+ let normalizedFiles: [[String: String]] = files.compactMap { item in
49
+ guard let obj = item as? JSObject else { return nil }
50
+ var out: [String: String] = [:]
51
+ if let filePath = obj["filePath"] as? String { out["filePath"] = filePath }
52
+ if let fieldName = obj["fieldName"] as? String { out["fieldName"] = fieldName }
53
+ if let mimeType = obj["mimeType"] as? String { out["mimeType"] = mimeType }
54
+ return out.isEmpty ? nil : out
55
+ }
56
+ if !normalizedFiles.isEmpty { options["files"] = normalizedFiles }
57
+ }
58
+
39
59
  let maxRetries = call.getInt("maxRetries") ?? 3
40
60
 
41
61
  Task {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-uploader",
3
- "version": "8.1.21",
3
+ "version": "8.2.0",
4
4
  "description": "Upload file natively",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",