@cappitolian/http-local-server-swifter 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,18 @@
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 = 'CappitolianHttpLocalServerSwifter'
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 = '15.0'
15
+ s.dependency 'Capacitor'
16
+ s.dependency 'Swifter', '~> 1.5.0'
17
+ s.swift_version = '5.1'
18
+ end
package/Package.swift ADDED
@@ -0,0 +1,30 @@
1
+ // swift-tools-version: 5.9
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "CappitolianHttpLocalServerSwifter",
6
+ platforms: [.iOS(.v15)],
7
+ products: [
8
+ .library(
9
+ name: "CappitolianHttpLocalServerSwifter",
10
+ targets: ["HttpLocalServerSwifterPlugin"])
11
+ ],
12
+ dependencies: [
13
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"),
14
+ .package(url: "https://github.com/httpswift/swifter.git", from: "1.5.0")
15
+ ],
16
+ targets: [
17
+ .target(
18
+ name: "HttpLocalServerSwifterPlugin",
19
+ dependencies: [
20
+ .product(name: "Capacitor", package: "capacitor-swift-pm"),
21
+ .product(name: "Cordova", package: "capacitor-swift-pm"),
22
+ .product(name: "Swifter", package: "swifter")
23
+ ],
24
+ path: "ios/Sources/HttpLocalServerSwifterPlugin"),
25
+ .testTarget(
26
+ name: "HttpLocalServerSwifterPluginTests",
27
+ dependencies: ["HttpLocalServerSwifterPlugin"],
28
+ path: "ios/Tests/HttpLocalServerSwifterPluginTests")
29
+ ]
30
+ )
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # @cappitolian/http-local-server
2
+
3
+ A Capacitor plugin to run a local HTTP server on your device, allowing you to receive and respond to HTTP requests directly from Angular/JavaScript.
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - ✅ Embed a real HTTP server (NanoHTTPD on Android, **Swifter** on iOS)
10
+ - ✅ Receive requests via events and send responses back from the JS layer
11
+ - ✅ CORS support enabled by default for local communication
12
+ - ✅ Support for all HTTP methods (GET, POST, PUT, PATCH, DELETE, OPTIONS)
13
+ - ✅ Swift Package Manager (SPM) support
14
+ - ✅ Tested with **Capacitor 8** and **Ionic 8**
15
+
16
+ ---
17
+
18
+ ## Installation
19
+ ```bash
20
+ npm install @cappitolian/http-local-server
21
+ npx cap sync
22
+ ```
23
+
24
+ ---
25
+
26
+ ## Usage
27
+
28
+ ### Import
29
+ ```typescript
30
+ import { HttpLocalServerSwifter } from '@cappitolian/http-local-server';
31
+ ```
32
+
33
+ ### Listen and Respond
34
+ ```typescript
35
+ // 1. Set up the listener for incoming requests
36
+ await HttpLocalServerSwifter.addListener('onRequest', async (data) => {
37
+ console.log(`${data.method} ${data.path}`);
38
+ console.log('Body:', data.body);
39
+ console.log('Headers:', data.headers);
40
+ console.log('Query:', data.query);
41
+
42
+ // 2. Send a response back to the client using the requestId
43
+ await HttpLocalServerSwifter.sendResponse({
44
+ requestId: data.requestId,
45
+ body: JSON.stringify({
46
+ success: true,
47
+ message: 'Request processed!'
48
+ })
49
+ });
50
+ });
51
+
52
+ // 3. Start the server
53
+ HttpLocalServerSwifter.connect().then(result => {
54
+ console.log('Server running at:', result.ip, 'Port:', result.port);
55
+ });
56
+ ```
57
+
58
+ ### Stop Server
59
+ ```typescript
60
+ // 4. Stop the server
61
+ await HttpLocalServerSwifter.disconnect();
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Platforms
67
+
68
+ - **iOS** (Swift with Swifter)
69
+ - **Android** (Java with NanoHTTPD)
70
+ - **Web** (Returns mock values for development)
71
+
72
+ ---
73
+
74
+ ## Requirements
75
+
76
+ - [Capacitor 8](https://capacitorjs.com/)
77
+ - iOS 13.0+
78
+ - Android API 22+
79
+
80
+ ---
81
+
82
+ ## Migration from v0.0.x
83
+
84
+ Version 0.1.0 migrates from GCDWebServer to Swifter on iOS for better Swift Package Manager support. The API remains the same, so no code changes are needed in your app.
85
+
86
+ ---
87
+
88
+ ## License
89
+
90
+ MIT
91
+
92
+ ---
93
+
94
+ ## Support
95
+
96
+ If you have any issues or feature requests, please open an issue on the repository.
97
+ ```
98
+
99
+ ---
100
+
101
+ ## 📋 Cambios Principales
102
+
103
+ ### **GCDWebServer → Swifter**
104
+
105
+ | Aspecto | GCDWebServer | Swifter |
106
+ |---------|--------------|---------|
107
+ | **Importar** | `import GCDWebServer` | `import Swifter` |
108
+ | **Crear servidor** | `GCDWebServer()` | `HttpServer()` |
109
+ | **Tipo de puerto** | `UInt` | `UInt16` |
110
+ | **Handlers** | `.addDefaultHandler(forMethod:)` | `server["/(.*)"] = { ... }` |
111
+ | **Request type** | `GCDWebServerRequest` | `HttpRequest` |
112
+ | **Response type** | `GCDWebServerDataResponse` | `HttpResponse` |
113
+ | **Método HTTP** | `request.method` | `request.method` (igual) |
114
+ | **Path** | `request.url.path` | `request.path` |
115
+ | **Body** | `(request as? GCDWebServerDataRequest)?.data` | `request.body` (bytes) |
116
+ | **Headers** | `request.headers` | `request.headers` (igual) |
117
+ | **Query params** | `request.query` | `request.queryParams` |
118
+ | **Start server** | `try server.start(options:)` | `try server.start(port)` |
119
+ | **Stop server** | `server.stop()` | `server.stop()` (igual) |
120
+ | **Response** | `GCDWebServerDataResponse(text:)` | `.ok(.text())` |
121
+ | **CORS headers** | `setValue(_:forAdditionalHeader:)` | Custom extension |
122
+
123
+ ---
124
+
125
+ ## ✅ Pasos para Aplicar
126
+
127
+ 1. **Reemplaza `HttpLocalServerSwifter.swift`** con la versión de arriba
128
+ 2. **Actualiza `Package.swift`**
129
+ 3. **Actualiza `.podspec`**
130
+ 4. **Actualiza `package.json`** (versiones de Capacitor 8)
131
+ 5. **En Xcode**:
132
+ ```
133
+ File → Packages → Reset Package Caches
134
+ File → Packages → Resolve Package Versions
135
+ Product → Clean Build Folder
@@ -0,0 +1,58 @@
1
+ ext {
2
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
4
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
5
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
6
+ }
7
+
8
+ buildscript {
9
+ repositories {
10
+ google()
11
+ mavenCentral()
12
+ }
13
+ dependencies {
14
+ classpath 'com.android.tools.build:gradle:8.13.0'
15
+ }
16
+ }
17
+
18
+ apply plugin: 'com.android.library'
19
+
20
+ android {
21
+ namespace = "com.cappitolian.plugins.httplocalserviceswifter"
22
+ compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
23
+ defaultConfig {
24
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
25
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
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_21
41
+ targetCompatibility JavaVersion.VERSION_21
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
+ testImplementation "junit:junit:$junitVersion"
56
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
57
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
58
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,365 @@
1
+ package com.cappitolian.plugins.httplocalserviceswifter;
2
+
3
+ import android.content.Context;
4
+ import android.net.wifi.WifiInfo;
5
+ import android.net.wifi.WifiManager;
6
+ import android.text.format.Formatter;
7
+ import android.util.Log;
8
+
9
+ import androidx.annotation.NonNull;
10
+ import androidx.annotation.Nullable;
11
+
12
+ import com.getcapacitor.JSObject;
13
+ import com.getcapacitor.Plugin;
14
+ import com.getcapacitor.PluginCall;
15
+
16
+ import org.json.JSONException;
17
+ import org.json.JSONObject;
18
+
19
+ import java.io.IOException;
20
+ import java.util.HashMap;
21
+ import java.util.Map;
22
+ import java.util.UUID;
23
+ import java.util.concurrent.CompletableFuture;
24
+ import java.util.concurrent.ConcurrentHashMap;
25
+ import java.util.concurrent.TimeUnit;
26
+ import java.util.concurrent.TimeoutException;
27
+
28
+ import fi.iki.elonen.NanoHTTPD;
29
+
30
+ /**
31
+ * Local HTTP server implementation for Android using NanoHTTPD.
32
+ * Handles incoming HTTP requests and communicates with JavaScript layer.
33
+ */
34
+ public class HttpLocalServerSwifter {
35
+ // MARK: - Constants
36
+ private static final String TAG = "HttpLocalServerSwifter";
37
+ private static final int DEFAULT_PORT = 8080;
38
+ private static final int DEFAULT_TIMEOUT_SECONDS = 5;
39
+ private static final String FALLBACK_IP = "127.0.0.1";
40
+
41
+ // MARK: - Properties
42
+ private LocalNanoServer server;
43
+ private final Plugin plugin;
44
+ private final int port;
45
+ private final int timeoutSeconds;
46
+
47
+ private static final ConcurrentHashMap<String, CompletableFuture<String>> pendingResponses = new ConcurrentHashMap<>();
48
+
49
+ // MARK: - Constructor
50
+ public HttpLocalServerSwifter(@NonNull Plugin plugin) {
51
+ this(plugin, DEFAULT_PORT, DEFAULT_TIMEOUT_SECONDS);
52
+ }
53
+
54
+ public HttpLocalServerSwifter(@NonNull Plugin plugin, int port, int timeoutSeconds) {
55
+ this.plugin = plugin;
56
+ this.port = port;
57
+ this.timeoutSeconds = timeoutSeconds;
58
+ }
59
+
60
+ // MARK: - Public Methods
61
+ public void connect(@NonNull PluginCall call) {
62
+ if (server != null && server.isAlive()) {
63
+ call.reject("Server is already running");
64
+ return;
65
+ }
66
+
67
+ try {
68
+ String localIp = getLocalIpAddress(plugin.getContext());
69
+ server = new LocalNanoServer(localIp, port, plugin, timeoutSeconds);
70
+ server.start();
71
+
72
+ JSObject response = new JSObject();
73
+ response.put("ip", localIp);
74
+ response.put("port", port);
75
+ call.resolve(response);
76
+
77
+ Log.i(TAG, "Server started at " + localIp + ":" + port);
78
+ } catch (IOException e) {
79
+ Log.e(TAG, "Failed to start server", e);
80
+ call.reject("Failed to start server: " + e.getMessage());
81
+ }
82
+ }
83
+
84
+ public void disconnect(@Nullable PluginCall call) {
85
+ if (server != null) {
86
+ server.stop();
87
+ server = null;
88
+
89
+ // Clear all pending responses
90
+ pendingResponses.clear();
91
+
92
+ Log.i(TAG, "Server stopped");
93
+ }
94
+
95
+ if (call != null) {
96
+ call.resolve();
97
+ }
98
+ }
99
+
100
+ // MARK: - Static Methods
101
+ /**
102
+ * Called by plugin when JavaScript responds to a request
103
+ */
104
+ public static void handleJsResponse(@NonNull String requestId, @NonNull String body) {
105
+ CompletableFuture<String> future = pendingResponses.remove(requestId);
106
+ 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);
111
+ }
112
+ }
113
+
114
+ // MARK: - Private Methods
115
+ /**
116
+ * Get the local WiFi IP address
117
+ */
118
+ @NonNull
119
+ private String getLocalIpAddress(@NonNull Context context) {
120
+ 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
+
129
+ WifiInfo wifiInfo = wifiManager.getConnectionInfo();
130
+ if (wifiInfo == null) {
131
+ Log.w(TAG, "WifiInfo is null, using fallback IP");
132
+ return FALLBACK_IP;
133
+ }
134
+
135
+ 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);
142
+ } catch (Exception e) {
143
+ Log.e(TAG, "Error getting IP address", e);
144
+ return FALLBACK_IP;
145
+ }
146
+ }
147
+
148
+ // MARK: - Inner Class: LocalNanoServer
149
+ /**
150
+ * NanoHTTPD server implementation that handles HTTP requests
151
+ */
152
+ private static class LocalNanoServer extends NanoHTTPD {
153
+
154
+ private final Plugin plugin;
155
+ private final int timeoutSeconds;
156
+
157
+ public LocalNanoServer(@NonNull String hostname, int port, @NonNull Plugin plugin, int timeoutSeconds) {
158
+ super(hostname, port);
159
+ this.plugin = plugin;
160
+ this.timeoutSeconds = timeoutSeconds;
161
+ }
162
+
163
+ @Override
164
+ public Response serve(@NonNull IHTTPSession session) {
165
+ String method = session.getMethod().name();
166
+ String path = session.getUri();
167
+
168
+ // Handle CORS preflight
169
+ if (Method.OPTIONS.equals(session.getMethod())) {
170
+ return createCorsResponse();
171
+ }
172
+
173
+ try {
174
+ // Extract request data
175
+ String body = extractBody(session);
176
+ Map<String, String> headers = session.getHeaders();
177
+ Map<String, String> params = session.getParms();
178
+
179
+ // Process request
180
+ String responseBody = processRequest(method, path, body, headers, params);
181
+
182
+ return createJsonResponse(responseBody, Response.Status.OK);
183
+ } catch (Exception e) {
184
+ Log.e(TAG, "Error processing request", e);
185
+ return createErrorResponse("Internal server error: " + e.getMessage(),
186
+ Response.Status.INTERNAL_ERROR);
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Extract body from POST/PUT/PATCH requests
192
+ */
193
+ @Nullable
194
+ private String extractBody(@NonNull IHTTPSession session) {
195
+ Method method = session.getMethod();
196
+
197
+ if (method != Method.POST && method != Method.PUT && method != Method.PATCH) {
198
+ return null;
199
+ }
200
+
201
+ try {
202
+ HashMap<String, String> files = new HashMap<>();
203
+ session.parseBody(files);
204
+
205
+ // Body comes in the map with key "postData"
206
+ 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;
217
+ } catch (IOException | ResponseException e) {
218
+ Log.e(TAG, "Error parsing body", e);
219
+ return null;
220
+ }
221
+ }
222
+
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
+
231
+ String requestId = UUID.randomUUID().toString();
232
+
233
+ // Build request data for JavaScript
234
+ JSObject requestData = new JSObject();
235
+ requestData.put("requestId", requestId);
236
+ requestData.put("method", method);
237
+ requestData.put("path", path);
238
+
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
+ CompletableFuture<String> future = new CompletableFuture<>();
253
+ pendingResponses.put(requestId, future);
254
+
255
+ // Notify plugin
256
+ if (plugin instanceof HttpLocalServerSwifterPlugin) {
257
+ ((HttpLocalServerSwifterPlugin) plugin).fireOnRequest(requestData);
258
+ } else {
259
+ Log.e(TAG, "Plugin is not instance of HttpLocalServerSwifterPlugin");
260
+ }
261
+
262
+ // Wait for JavaScript response
263
+ 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);
270
+ } catch (Exception e) {
271
+ Log.e(TAG, "Error waiting for response: " + requestId, e);
272
+ return createGenericError("Error waiting for response");
273
+ } finally {
274
+ pendingResponses.remove(requestId);
275
+ }
276
+ }
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
304
+ private Response createCorsResponse() {
305
+ Response response = newFixedLengthResponse(Response.Status.NO_CONTENT,
306
+ "text/plain", "");
307
+ addCorsHeaders(response);
308
+ return response;
309
+ }
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
+ */
357
+ private void addCorsHeaders(@NonNull Response response) {
358
+ response.addHeader("Access-Control-Allow-Origin", "*");
359
+ 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");
362
+ response.addHeader("Access-Control-Max-Age", "3600");
363
+ }
364
+ }
365
+ }
@@ -0,0 +1,80 @@
1
+ package com.cappitolian.plugins.httplocalserviceswifter;
2
+
3
+ import android.util.Log;
4
+
5
+ import androidx.annotation.NonNull;
6
+
7
+ import com.getcapacitor.JSObject;
8
+ import com.getcapacitor.Plugin;
9
+ import com.getcapacitor.PluginCall;
10
+ import com.getcapacitor.PluginMethod;
11
+ import com.getcapacitor.annotation.CapacitorPlugin;
12
+
13
+ @CapacitorPlugin(name = "HttpLocalServerSwifter")
14
+ public class HttpLocalServerSwifterPlugin extends Plugin {
15
+ private static final String TAG = "HttpLocalServerSwifterPlugin";
16
+ private HttpLocalServerSwifter localServer;
17
+ @Override
18
+ public void load() {
19
+ super.load();
20
+ // Inicializar el servidor con configuración por defecto
21
+ 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
+ }
28
+
29
+ @PluginMethod
30
+ public void connect(PluginCall call) {
31
+ if (localServer == null) {
32
+ localServer = new HttpLocalServerSwifter(this);
33
+ }
34
+ localServer.connect(call);
35
+ }
36
+
37
+ @PluginMethod
38
+ public void disconnect(PluginCall call) {
39
+ if (localServer != null) {
40
+ localServer.disconnect(call);
41
+ } else {
42
+ call.resolve();
43
+ }
44
+ }
45
+
46
+ @PluginMethod
47
+ public void sendResponse(PluginCall call) {
48
+ String requestId = call.getString("requestId");
49
+ String body = call.getString("body");
50
+
51
+ if (requestId == null || requestId.isEmpty()) {
52
+ call.reject("Missing requestId");
53
+ return;
54
+ }
55
+
56
+ if (body == null || body.isEmpty()) {
57
+ call.reject("Missing body");
58
+ return;
59
+ }
60
+
61
+ HttpLocalServerSwifter.handleJsResponse(requestId, body);
62
+ call.resolve();
63
+ }
64
+
65
+ /**
66
+ * Called by HttpLocalServerSwifter to notify JavaScript of incoming requests
67
+ */
68
+ public void fireOnRequest(@NonNull JSObject data) {
69
+ notifyListeners("onRequest", data);
70
+ }
71
+
72
+ @Override
73
+ protected void handleOnDestroy() {
74
+ if (localServer != null) {
75
+ localServer.disconnect(null);
76
+ localServer = null;
77
+ }
78
+ super.handleOnDestroy();
79
+ }
80
+ }
File without changes