@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.
- package/CappitolianHttpLocalServerSwifter.podspec +18 -0
- package/Package.swift +30 -0
- package/README.md +135 -0
- package/android/build.gradle +58 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/cappitolian/plugins/httplocalserviceswifter/HttpLocalServerSwifter.java +365 -0
- package/android/src/main/java/com/cappitolian/plugins/httplocalserviceswifter/HttpLocalServerSwifterPlugin.java +80 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +312 -0
- package/dist/esm/definitions.d.ts +170 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +33 -0
- package/dist/esm/index.js +36 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +14 -0
- package/dist/esm/web.js +49 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +92 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +95 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/HttpLocalServerSwifterPlugin/HttpLocalServerSwifter.swift +406 -0
- package/ios/Sources/HttpLocalServerSwifterPlugin/HttpLocalServerSwifterPlugin.swift +68 -0
- package/ios/Tests/HttpLocalServerSwifterPluginTests/HttpLocalServerSwifterTests.swift +15 -0
- package/package.json +80 -0
|
@@ -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,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
|