@cappitolian/http-local-server-swifter 0.0.13 → 0.0.15

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.
@@ -27,26 +27,20 @@ import java.util.concurrent.TimeoutException;
27
27
 
28
28
  import fi.iki.elonen.NanoHTTPD;
29
29
 
30
- /**
31
- * Local HTTP server implementation for Android using NanoHTTPD.
32
- * Handles incoming HTTP requests and communicates with JavaScript layer.
33
- */
34
30
  public class HttpLocalServerSwifter {
35
- // MARK: - Constants
36
31
  private static final String TAG = "HttpLocalServerSwifter";
37
32
  private static final int DEFAULT_PORT = 8080;
38
33
  private static final int DEFAULT_TIMEOUT_SECONDS = 5;
39
34
  private static final String FALLBACK_IP = "127.0.0.1";
40
35
 
41
- // MARK: - Properties
42
36
  private LocalNanoServer server;
43
37
  private final Plugin plugin;
44
38
  private final int port;
45
39
  private final int timeoutSeconds;
46
40
 
41
+ // Changed to String to transport the full JSON response from JS
47
42
  private static final ConcurrentHashMap<String, CompletableFuture<String>> pendingResponses = new ConcurrentHashMap<>();
48
43
 
49
- // MARK: - Constructor
50
44
  public HttpLocalServerSwifter(@NonNull Plugin plugin) {
51
45
  this(plugin, DEFAULT_PORT, DEFAULT_TIMEOUT_SECONDS);
52
46
  }
@@ -57,7 +51,6 @@ public class HttpLocalServerSwifter {
57
51
  this.timeoutSeconds = timeoutSeconds;
58
52
  }
59
53
 
60
- // MARK: - Public Methods
61
54
  public void connect(@NonNull PluginCall call) {
62
55
  if (server != null && server.isAlive()) {
63
56
  call.reject("Server is already running");
@@ -85,72 +78,37 @@ public class HttpLocalServerSwifter {
85
78
  if (server != null) {
86
79
  server.stop();
87
80
  server = null;
88
-
89
- // Clear all pending responses
90
81
  pendingResponses.clear();
91
-
92
82
  Log.i(TAG, "Server stopped");
93
83
  }
94
-
95
- if (call != null) {
96
- call.resolve();
97
- }
84
+ if (call != null) call.resolve();
98
85
  }
99
86
 
100
- // MARK: - Static Methods
101
87
  /**
102
- * Called by plugin when JavaScript responds to a request
103
- */
104
- public static void handleJsResponse(@NonNull String requestId, @NonNull String body) {
88
+ * Completes the future with the full JS response object (body, status, headers)
89
+ */
90
+ public static void handleJsResponse(@NonNull String requestId, @NonNull JSObject responseData) {
105
91
  CompletableFuture<String> future = pendingResponses.remove(requestId);
106
92
  if (future != null && !future.isDone()) {
107
- future.complete(body);
108
- Log.d(TAG, "Response received for request: " + requestId);
109
- } else {
110
- Log.w(TAG, "No pending request found for ID: " + requestId);
93
+ future.complete(responseData.toString());
94
+ Log.d(TAG, "Response object delivered to future for ID: " + requestId);
111
95
  }
112
96
  }
113
-
114
- // MARK: - Private Methods
115
- /**
116
- * Get the local WiFi IP address
117
- */
118
- @NonNull
119
- private String getLocalIpAddress(@NonNull Context context) {
97
+
98
+ private @NonNull String getLocalIpAddress(@NonNull Context context) {
120
99
  try {
121
- WifiManager wifiManager = (WifiManager) context.getApplicationContext()
122
- .getSystemService(Context.WIFI_SERVICE);
123
-
124
- if (wifiManager == null) {
125
- Log.w(TAG, "WifiManager is null, using fallback IP");
126
- return FALLBACK_IP;
127
- }
128
-
100
+ WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
101
+ if (wifiManager == null) return FALLBACK_IP;
129
102
  WifiInfo wifiInfo = wifiManager.getConnectionInfo();
130
- if (wifiInfo == null) {
131
- Log.w(TAG, "WifiInfo is null, using fallback IP");
132
- return FALLBACK_IP;
133
- }
134
-
103
+ if (wifiInfo == null) return FALLBACK_IP;
135
104
  int ipAddress = wifiInfo.getIpAddress();
136
- if (ipAddress == 0) {
137
- Log.w(TAG, "IP address is 0, using fallback IP");
138
- return FALLBACK_IP;
139
- }
140
-
141
- return Formatter.formatIpAddress(ipAddress);
105
+ return ipAddress == 0 ? FALLBACK_IP : Formatter.formatIpAddress(ipAddress);
142
106
  } catch (Exception e) {
143
- Log.e(TAG, "Error getting IP address", e);
144
107
  return FALLBACK_IP;
145
108
  }
146
109
  }
147
-
148
- // MARK: - Inner Class: LocalNanoServer
149
- /**
150
- * NanoHTTPD server implementation that handles HTTP requests
151
- */
110
+
152
111
  private static class LocalNanoServer extends NanoHTTPD {
153
-
154
112
  private final Plugin plugin;
155
113
  private final int timeoutSeconds;
156
114
 
@@ -162,204 +120,109 @@ public class HttpLocalServerSwifter {
162
120
 
163
121
  @Override
164
122
  public Response serve(@NonNull IHTTPSession session) {
165
- String method = session.getMethod().name();
166
- String path = session.getUri();
167
-
168
- // Handle CORS preflight
123
+ // Native CORS Preflight handling for efficiency
169
124
  if (Method.OPTIONS.equals(session.getMethod())) {
170
125
  return createCorsResponse();
171
126
  }
172
127
 
173
128
  try {
174
- // Extract request data
129
+ String method = session.getMethod().name();
130
+ String path = session.getUri();
175
131
  String body = extractBody(session);
176
132
  Map<String, String> headers = session.getHeaders();
177
133
  Map<String, String> params = session.getParms();
178
134
 
179
- // Process request
180
- String responseBody = processRequest(method, path, body, headers, params);
181
-
182
- return createJsonResponse(responseBody, Response.Status.OK);
135
+ // Wait for TypeScript to process logic and provide the complex response
136
+ String jsResponseRaw = processRequest(method, path, body, headers, params);
137
+ return createDynamicResponse(jsResponseRaw);
183
138
  } catch (Exception e) {
184
- Log.e(TAG, "Error processing request", e);
185
- return createErrorResponse("Internal server error: " + e.getMessage(),
186
- Response.Status.INTERNAL_ERROR);
139
+ return createErrorResponse("Internal server error: " + e.getMessage(), Response.Status.INTERNAL_ERROR);
187
140
  }
188
141
  }
189
-
142
+
190
143
  /**
191
- * Extract body from POST/PUT/PATCH requests
192
- */
193
- @Nullable
144
+ * Parses the JSON from JS and builds a NanoHTTPD Response with custom status and headers
145
+ */
146
+ private Response createDynamicResponse(String jsResponseRaw) {
147
+ try {
148
+ JSONObject res = new JSONObject(jsResponseRaw);
149
+ String body = res.optString("body", "");
150
+ int statusCode = res.optInt("status", 200);
151
+ JSONObject customHeaders = res.optJSONObject("headers");
152
+
153
+ Response.IStatus status = Response.Status.lookup(statusCode);
154
+ Response response = newFixedLengthResponse(status != null ? status : Response.Status.OK, "application/json", body);
155
+
156
+ // Add standard CORS headers
157
+ addCorsHeaders(response);
158
+
159
+ // Inject custom headers from TypeScript (allows overriding CORS or Content-Type)
160
+ if (customHeaders != null) {
161
+ java.util.Iterator<String> keys = customHeaders.keys();
162
+ while (keys.hasNext()) {
163
+ String key = keys.next();
164
+ response.addHeader(key, customHeaders.getString(key));
165
+ }
166
+ }
167
+ return response;
168
+ } catch (Exception e) {
169
+ // Fallback for simple string responses or parsing errors
170
+ return newFixedLengthResponse(Response.Status.OK, "application/json", jsResponseRaw);
171
+ }
172
+ }
173
+
194
174
  private String extractBody(@NonNull IHTTPSession session) {
195
175
  Method method = session.getMethod();
196
-
197
- if (method != Method.POST && method != Method.PUT && method != Method.PATCH) {
198
- return null;
199
- }
200
-
176
+ if (method != Method.POST && method != Method.PUT && method != Method.PATCH) return null;
201
177
  try {
202
178
  HashMap<String, String> files = new HashMap<>();
203
179
  session.parseBody(files);
204
-
205
- // Body comes in the map with key "postData"
206
180
  String body = files.get("postData");
207
-
208
- // Fallback to query parameters for form-data
209
- if (body == null || body.isEmpty()) {
210
- body = session.getQueryParameterString();
211
- }
212
-
213
- Log.d(TAG, "Body received (" + body.length() + " bytes): " +
214
- (body != null ? body.substring(0, Math.min(body.length(), 100)) : "null"));
215
-
216
- return body;
181
+ return (body == null || body.isEmpty()) ? session.getQueryParameterString() : body;
217
182
  } catch (IOException | ResponseException e) {
218
- Log.e(TAG, "Error parsing body", e);
219
183
  return null;
220
184
  }
221
185
  }
222
186
 
223
- /**
224
- * Process the request and wait for JavaScript response
225
- */
226
- @NonNull
227
- private String processRequest(@NonNull String method, @NonNull String path,
228
- @Nullable String body, @NonNull Map<String, String> headers,
229
- @NonNull Map<String, String> params) {
230
-
187
+ private String processRequest(String method, String path, String body, Map<String, String> headers, Map<String, String> params) {
231
188
  String requestId = UUID.randomUUID().toString();
232
-
233
- // Build request data for JavaScript
234
189
  JSObject requestData = new JSObject();
235
190
  requestData.put("requestId", requestId);
236
191
  requestData.put("method", method);
237
192
  requestData.put("path", path);
193
+ if (body != null) requestData.put("body", body);
238
194
 
239
- if (body != null && !body.isEmpty()) {
240
- requestData.put("body", body);
241
- }
242
-
243
- if (!headers.isEmpty()) {
244
- requestData.put("headers", mapToJson(headers));
245
- }
246
-
247
- if (!params.isEmpty()) {
248
- requestData.put("query", mapToJson(params));
249
- }
250
-
251
- // Create future for response
252
195
  CompletableFuture<String> future = new CompletableFuture<>();
253
196
  pendingResponses.put(requestId, future);
254
197
 
255
- // Notify plugin
256
198
  if (plugin instanceof HttpLocalServerSwifterPlugin) {
257
199
  ((HttpLocalServerSwifterPlugin) plugin).fireOnRequest(requestData);
258
- } else {
259
- Log.e(TAG, "Plugin is not instance of HttpLocalServerSwifterPlugin");
260
200
  }
261
201
 
262
- // Wait for JavaScript response
263
202
  try {
264
- String response = future.get(timeoutSeconds, TimeUnit.SECONDS);
265
- Log.d(TAG, "Response received for request: " + requestId);
266
- return response;
267
- } catch (TimeoutException e) {
268
- Log.w(TAG, "Timeout waiting for response: " + requestId);
269
- return createTimeoutError(requestId);
203
+ return future.get(timeoutSeconds, TimeUnit.SECONDS);
270
204
  } catch (Exception e) {
271
- Log.e(TAG, "Error waiting for response: " + requestId, e);
272
- return createGenericError("Error waiting for response");
205
+ return "{\"error\":\"Timeout or processing error\"}";
273
206
  } finally {
274
207
  pendingResponses.remove(requestId);
275
208
  }
276
209
  }
277
-
278
- /**
279
- * Convert Map to JSObject
280
- */
281
- @NonNull
282
- private JSObject mapToJson(@NonNull Map<String, String> map) {
283
- JSObject json = new JSObject();
284
- for (Map.Entry<String, String> entry : map.entrySet()) {
285
- json.put(entry.getKey(), entry.getValue());
286
- }
287
- return json;
288
- }
289
-
290
- /**
291
- * Create JSON response with CORS headers
292
- */
293
- @NonNull
294
- private Response createJsonResponse(@NonNull String body, @NonNull Response.Status status) {
295
- Response response = newFixedLengthResponse(status, "application/json", body);
296
- addCorsHeaders(response);
297
- return response;
298
- }
299
-
300
- /**
301
- * Create CORS preflight response
302
- */
303
- @NonNull
210
+
304
211
  private Response createCorsResponse() {
305
- Response response = newFixedLengthResponse(Response.Status.NO_CONTENT,
306
- "text/plain", "");
212
+ Response response = newFixedLengthResponse(Response.Status.NO_CONTENT, "text/plain", "");
307
213
  addCorsHeaders(response);
308
214
  return response;
309
215
  }
310
-
311
- /**
312
- * Create error response
313
- */
314
- @NonNull
315
- private Response createErrorResponse(@NonNull String message, @NonNull Response.Status status) {
316
- try {
317
- JSONObject error = new JSONObject();
318
- error.put("error", message);
319
- return createJsonResponse(error.toString(), status);
320
- } catch (JSONException e) {
321
- return newFixedLengthResponse(status, "text/plain", message);
322
- }
323
- }
324
-
325
- /**
326
- * Create timeout error JSON
327
- */
328
- @NonNull
329
- private String createTimeoutError(@NonNull String requestId) {
330
- try {
331
- JSONObject error = new JSONObject();
332
- error.put("error", "Request timeout");
333
- error.put("requestId", requestId);
334
- return error.toString();
335
- } catch (JSONException e) {
336
- return "{\"error\":\"Request timeout\"}";
337
- }
338
- }
339
-
340
- /**
341
- * Create generic error JSON
342
- */
343
- @NonNull
344
- private String createGenericError(@NonNull String message) {
345
- try {
346
- JSONObject error = new JSONObject();
347
- error.put("error", message);
348
- return error.toString();
349
- } catch (JSONException e) {
350
- return "{\"error\":\"" + message + "\"}";
351
- }
352
- }
353
-
354
- /**
355
- * Add CORS headers to response
356
- */
216
+
357
217
  private void addCorsHeaders(@NonNull Response response) {
358
218
  response.addHeader("Access-Control-Allow-Origin", "*");
359
219
  response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
360
- response.addHeader("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization");
361
- response.addHeader("Access-Control-Allow-Credentials", "true");
220
+ response.addHeader("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization, X-Requested-With");
362
221
  response.addHeader("Access-Control-Max-Age", "3600");
363
222
  }
223
+
224
+ private Response createErrorResponse(String message, Response.Status status) {
225
+ return newFixedLengthResponse(status, "application/json", "{\"error\":\"" + message + "\"}");
226
+ }
364
227
  }
365
228
  }
@@ -1,9 +1,5 @@
1
1
  package com.cappitolian.plugins.httplocalserviceswifter;
2
2
 
3
- import android.util.Log;
4
-
5
- import androidx.annotation.NonNull;
6
-
7
3
  import com.getcapacitor.JSObject;
8
4
  import com.getcapacitor.Plugin;
9
5
  import com.getcapacitor.PluginCall;
@@ -12,69 +8,39 @@ import com.getcapacitor.annotation.CapacitorPlugin;
12
8
 
13
9
  @CapacitorPlugin(name = "HttpLocalServerSwifter")
14
10
  public class HttpLocalServerSwifterPlugin extends Plugin {
15
- private static final String TAG = "HttpLocalServerSwifterPlugin";
16
11
  private HttpLocalServerSwifter localServer;
12
+
17
13
  @Override
18
14
  public void load() {
19
15
  super.load();
20
- // Inicializar el servidor con configuración por defecto
21
16
  localServer = new HttpLocalServerSwifter(this);
22
-
23
- // O con configuración personalizada:
24
- // localServer = new HttpLocalServerSwifter(this, 8080, 5); // puerto y timeout
25
-
26
- Log.d(TAG, "Plugin loaded");
27
17
  }
28
18
 
29
19
  @PluginMethod
30
20
  public void connect(PluginCall call) {
31
- if (localServer == null) {
32
- localServer = new HttpLocalServerSwifter(this);
33
- }
34
21
  localServer.connect(call);
35
22
  }
36
23
 
37
24
  @PluginMethod
38
25
  public void disconnect(PluginCall call) {
39
- if (localServer != null) {
40
- localServer.disconnect(call);
41
- } else {
42
- call.resolve();
43
- }
26
+ localServer.disconnect(call);
44
27
  }
45
28
 
46
29
  @PluginMethod
47
30
  public void sendResponse(PluginCall call) {
48
31
  String requestId = call.getString("requestId");
49
- String body = call.getString("body");
50
-
51
32
  if (requestId == null || requestId.isEmpty()) {
52
33
  call.reject("Missing requestId");
53
34
  return;
54
35
  }
55
36
 
56
- if (body == null || body.isEmpty()) {
57
- call.reject("Missing body");
58
- return;
59
- }
60
-
61
- HttpLocalServerSwifter.handleJsResponse(requestId, body);
37
+ // Pass the entire PluginCall data object to handleJsResponse
38
+ // This allows us to capture 'status' and 'headers' along with the 'body'
39
+ HttpLocalServerSwifter.handleJsResponse(requestId, call.getData());
62
40
  call.resolve();
63
41
  }
64
42
 
65
- /**
66
- * Called by HttpLocalServerSwifter to notify JavaScript of incoming requests
67
- */
68
- public void fireOnRequest(@NonNull JSObject data) {
43
+ public void fireOnRequest(JSObject data) {
69
44
  notifyListeners("onRequest", data);
70
45
  }
71
-
72
- @Override
73
- protected void handleOnDestroy() {
74
- if (localServer != null) {
75
- localServer.disconnect(null);
76
- localServer = null;
77
- }
78
- super.handleOnDestroy();
79
- }
80
46
  }