@capgo/capacitor-uploader 8.3.7 → 8.3.9

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.
@@ -1,5 +1,6 @@
1
1
  package ee.forgr.capacitor.uploader;
2
2
 
3
+ import android.app.Application;
3
4
  import android.app.NotificationChannel;
4
5
  import android.app.NotificationManager;
5
6
  import android.content.Context;
@@ -14,14 +15,18 @@ import com.getcapacitor.Plugin;
14
15
  import com.getcapacitor.PluginCall;
15
16
  import com.getcapacitor.PluginMethod;
16
17
  import com.getcapacitor.annotation.CapacitorPlugin;
18
+ import java.lang.ref.WeakReference;
17
19
  import java.util.ArrayList;
18
20
  import java.util.HashMap;
19
21
  import java.util.Iterator;
20
22
  import java.util.Map;
23
+ import java.util.Set;
21
24
  import java.util.UUID;
25
+ import java.util.concurrent.ConcurrentHashMap;
26
+ import kotlin.jvm.functions.Function1;
22
27
  import net.gotev.uploadservice.data.UploadInfo;
23
28
  import net.gotev.uploadservice.network.ServerResponse;
24
- import net.gotev.uploadservice.observer.request.RequestObserver;
29
+ import net.gotev.uploadservice.observer.request.GlobalRequestObserver;
25
30
  import net.gotev.uploadservice.observer.request.RequestObserverDelegate;
26
31
  import org.json.JSONException;
27
32
  import org.json.JSONObject;
@@ -33,7 +38,7 @@ public class UploaderPlugin extends Plugin {
33
38
 
34
39
  private static final String CAPACITOR_CONTENT_PATH_PREFIX = "/_capacitor_content_";
35
40
 
36
- private final String pluginVersion = "8.3.7";
41
+ private final String pluginVersion = "8.3.9";
37
42
 
38
43
  private Uploader implementation;
39
44
 
@@ -45,44 +50,60 @@ public class UploaderPlugin extends Plugin {
45
50
  private static final String PENDING_EVENTS_KEY = "pending_events";
46
51
  private static final String TAG = "UploaderPlugin";
47
52
 
48
- private void saveEventToPrefs(String eventId, JSObject event) {
49
- SharedPreferences prefs = getContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
50
- String existingJson = prefs.getString(PENDING_EVENTS_KEY, "{}");
51
- try {
52
- JSONObject pendingEvents = new JSONObject(existingJson);
53
- pendingEvents.put(eventId, new JSONObject(event.toString()));
54
- prefs.edit().putString(PENDING_EVENTS_KEY, pendingEvents.toString()).apply();
55
- } catch (JSONException e) {
56
- Log.e(TAG, "Failed to persist upload event", e);
53
+ // Process-scoped: one observer for the process, filtered to plugin-owned uploads.
54
+ private static final Set<String> OWNED_UPLOAD_IDS = ConcurrentHashMap.newKeySet();
55
+ private static final Object PENDING_EVENTS_LOCK = new Object();
56
+ private static final Object OBSERVER_LOCK = new Object();
57
+ private static GlobalRequestObserver processObserver;
58
+ private static Uploader processUploader;
59
+ private static WeakReference<UploaderPlugin> activePlugin = new WeakReference<>(null);
60
+ // Cleared in handleOnDestroy so emitEvent no-ops while Activity/Bridge is tearing down.
61
+ private static volatile boolean pluginAttached = false;
62
+
63
+ private static void saveEventToPrefs(Context context, String eventId, JSObject event) {
64
+ synchronized (PENDING_EVENTS_LOCK) {
65
+ SharedPreferences prefs = context.getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
66
+ String existingJson = prefs.getString(PENDING_EVENTS_KEY, "{}");
67
+ try {
68
+ JSONObject pendingEvents = new JSONObject(existingJson);
69
+ pendingEvents.put(eventId, new JSONObject(event.toString()));
70
+ prefs.edit().putString(PENDING_EVENTS_KEY, pendingEvents.toString()).apply();
71
+ } catch (JSONException e) {
72
+ Log.e(TAG, "Failed to persist upload event", e);
73
+ }
57
74
  }
58
75
  }
59
76
 
60
- private void removeEventFromPrefs(String eventId) {
61
- SharedPreferences prefs = getContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
62
- String existingJson = prefs.getString(PENDING_EVENTS_KEY, "{}");
63
- try {
64
- JSONObject pendingEvents = new JSONObject(existingJson);
65
- pendingEvents.remove(eventId);
66
- prefs.edit().putString(PENDING_EVENTS_KEY, pendingEvents.toString()).apply();
67
- } catch (JSONException e) {
68
- Log.e(TAG, "Failed to remove upload event from prefs", e);
77
+ private static void removeEventFromPrefs(Context context, String eventId) {
78
+ synchronized (PENDING_EVENTS_LOCK) {
79
+ SharedPreferences prefs = context.getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
80
+ String existingJson = prefs.getString(PENDING_EVENTS_KEY, "{}");
81
+ try {
82
+ JSONObject pendingEvents = new JSONObject(existingJson);
83
+ pendingEvents.remove(eventId);
84
+ prefs.edit().putString(PENDING_EVENTS_KEY, pendingEvents.toString()).apply();
85
+ } catch (JSONException e) {
86
+ Log.e(TAG, "Failed to remove upload event from prefs", e);
87
+ }
69
88
  }
70
89
  }
71
90
 
72
91
  private void replayPendingEvents() {
73
- SharedPreferences prefs = getContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
74
- String existingJson = prefs.getString(PENDING_EVENTS_KEY, "{}");
75
- try {
76
- JSONObject pendingEvents = new JSONObject(existingJson);
77
- Iterator<String> keys = pendingEvents.keys();
78
- while (keys.hasNext()) {
79
- String eventId = keys.next();
80
- JSONObject eventJson = pendingEvents.getJSONObject(eventId);
81
- JSObject event = JSObject.fromJSONObject(eventJson);
82
- notifyListeners("events", event);
92
+ synchronized (PENDING_EVENTS_LOCK) {
93
+ SharedPreferences prefs = getContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
94
+ String existingJson = prefs.getString(PENDING_EVENTS_KEY, "{}");
95
+ try {
96
+ JSONObject pendingEvents = new JSONObject(existingJson);
97
+ Iterator<String> keys = pendingEvents.keys();
98
+ while (keys.hasNext()) {
99
+ String eventId = keys.next();
100
+ JSONObject eventJson = pendingEvents.getJSONObject(eventId);
101
+ JSObject event = JSObject.fromJSONObject(eventJson);
102
+ notifyListeners("events", event, true);
103
+ }
104
+ } catch (JSONException e) {
105
+ Log.e(TAG, "Failed to replay pending upload events", e);
83
106
  }
84
- } catch (JSONException e) {
85
- Log.e(TAG, "Failed to replay pending upload events", e);
86
107
  }
87
108
  }
88
109
 
@@ -97,76 +118,135 @@ public class UploaderPlugin extends Plugin {
97
118
  }
98
119
  }
99
120
 
100
- @Override
101
- public void load() {
102
- createNotificationChannel();
103
-
104
- // Create a request observer for all uploads
105
- RequestObserver observer = new RequestObserver(
106
- getContext().getApplicationContext(),
107
- getActivity(),
108
- new RequestObserverDelegate() {
109
- @Override
110
- public void onProgress(Context context, UploadInfo uploadInfo) {
111
- JSObject event = new JSObject();
112
- event.put("name", "uploading");
113
- JSObject payload = new JSObject();
114
- payload.put("percent", uploadInfo.getProgressPercent());
115
- event.put("payload", payload);
116
- event.put("id", uploadInfo.getUploadId());
117
- notifyListeners("events", event);
118
- }
121
+ private static UploaderPlugin currentPlugin() {
122
+ return activePlugin.get();
123
+ }
119
124
 
120
- @Override
121
- public void onSuccess(Context context, UploadInfo uploadInfo, ServerResponse serverResponse) {
122
- implementation.clearTempMultipartBody(uploadInfo.getUploadId());
123
- JSObject event = new JSObject();
124
- event.put("name", "completed");
125
- JSObject payload = new JSObject();
126
- payload.put("statusCode", serverResponse.getCode());
127
- event.put("payload", payload);
128
- event.put("id", uploadInfo.getUploadId());
129
- String eventId = UUID.randomUUID().toString();
130
- event.put("eventId", eventId);
131
- saveEventToPrefs(eventId, event);
132
- notifyListeners("events", event);
133
- }
125
+ private static void emitEvent(String name, JSObject event, boolean retainUntilConsumed) {
126
+ if (!pluginAttached) {
127
+ return;
128
+ }
129
+ UploaderPlugin plugin = currentPlugin();
130
+ if (plugin == null || plugin.getBridge() == null) {
131
+ return;
132
+ }
133
+ if (retainUntilConsumed) {
134
+ plugin.notifyListeners(name, event, true);
135
+ } else {
136
+ plugin.notifyListeners(name, event);
137
+ }
138
+ }
134
139
 
135
- @Override
136
- public void onError(Context context, UploadInfo uploadInfo, Throwable exception) {
137
- implementation.clearTempMultipartBody(uploadInfo.getUploadId());
138
- JSObject event = new JSObject();
139
- event.put("name", "failed");
140
- JSObject payload = new JSObject();
141
- payload.put("error", exception.getMessage());
142
- event.put("payload", payload);
143
- event.put("id", uploadInfo.getUploadId());
144
- String eventId = UUID.randomUUID().toString();
145
- event.put("eventId", eventId);
146
- saveEventToPrefs(eventId, event);
147
- notifyListeners("events", event);
148
- }
140
+ private static void clearTempBody(String uploadId) {
141
+ if (processUploader != null) {
142
+ processUploader.clearTempMultipartBody(uploadId);
143
+ }
144
+ }
149
145
 
150
- @Override
151
- public void onCompleted(Context context, UploadInfo uploadInfo) {
152
- implementation.clearTempMultipartBody(uploadInfo.getUploadId());
153
- JSObject event = new JSObject();
154
- event.put("name", "finished");
155
- event.put("id", uploadInfo.getUploadId());
156
- notifyListeners("events", event);
157
- }
146
+ private static void releaseUpload(String uploadId) {
147
+ clearTempBody(uploadId);
148
+ OWNED_UPLOAD_IDS.remove(uploadId);
149
+ }
158
150
 
159
- @Override
160
- public void onCompletedWhileNotObserving() {
161
- replayPendingEvents();
162
- }
151
+ private static void ensureProcessObserver(Application application) {
152
+ synchronized (OBSERVER_LOCK) {
153
+ if (processObserver != null) {
154
+ return;
155
+ }
156
+ if (processUploader == null) {
157
+ processUploader = new Uploader(application);
163
158
  }
164
- );
159
+ // Keep observer for process lifetime. Weak plugin ref avoids retaining Bridge/WebView
160
+ // after Activity destroy; register once to avoid duplicate receivers.
161
+ processObserver = new GlobalRequestObserver(
162
+ application,
163
+ new RequestObserverDelegate() {
164
+ @Override
165
+ public void onProgress(Context context, UploadInfo uploadInfo) {
166
+ JSObject event = new JSObject();
167
+ event.put("name", "uploading");
168
+ JSObject payload = new JSObject();
169
+ payload.put("percent", uploadInfo.getProgressPercent());
170
+ event.put("payload", payload);
171
+ event.put("id", uploadInfo.getUploadId());
172
+ emitEvent("events", event, false);
173
+ }
174
+
175
+ @Override
176
+ public void onSuccess(Context context, UploadInfo uploadInfo, ServerResponse serverResponse) {
177
+ clearTempBody(uploadInfo.getUploadId());
178
+ JSObject event = new JSObject();
179
+ event.put("name", "completed");
180
+ JSObject payload = new JSObject();
181
+ payload.put("statusCode", serverResponse.getCode());
182
+ event.put("payload", payload);
183
+ event.put("id", uploadInfo.getUploadId());
184
+ String eventId = UUID.randomUUID().toString();
185
+ event.put("eventId", eventId);
186
+ saveEventToPrefs(context, eventId, event);
187
+ emitEvent("events", event, true);
188
+ }
189
+
190
+ @Override
191
+ public void onError(Context context, UploadInfo uploadInfo, Throwable exception) {
192
+ clearTempBody(uploadInfo.getUploadId());
193
+ JSObject event = new JSObject();
194
+ event.put("name", "failed");
195
+ JSObject payload = new JSObject();
196
+ payload.put("error", exception.getMessage());
197
+ event.put("payload", payload);
198
+ event.put("id", uploadInfo.getUploadId());
199
+ String eventId = UUID.randomUUID().toString();
200
+ event.put("eventId", eventId);
201
+ saveEventToPrefs(context, eventId, event);
202
+ emitEvent("events", event, true);
203
+ }
204
+
205
+ @Override
206
+ public void onCompleted(Context context, UploadInfo uploadInfo) {
207
+ releaseUpload(uploadInfo.getUploadId());
208
+ JSObject event = new JSObject();
209
+ event.put("name", "finished");
210
+ event.put("id", uploadInfo.getUploadId());
211
+ emitEvent("events", event, false);
212
+ }
213
+
214
+ @Override
215
+ public void onCompletedWhileNotObserving() {
216
+ if (!pluginAttached) {
217
+ return;
218
+ }
219
+ UploaderPlugin plugin = currentPlugin();
220
+ if (plugin != null && plugin.getBridge() != null) {
221
+ plugin.replayPendingEvents();
222
+ }
223
+ }
224
+ },
225
+ (Function1<UploadInfo, Boolean>) (uploadInfo) -> OWNED_UPLOAD_IDS.contains(uploadInfo.getUploadId())
226
+ );
227
+ }
228
+ }
165
229
 
166
- implementation = new Uploader(getContext().getApplicationContext());
230
+ @Override
231
+ public void load() {
232
+ createNotificationChannel();
233
+ activePlugin = new WeakReference<>(this);
234
+ pluginAttached = true;
235
+ ensureProcessObserver(getActivity().getApplication());
236
+ implementation = processUploader;
167
237
  replayPendingEvents();
168
238
  }
169
239
 
240
+ @Override
241
+ protected void handleOnDestroy() {
242
+ // Detach the plugin sink only — keep processObserver registered for background uploads.
243
+ if (activePlugin.get() == this) {
244
+ pluginAttached = false;
245
+ activePlugin.clear();
246
+ }
247
+ super.handleOnDestroy();
248
+ }
249
+
170
250
  public static String getMimeType(String url) {
171
251
  String type = null;
172
252
  String extension = MimeTypeMap.getFileExtensionFromUrl(url);
@@ -250,6 +330,7 @@ public class UploaderPlugin extends Plugin {
250
330
  maxRetries,
251
331
  uploadType
252
332
  );
333
+ OWNED_UPLOAD_IDS.add(id);
253
334
  JSObject result = new JSObject();
254
335
  result.put("id", id);
255
336
  call.resolve(result);
@@ -290,6 +371,7 @@ public class UploaderPlugin extends Plugin {
290
371
  filesToUpload.add(new Uploader.UploadFile(localFilePath, fieldName, mimeType));
291
372
 
292
373
  String id = implementation.startUpload(filesToUpload, serverUrl, headers, fields, "POST", "File Upload", 2, "multipart");
374
+ OWNED_UPLOAD_IDS.add(id);
293
375
  JSObject result = new JSObject();
294
376
  result.put("id", id);
295
377
  call.resolve(result);
@@ -306,6 +388,7 @@ public class UploaderPlugin extends Plugin {
306
388
  return;
307
389
  }
308
390
  try {
391
+ OWNED_UPLOAD_IDS.remove(id);
309
392
  implementation.removeUpload(id);
310
393
  call.resolve();
311
394
  } catch (Exception e) {
@@ -369,7 +452,7 @@ public class UploaderPlugin extends Plugin {
369
452
  call.reject("Missing required parameter: eventId");
370
453
  return;
371
454
  }
372
- removeEventFromPrefs(eventId);
455
+ removeEventFromPrefs(getContext(), eventId);
373
456
  call.resolve();
374
457
  }
375
458
 
@@ -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.3.7"
6
+ private let pluginVersion: String = "8.3.9"
7
7
  public let identifier = "UploaderPlugin"
8
8
  public let jsName = "Uploader"
9
9
  public let pluginMethods: [CAPPluginMethod] = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-uploader",
3
- "version": "8.3.7",
3
+ "version": "8.3.9",
4
4
  "description": "Upload file natively",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",