@capgo/capacitor-uploader 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'CapgoCapacitorUploader'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.license = package['license']
10
+ s.homepage = package['repository']['url']
11
+ s.author = package['author']
12
+ s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13
+ s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
14
+ s.ios.deployment_target = '13.0'
15
+ s.dependency 'Capacitor'
16
+ s.swift_version = '5.1'
17
+ end
package/Package.swift ADDED
@@ -0,0 +1,28 @@
1
+ // swift-tools-version: 5.9
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "CapgoCapacitorUploader",
6
+ platforms: [.iOS(.v13)],
7
+ products: [
8
+ .library(
9
+ name: "CapgoCapacitorUploader",
10
+ targets: ["UploaderPlugin"])
11
+ ],
12
+ dependencies: [
13
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", branch: "main")
14
+ ],
15
+ targets: [
16
+ .target(
17
+ name: "UploaderPlugin",
18
+ dependencies: [
19
+ .product(name: "Capacitor", package: "capacitor-swift-pm"),
20
+ .product(name: "Cordova", package: "capacitor-swift-pm")
21
+ ],
22
+ path: "ios/Sources/UploaderPlugin"),
23
+ .testTarget(
24
+ name: "UploaderPluginTests",
25
+ dependencies: ["UploaderPlugin"],
26
+ path: "ios/Tests/UploaderPluginTests")
27
+ ]
28
+ )
package/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # @capgo/capacitor-uploader
2
+ <a href="https://capgo.app/"><img src='https://raw.githubusercontent.com/Cap-go/capgo/main/assets/capgo_banner.png' alt='Capgo - Instant updates for capacitor'/></a>
3
+ <div align="center">
4
+ <h2><a href="https://capgo.app/">Check out: Capgo — Instant updates for capacitor</a></h2>
5
+ </div>
6
+
7
+ ## Uploader Plugin
8
+
9
+ This plugin provides a flexible way to upload natively files to various servers, including S3 with presigned URLs.
10
+
11
+ WIP: this is a work in progress still not ready for use
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install @capgo/capacitor-uploader
17
+ npx cap sync
18
+ ```
19
+
20
+ ## Android:
21
+
22
+ Add the following to your `AndroidManifest.xml` file:
23
+
24
+ ```xml
25
+ <uses-permission android:name="android.permission.INTERNET" />
26
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
27
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
28
+ ```
29
+
30
+ ## Exemple S3 upload:
31
+
32
+
33
+ ## Example S3 upload:
34
+
35
+ ```typescript
36
+ import { Uploader } from '@capgo/capacitor-uploader';
37
+
38
+ async function uploadToS3(filePath: string, presignedUrl: string, fields: Record<string, string>) {
39
+ try {
40
+ const { id } = await Uploader.startUpload({
41
+ filePath: filePath,
42
+ serverUrl: presignedUrl,
43
+ method: 'PUT',
44
+ parameters: fields,
45
+ notificationTitle: 'Uploading to S3'
46
+ });
47
+
48
+ console.log('Upload started with ID:', id);
49
+
50
+ // Listen for upload events
51
+ Uploader.addListener('events', (event: UploadEvent) => {
52
+ if (event.name === 'uploading') {
53
+ console.log(`Upload progress: ${event.payload.percent}%`);
54
+ } else if (event.name === 'completed') {
55
+ console.log('Upload completed successfully');
56
+ } else if (event.name === 'failed') {
57
+ console.error('Upload failed:', event.payload.error);
58
+ }
59
+ });
60
+
61
+ } catch (error) {
62
+ console.error('Failed to start upload:', error);
63
+ }
64
+ }
65
+
66
+ ```
67
+
68
+ ### Exemple upload to a custom server:
69
+
70
+ ```typescript
71
+ import { Uploader } from '@capgo/capacitor-uploader';
72
+
73
+ async function uploadToCustomServer(filePath: string, serverUrl: string) {
74
+ try {
75
+ // Start the upload
76
+ const { id } = await Uploader.startUpload({
77
+ filePath: filePath,
78
+ serverUrl: serverUrl,
79
+ method: 'POST',
80
+ headers: {
81
+ 'Authorization': 'Bearer your-auth-token-here'
82
+ },
83
+ parameters: {
84
+ 'user_id': '12345',
85
+ 'file_type': 'image'
86
+ },
87
+ notificationTitle: 'Uploading to Custom Server',
88
+ maxRetries: 3
89
+ });
90
+
91
+ console.log('Upload started with ID:', id);
92
+
93
+ // Listen for upload events
94
+ Uploader.addListener('events', (event) => {
95
+ switch (event.name) {
96
+ case 'uploading':
97
+ console.log(`Upload progress: ${event.payload.percent}%`);
98
+ break;
99
+ case 'completed':
100
+ console.log('Upload completed successfully');
101
+ console.log('Server response status code:', event.payload.statusCode);
102
+ break;
103
+ case 'failed':
104
+ console.error('Upload failed:', event.payload.error);
105
+ break;
106
+ }
107
+ });
108
+
109
+ // Optional: Remove the upload if needed
110
+ // await Uploader.removeUpload({ id: id });
111
+
112
+ } catch (error) {
113
+ console.error('Failed to start upload:', error);
114
+ }
115
+ }
116
+
117
+ // Usage
118
+ const filePath = 'file:///path/to/your/file.jpg';
119
+ const serverUrl = 'https://your-custom-server.com/upload';
120
+ uploadToCustomServer(filePath, serverUrl);
121
+
122
+ ```
123
+
124
+ ## API
125
+
126
+ <docgen-index>
127
+
128
+ * [`startUpload(...)`](#startupload)
129
+ * [`removeUpload(...)`](#removeupload)
130
+ * [`addListener('events', ...)`](#addlistenerevents-)
131
+ * [Interfaces](#interfaces)
132
+
133
+ </docgen-index>
134
+
135
+ <docgen-api>
136
+ <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
137
+
138
+ ### startUpload(...)
139
+
140
+ ```typescript
141
+ startUpload(options: uploadOption) => any
142
+ ```
143
+
144
+ | Param | Type |
145
+ | ------------- | ----------------------------------------------------- |
146
+ | **`options`** | <code><a href="#uploadoption">uploadOption</a></code> |
147
+
148
+ **Returns:** <code>any</code>
149
+
150
+ **Since:** 1.0.0
151
+
152
+ --------------------
153
+
154
+
155
+ ### removeUpload(...)
156
+
157
+ ```typescript
158
+ removeUpload(options: { id: string; }) => any
159
+ ```
160
+
161
+ | Param | Type |
162
+ | ------------- | ---------------------------- |
163
+ | **`options`** | <code>{ id: string; }</code> |
164
+
165
+ **Returns:** <code>any</code>
166
+
167
+ **Since:** 1.0.0
168
+
169
+ --------------------
170
+
171
+
172
+ ### addListener('events', ...)
173
+
174
+ ```typescript
175
+ addListener(eventName: 'events', listenerFunc: (state: UploadEvent) => void) => any
176
+ ```
177
+
178
+ | Param | Type |
179
+ | ------------------ | ----------------------------------------------------------------------- |
180
+ | **`eventName`** | <code>'events'</code> |
181
+ | **`listenerFunc`** | <code>(state: <a href="#uploadevent">UploadEvent</a>) =&gt; void</code> |
182
+
183
+ **Returns:** <code>any</code>
184
+
185
+ **Since:** 1.0.0
186
+
187
+ --------------------
188
+
189
+
190
+ ### Interfaces
191
+
192
+
193
+ #### uploadOption
194
+
195
+ | Prop | Type | Default | Since |
196
+ | ----------------------- | --------------------------------------- | ------------------------ | ----- |
197
+ | **`filePath`** | <code>string</code> | | 1.0.0 |
198
+ | **`serverUrl`** | <code>string</code> | | 1.0.0 |
199
+ | **`notificationTitle`** | <code>number</code> | <code>'Uploading'</code> | 1.0.0 |
200
+ | **`headers`** | <code>{ [key: string]: string; }</code> | | 1.0.0 |
201
+ | **`method`** | <code>'PUT' \| 'POST'</code> | <code>'POST'</code> | 1.0.0 |
202
+ | **`mimeType`** | <code>string</code> | | 1.0.0 |
203
+ | **`parameters`** | <code>{ [key: string]: string; }</code> | | 1.0.0 |
204
+ | **`maxRetries`** | <code>number</code> | | 1.0.0 |
205
+
206
+
207
+ #### UploadEvent
208
+
209
+ | Prop | Type | Description | Since |
210
+ | ------------- | ----------------------------------------------------------------------- | -------------------------------------------- | ----- |
211
+ | **`name`** | <code>'uploading' \| 'completed' \| 'failed'</code> | Current status of upload, between 0 and 100. | |
212
+ | **`payload`** | <code>{ percent?: number; error?: string; statusCode?: number; }</code> | | 1.0.0 |
213
+ | **`id`** | <code>string</code> | | 1.0.0 |
214
+
215
+
216
+ #### PluginListenerHandle
217
+
218
+ | Prop | Type |
219
+ | ------------ | ------------------------- |
220
+ | **`remove`** | <code>() =&gt; any</code> |
221
+
222
+ </docgen-api>
223
+
224
+ ### Credits:
225
+
226
+ For the inspiration and the code on ios: https://github.com/Vydia/react-native-background-upload/tree/master
227
+ For the API definition: https://www.npmjs.com/package/cordova-plugin-background-upload-put-s3
228
+
@@ -0,0 +1,59 @@
1
+ ext {
2
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.6.1'
4
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1.5'
5
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.5.1'
6
+ }
7
+
8
+ buildscript {
9
+ repositories {
10
+ google()
11
+ mavenCentral()
12
+ }
13
+ dependencies {
14
+ classpath 'com.android.tools.build:gradle:8.2.1'
15
+ }
16
+ }
17
+
18
+ apply plugin: 'com.android.library'
19
+
20
+ android {
21
+ namespace "ee.forgr.capacitor.uploader"
22
+ compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 34
23
+ defaultConfig {
24
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22
25
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 34
26
+ versionCode 1
27
+ versionName "1.0"
28
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
29
+ }
30
+ buildTypes {
31
+ release {
32
+ minifyEnabled false
33
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
34
+ }
35
+ }
36
+ lintOptions {
37
+ abortOnError false
38
+ }
39
+ compileOptions {
40
+ sourceCompatibility JavaVersion.VERSION_17
41
+ targetCompatibility JavaVersion.VERSION_17
42
+ }
43
+ }
44
+
45
+ repositories {
46
+ google()
47
+ mavenCentral()
48
+ }
49
+
50
+
51
+ dependencies {
52
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
53
+ implementation project(':capacitor-android')
54
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
55
+ implementation 'net.gotev:uploadservice:4.7.0'
56
+ testImplementation "junit:junit:$junitVersion"
57
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
58
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
59
+ }
@@ -0,0 +1,33 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
+ package="ee.forgr.capacitor.uploader">
4
+
5
+ <application>
6
+ <service
7
+ android:name="net.gotev.uploadservice.UploadService"
8
+ android:enabled="true"
9
+ android:exported="false"
10
+ android:foregroundServiceType="dataSync">
11
+ <intent-filter>
12
+ <action android:name="android.intent.action.RUN" />
13
+ </intent-filter>
14
+ </service>
15
+
16
+ <service
17
+ android:name="net.gotev.uploadservice.UploadJobService"
18
+ android:enabled="true"
19
+ android:exported="false"
20
+ android:permission="android.permission.BIND_JOB_SERVICE">
21
+ </service>
22
+
23
+ <receiver
24
+ android:name="net.gotev.uploadservice.UploadServiceBroadcastReceiver"
25
+ android:enabled="true"
26
+ android:exported="false">
27
+ <intent-filter>
28
+ <action android:name="net.gotev.uploadservice.broadcast.status" />
29
+ </intent-filter>
30
+ </receiver>
31
+ </application>
32
+
33
+ </manifest>
@@ -0,0 +1,123 @@
1
+ package ee.forgr.capacitor.uploader;
2
+
3
+ import android.app.Application;
4
+ import android.content.Context;
5
+ import android.net.Uri;
6
+ import android.provider.OpenableColumns;
7
+ import android.database.Cursor;
8
+
9
+ import net.gotev.uploadservice.UploadServiceConfig;
10
+ import net.gotev.uploadservice.data.UploadNotificationConfig;
11
+ import net.gotev.uploadservice.data.UploadNotificationStatusConfig;
12
+ import net.gotev.uploadservice.protocols.multipart.MultipartUploadRequest;
13
+ import net.gotev.uploadservice.observer.request.RequestObserverDelegate;
14
+
15
+ import java.util.Map;
16
+
17
+ public class Uploader {
18
+
19
+ private final Context context;
20
+ private final RequestObserverDelegate delegate;
21
+
22
+ public Uploader(Context context, RequestObserverDelegate delegate) {
23
+ this.context = context;
24
+ this.delegate = delegate;
25
+ initializeUploadService(context);
26
+ }
27
+
28
+ private void initializeUploadService(Context context) {
29
+ Application application = getApplication(context);
30
+ if (application != null) {
31
+ UploadServiceConfig.initialize(application, "ee.forgr.capacitor.uploader.notification_channel_id", true);
32
+ } else {
33
+ throw new IllegalStateException("Unable to get Application instance");
34
+ }
35
+ }
36
+
37
+ private Application getApplication(Context context) {
38
+ if (context == null) {
39
+ return null;
40
+ } else if (context instanceof Application) {
41
+ return (Application) context;
42
+ } else {
43
+ return getApplication(context.getApplicationContext());
44
+ }
45
+ }
46
+
47
+ public String startUpload(String filePath, String serverUrl, Map<String, String> headers, Map<String, String> parameters,
48
+ String httpMethod, String notificationTitle, int maxRetries, String mimeType) throws Exception {
49
+ UploadNotificationConfig notificationConfig = createNotificationConfig(notificationTitle);
50
+
51
+ MultipartUploadRequest request = new MultipartUploadRequest(context, serverUrl)
52
+ .setMethod(httpMethod)
53
+ .addFileToUpload(filePath, "file", mimeType) // Updated this line
54
+ .setNotificationConfig((ctx, uploadId) -> notificationConfig)
55
+ .setMaxRetries(maxRetries);
56
+
57
+ // Add headers
58
+ for (Map.Entry<String, String> entry : headers.entrySet()) {
59
+ request.addHeader(entry.getKey(), entry.getValue());
60
+ }
61
+
62
+ // Add parameters
63
+ for (Map.Entry<String, String> entry : parameters.entrySet()) {
64
+ request.addParameter(entry.getKey(), entry.getValue());
65
+ }
66
+
67
+ // Set file name if it's a content URI
68
+ if (filePath.startsWith("content://")) {
69
+ Uri uri = Uri.parse(filePath);
70
+ String fileName = getFileNameFromUri(uri);
71
+ if (fileName != null) {
72
+ request.addParameter("filename", fileName);
73
+ }
74
+ }
75
+
76
+ // Start the upload
77
+ return request.startUpload();
78
+ }
79
+
80
+ public void removeUpload(String uploadId) {
81
+ net.gotev.uploadservice.UploadService.stopUpload(uploadId);
82
+ }
83
+
84
+ private UploadNotificationConfig createNotificationConfig(String notificationTitle) {
85
+ UploadNotificationStatusConfig progress = new UploadNotificationStatusConfig(notificationTitle, notificationTitle + " - In Progress");
86
+ UploadNotificationStatusConfig success = new UploadNotificationStatusConfig(notificationTitle, notificationTitle + " - Completed");
87
+ UploadNotificationStatusConfig error = new UploadNotificationStatusConfig(notificationTitle, notificationTitle + " - Error");
88
+ UploadNotificationStatusConfig cancelled = new UploadNotificationStatusConfig(notificationTitle, notificationTitle + " - Cancelled");
89
+
90
+ return new UploadNotificationConfig(
91
+ "ee.forgr.capacitor.uploader.notification_channel_id",
92
+ false,
93
+ progress,
94
+ success,
95
+ error,
96
+ cancelled
97
+ );
98
+ }
99
+
100
+ private String getFileNameFromUri(Uri uri) {
101
+ String result = null;
102
+ if (uri.getScheme().equals("content")) {
103
+ try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
104
+ if (cursor != null && cursor.moveToFirst()) {
105
+ int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
106
+ if (index != -1) {
107
+ result = cursor.getString(index);
108
+ }
109
+ }
110
+ } catch (Exception e) {
111
+ e.printStackTrace();
112
+ }
113
+ }
114
+ if (result == null) {
115
+ result = uri.getPath();
116
+ int cut = result.lastIndexOf('/');
117
+ if (cut != -1) {
118
+ result = result.substring(cut + 1);
119
+ }
120
+ }
121
+ return result;
122
+ }
123
+ }
@@ -0,0 +1,116 @@
1
+ package ee.forgr.capacitor.uploader;
2
+
3
+ import android.content.Context;
4
+ import com.getcapacitor.JSObject;
5
+ import com.getcapacitor.Plugin;
6
+ import com.getcapacitor.PluginCall;
7
+ import com.getcapacitor.PluginMethod;
8
+ import com.getcapacitor.annotation.CapacitorPlugin;
9
+
10
+ import net.gotev.uploadservice.data.UploadInfo;
11
+ import net.gotev.uploadservice.network.ServerResponse;
12
+ import net.gotev.uploadservice.observer.request.RequestObserverDelegate;
13
+
14
+ import java.util.Map;
15
+ import java.util.HashMap;
16
+ import java.util.Iterator;
17
+
18
+ @CapacitorPlugin(name = "Uploader")
19
+ public class UploaderPlugin extends Plugin {
20
+
21
+ private Uploader implementation;
22
+
23
+ @Override
24
+ public void load() {
25
+ implementation = new Uploader(getContext(), new RequestObserverDelegate() {
26
+ @Override
27
+ public void onProgress(Context context, UploadInfo uploadInfo) {
28
+ JSObject event = new JSObject();
29
+ event.put("name", "uploading");
30
+ JSObject payload = new JSObject();
31
+ payload.put("percent", uploadInfo.getProgressPercent());
32
+ event.put("payload", payload);
33
+ event.put("id", uploadInfo.getUploadId());
34
+ notifyListeners("events", event);
35
+ }
36
+
37
+ @Override
38
+ public void onSuccess(Context context, UploadInfo uploadInfo, ServerResponse serverResponse) {
39
+ JSObject event = new JSObject();
40
+ event.put("name", "completed");
41
+ JSObject payload = new JSObject();
42
+ payload.put("statusCode", serverResponse.getCode());
43
+ event.put("payload", payload);
44
+ event.put("id", uploadInfo.getUploadId());
45
+ notifyListeners("events", event);
46
+ }
47
+
48
+ @Override
49
+ public void onError(Context context, UploadInfo uploadInfo, Throwable exception) {
50
+ JSObject event = new JSObject();
51
+ event.put("name", "failed");
52
+ JSObject payload = new JSObject();
53
+ payload.put("error", exception.getMessage());
54
+ event.put("payload", payload);
55
+ event.put("id", uploadInfo.getUploadId());
56
+ notifyListeners("events", event);
57
+ }
58
+
59
+ @Override
60
+ public void onCompleted(Context context, UploadInfo uploadInfo) {
61
+ // This method is called after onSuccess or onError
62
+ }
63
+
64
+ @Override
65
+ public void onCompletedWhileNotObserving() {
66
+ // This method is called when the upload completes while the observer is not registered
67
+ }
68
+ });
69
+ }
70
+
71
+ @PluginMethod
72
+ public void startUpload(PluginCall call) {
73
+ String filePath = call.getString("filePath");
74
+ String serverUrl = call.getString("serverUrl");
75
+ JSObject headersObj = call.getObject("headers", new JSObject());
76
+ JSObject parametersObj = call.getObject("parameters", new JSObject());
77
+ String httpMethod = call.getString("method", "POST");
78
+ String notificationTitle = call.getString("notificationTitle", "File Upload");
79
+ int maxRetries = call.getInt("maxRetries", 2);
80
+ String mimeType = call.getString("mimeType"); // Add this line
81
+
82
+ Map<String, String> headers = JSObjectToMap(headersObj);
83
+ Map<String, String> parameters = JSObjectToMap(parametersObj);
84
+
85
+ try {
86
+ String id = implementation.startUpload(filePath, serverUrl, headers, parameters, httpMethod, notificationTitle, maxRetries, mimeType);
87
+ JSObject result = new JSObject();
88
+ result.put("id", id);
89
+ call.resolve(result);
90
+ } catch (Exception e) {
91
+ call.reject(e.getMessage());
92
+ }
93
+ }
94
+
95
+ @PluginMethod
96
+ public void removeUpload(PluginCall call) {
97
+ String id = call.getString("id");
98
+ try {
99
+ implementation.removeUpload(id);
100
+ call.resolve();
101
+ } catch (Exception e) {
102
+ call.reject(e.getMessage());
103
+ }
104
+ }
105
+
106
+ private Map<String, String> JSObjectToMap(JSObject object) {
107
+ Map<String, String> map = new HashMap<>();
108
+ if (object != null) {
109
+ for (Iterator<String> it = object.keys(); it.hasNext(); ) {
110
+ String key = it.next();
111
+ map.put(key, object.getString(key));
112
+ }
113
+ }
114
+ return map;
115
+ }
116
+ }
File without changes