@cappitolian/http-local-server-swifter 0.0.29 → 0.0.31
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/android/src/main/java/com/cappitolian/plugins/httplocalserviceswifter/HttpLocalServerSwifter.java +113 -35
- package/android/src/main/java/com/cappitolian/plugins/httplocalserviceswifter/HttpLocalServerSwifterPlugin.java +4 -1
- package/ios/Sources/HttpLocalServerSwifterPlugin/HttpLocalServerSwifter.swift +50 -3
- package/ios/Sources/HttpLocalServerSwifterPlugin/HttpLocalServerSwifterPlugin.swift +7 -2
- package/package.json +1 -1
|
@@ -13,7 +13,6 @@ import com.getcapacitor.JSObject;
|
|
|
13
13
|
import com.getcapacitor.Plugin;
|
|
14
14
|
import com.getcapacitor.PluginCall;
|
|
15
15
|
|
|
16
|
-
import org.json.JSONException;
|
|
17
16
|
import org.json.JSONObject;
|
|
18
17
|
|
|
19
18
|
import java.io.IOException;
|
|
@@ -23,72 +22,108 @@ import java.util.UUID;
|
|
|
23
22
|
import java.util.concurrent.CompletableFuture;
|
|
24
23
|
import java.util.concurrent.ConcurrentHashMap;
|
|
25
24
|
import java.util.concurrent.TimeUnit;
|
|
26
|
-
import java.util.concurrent.TimeoutException;
|
|
27
25
|
|
|
28
26
|
import fi.iki.elonen.NanoHTTPD;
|
|
29
27
|
|
|
30
28
|
public class HttpLocalServerSwifter {
|
|
29
|
+
// Private static final properties
|
|
31
30
|
private static final String TAG = "HttpLocalServerSwifter";
|
|
32
31
|
private static final int DEFAULT_PORT = 8080;
|
|
33
32
|
private static final int DEFAULT_TIMEOUT_SECONDS = 5;
|
|
34
33
|
private static final String FALLBACK_IP = "127.0.0.1";
|
|
35
|
-
|
|
36
|
-
private
|
|
34
|
+
// Changed to String to transport the full JSON response from JS
|
|
35
|
+
private static final ConcurrentHashMap<String, CompletableFuture<String>> pendingResponses = new ConcurrentHashMap<>();
|
|
36
|
+
// Add inside LocalNanoServer
|
|
37
|
+
private static final ConcurrentHashMap<String, long[]> rateLimitMap = new ConcurrentHashMap<>();
|
|
38
|
+
private static final int RATE_LIMIT = 30; // requests
|
|
39
|
+
private static final long RATE_WINDOW_MS = 60_000; // per minute
|
|
40
|
+
|
|
41
|
+
// Private final properties
|
|
37
42
|
private final Plugin plugin;
|
|
38
43
|
private final int port;
|
|
39
44
|
private final int timeoutSeconds;
|
|
40
|
-
|
|
41
|
-
//
|
|
42
|
-
private
|
|
43
|
-
|
|
45
|
+
|
|
46
|
+
// Private properties
|
|
47
|
+
private LocalNanoServer server;
|
|
48
|
+
|
|
49
|
+
private static boolean isRateLimited(String ip) {
|
|
50
|
+
long now = System.currentTimeMillis();
|
|
51
|
+
|
|
52
|
+
rateLimitMap.compute(ip, (key, timestamps) -> {
|
|
53
|
+
if (timestamps == null)
|
|
54
|
+
timestamps = new long[] { now, 1 };
|
|
55
|
+
else if (now - timestamps[0] > RATE_WINDOW_MS) {
|
|
56
|
+
timestamps[0] = now;
|
|
57
|
+
timestamps[1] = 1;
|
|
58
|
+
} else
|
|
59
|
+
timestamps[1]++;
|
|
60
|
+
|
|
61
|
+
return timestamps;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
long[] entry = rateLimitMap.get(ip);
|
|
65
|
+
|
|
66
|
+
return entry != null && entry[1] > RATE_LIMIT;
|
|
67
|
+
}
|
|
68
|
+
|
|
44
69
|
public HttpLocalServerSwifter(@NonNull Plugin plugin) {
|
|
45
70
|
this(plugin, DEFAULT_PORT, DEFAULT_TIMEOUT_SECONDS);
|
|
46
71
|
}
|
|
47
|
-
|
|
72
|
+
|
|
48
73
|
public HttpLocalServerSwifter(@NonNull Plugin plugin, int port, int timeoutSeconds) {
|
|
49
74
|
this.plugin = plugin;
|
|
50
75
|
this.port = port;
|
|
51
76
|
this.timeoutSeconds = timeoutSeconds;
|
|
52
77
|
}
|
|
53
|
-
|
|
78
|
+
|
|
54
79
|
public void connect(@NonNull PluginCall call) {
|
|
55
80
|
if (server != null && server.isAlive()) {
|
|
56
81
|
call.reject("Server is already running");
|
|
82
|
+
|
|
57
83
|
return;
|
|
58
84
|
}
|
|
59
|
-
|
|
85
|
+
|
|
60
86
|
try {
|
|
61
87
|
String localIp = getLocalIpAddress(plugin.getContext());
|
|
88
|
+
|
|
62
89
|
server = new LocalNanoServer(localIp, port, plugin, timeoutSeconds);
|
|
63
90
|
server.start();
|
|
64
|
-
|
|
91
|
+
|
|
65
92
|
JSObject response = new JSObject();
|
|
93
|
+
|
|
66
94
|
response.put("ip", localIp);
|
|
67
95
|
response.put("port", port);
|
|
96
|
+
|
|
68
97
|
call.resolve(response);
|
|
69
|
-
|
|
98
|
+
|
|
70
99
|
Log.i(TAG, "Server started at " + localIp + ":" + port);
|
|
71
100
|
} catch (IOException e) {
|
|
72
101
|
Log.e(TAG, "Failed to start server", e);
|
|
102
|
+
|
|
73
103
|
call.reject("Failed to start server: " + e.getMessage());
|
|
74
104
|
}
|
|
75
105
|
}
|
|
76
|
-
|
|
106
|
+
|
|
77
107
|
public void disconnect(@Nullable PluginCall call) {
|
|
78
108
|
if (server != null) {
|
|
79
109
|
server.stop();
|
|
110
|
+
|
|
80
111
|
server = null;
|
|
112
|
+
|
|
81
113
|
pendingResponses.clear();
|
|
114
|
+
|
|
82
115
|
Log.i(TAG, "Server stopped");
|
|
83
116
|
}
|
|
84
|
-
if (call != null)
|
|
117
|
+
if (call != null)
|
|
118
|
+
call.resolve();
|
|
85
119
|
}
|
|
86
|
-
|
|
120
|
+
|
|
87
121
|
/**
|
|
88
122
|
* Completes the future with the full JS response object (body, status, headers)
|
|
89
123
|
*/
|
|
90
124
|
public static void handleJsResponse(@NonNull String requestId, @NonNull JSObject responseData) {
|
|
91
125
|
CompletableFuture<String> future = pendingResponses.remove(requestId);
|
|
126
|
+
|
|
92
127
|
if (future != null && !future.isDone()) {
|
|
93
128
|
future.complete(responseData.toString());
|
|
94
129
|
Log.d(TAG, "Response object delivered to future for ID: " + requestId);
|
|
@@ -97,11 +132,19 @@ public class HttpLocalServerSwifter {
|
|
|
97
132
|
|
|
98
133
|
private @NonNull String getLocalIpAddress(@NonNull Context context) {
|
|
99
134
|
try {
|
|
100
|
-
WifiManager wifiManager = (WifiManager) context.getApplicationContext()
|
|
101
|
-
|
|
135
|
+
WifiManager wifiManager = (WifiManager) context.getApplicationContext()
|
|
136
|
+
.getSystemService(Context.WIFI_SERVICE);
|
|
137
|
+
|
|
138
|
+
if (wifiManager == null)
|
|
139
|
+
return FALLBACK_IP;
|
|
140
|
+
|
|
102
141
|
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
|
|
103
|
-
|
|
142
|
+
|
|
143
|
+
if (wifiInfo == null)
|
|
144
|
+
return FALLBACK_IP;
|
|
145
|
+
|
|
104
146
|
int ipAddress = wifiInfo.getIpAddress();
|
|
147
|
+
|
|
105
148
|
return ipAddress == 0 ? FALLBACK_IP : Formatter.formatIpAddress(ipAddress);
|
|
106
149
|
} catch (Exception e) {
|
|
107
150
|
return FALLBACK_IP;
|
|
@@ -111,27 +154,46 @@ public class HttpLocalServerSwifter {
|
|
|
111
154
|
private static class LocalNanoServer extends NanoHTTPD {
|
|
112
155
|
private final Plugin plugin;
|
|
113
156
|
private final int timeoutSeconds;
|
|
114
|
-
|
|
157
|
+
|
|
115
158
|
public LocalNanoServer(@NonNull String hostname, int port, @NonNull Plugin plugin, int timeoutSeconds) {
|
|
116
159
|
super(hostname, port);
|
|
117
160
|
this.plugin = plugin;
|
|
118
161
|
this.timeoutSeconds = timeoutSeconds;
|
|
119
162
|
}
|
|
120
|
-
|
|
163
|
+
|
|
121
164
|
@Override
|
|
122
165
|
public Response serve(@NonNull IHTTPSession session) {
|
|
166
|
+
if (Method.OPTIONS.equals(session.getMethod())) {
|
|
167
|
+
return createCorsResponse();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Rate limiting
|
|
171
|
+
String clientIp = session.getHeaders().getOrDefault("http-client-ip",
|
|
172
|
+
session.getHeaders().getOrDefault("remote-addr", "unknown"));
|
|
173
|
+
|
|
174
|
+
if (isRateLimited(clientIp)) {
|
|
175
|
+
Response r = newFixedLengthResponse(
|
|
176
|
+
Response.Status.lookup(429), "application/json",
|
|
177
|
+
"{\"success\":false,\"error\":\"Too many requests\"}");
|
|
178
|
+
|
|
179
|
+
addCorsHeaders(r);
|
|
180
|
+
|
|
181
|
+
return r;
|
|
182
|
+
}
|
|
183
|
+
|
|
123
184
|
// Native CORS Preflight handling for efficiency
|
|
124
185
|
if (Method.OPTIONS.equals(session.getMethod())) {
|
|
125
186
|
return createCorsResponse();
|
|
126
187
|
}
|
|
127
|
-
|
|
188
|
+
|
|
128
189
|
try {
|
|
129
190
|
String method = session.getMethod().name();
|
|
130
191
|
String path = session.getUri();
|
|
131
192
|
String body = extractBody(session);
|
|
193
|
+
|
|
132
194
|
Map<String, String> headers = session.getHeaders();
|
|
133
195
|
Map<String, String> params = session.getParms();
|
|
134
|
-
|
|
196
|
+
|
|
135
197
|
// Wait for TypeScript to process logic and provide the complex response
|
|
136
198
|
String jsResponseRaw = processRequest(method, path, body, headers, params);
|
|
137
199
|
return createDynamicResponse(jsResponseRaw);
|
|
@@ -141,24 +203,30 @@ public class HttpLocalServerSwifter {
|
|
|
141
203
|
}
|
|
142
204
|
|
|
143
205
|
/**
|
|
144
|
-
* Parses the JSON from JS and builds a NanoHTTPD Response with custom status
|
|
206
|
+
* Parses the JSON from JS and builds a NanoHTTPD Response with custom status
|
|
207
|
+
* and headers
|
|
145
208
|
*/
|
|
146
209
|
private Response createDynamicResponse(String jsResponseRaw) {
|
|
147
210
|
try {
|
|
148
211
|
JSONObject res = new JSONObject(jsResponseRaw);
|
|
149
212
|
String body = res.optString("body", "");
|
|
213
|
+
|
|
150
214
|
int statusCode = res.optInt("status", 200);
|
|
215
|
+
|
|
151
216
|
JSONObject customHeaders = res.optJSONObject("headers");
|
|
152
217
|
|
|
153
218
|
Response.IStatus status = Response.Status.lookup(statusCode);
|
|
154
|
-
Response response = newFixedLengthResponse(status != null ? status : Response.Status.OK,
|
|
155
|
-
|
|
219
|
+
Response response = newFixedLengthResponse(status != null ? status : Response.Status.OK,
|
|
220
|
+
"application/json", body);
|
|
221
|
+
|
|
156
222
|
// Add standard CORS headers
|
|
157
223
|
addCorsHeaders(response);
|
|
158
224
|
|
|
159
|
-
// Inject custom headers from TypeScript (allows overriding CORS or
|
|
225
|
+
// Inject custom headers from TypeScript (allows overriding CORS or
|
|
226
|
+
// Content-Type)
|
|
160
227
|
if (customHeaders != null) {
|
|
161
228
|
java.util.Iterator<String> keys = customHeaders.keys();
|
|
229
|
+
|
|
162
230
|
while (keys.hasNext()) {
|
|
163
231
|
String key = keys.next();
|
|
164
232
|
response.addHeader(key, customHeaders.getString(key));
|
|
@@ -173,32 +241,39 @@ public class HttpLocalServerSwifter {
|
|
|
173
241
|
|
|
174
242
|
private String extractBody(@NonNull IHTTPSession session) {
|
|
175
243
|
Method method = session.getMethod();
|
|
176
|
-
|
|
244
|
+
|
|
245
|
+
if (method != Method.POST && method != Method.PUT && method != Method.PATCH)
|
|
246
|
+
return null;
|
|
247
|
+
|
|
177
248
|
try {
|
|
178
249
|
HashMap<String, String> files = new HashMap<>();
|
|
179
250
|
session.parseBody(files);
|
|
180
251
|
String body = files.get("postData");
|
|
252
|
+
|
|
181
253
|
return (body == null || body.isEmpty()) ? session.getQueryParameterString() : body;
|
|
182
254
|
} catch (IOException | ResponseException e) {
|
|
183
255
|
return null;
|
|
184
256
|
}
|
|
185
257
|
}
|
|
186
|
-
|
|
187
|
-
private String processRequest(String method, String path, String body, Map<String, String> headers,
|
|
258
|
+
|
|
259
|
+
private String processRequest(String method, String path, String body, Map<String, String> headers,
|
|
260
|
+
Map<String, String> params) {
|
|
188
261
|
String requestId = UUID.randomUUID().toString();
|
|
189
262
|
JSObject requestData = new JSObject();
|
|
190
263
|
requestData.put("requestId", requestId);
|
|
191
264
|
requestData.put("method", method);
|
|
192
265
|
requestData.put("path", path);
|
|
193
|
-
|
|
194
|
-
|
|
266
|
+
|
|
267
|
+
if (body != null)
|
|
268
|
+
requestData.put("body", body);
|
|
269
|
+
|
|
195
270
|
CompletableFuture<String> future = new CompletableFuture<>();
|
|
196
271
|
pendingResponses.put(requestId, future);
|
|
197
|
-
|
|
272
|
+
|
|
198
273
|
if (plugin instanceof HttpLocalServerSwifterPlugin) {
|
|
199
274
|
((HttpLocalServerSwifterPlugin) plugin).fireOnRequest(requestData);
|
|
200
275
|
}
|
|
201
|
-
|
|
276
|
+
|
|
202
277
|
try {
|
|
203
278
|
return future.get(timeoutSeconds, TimeUnit.SECONDS);
|
|
204
279
|
} catch (Exception e) {
|
|
@@ -210,14 +285,17 @@ public class HttpLocalServerSwifter {
|
|
|
210
285
|
|
|
211
286
|
private Response createCorsResponse() {
|
|
212
287
|
Response response = newFixedLengthResponse(Response.Status.NO_CONTENT, "text/plain", "");
|
|
288
|
+
|
|
213
289
|
addCorsHeaders(response);
|
|
290
|
+
|
|
214
291
|
return response;
|
|
215
292
|
}
|
|
216
293
|
|
|
217
294
|
private void addCorsHeaders(@NonNull Response response) {
|
|
218
295
|
response.addHeader("Access-Control-Allow-Origin", "*");
|
|
219
296
|
response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
|
|
220
|
-
response.addHeader("Access-Control-Allow-Headers",
|
|
297
|
+
response.addHeader("Access-Control-Allow-Headers",
|
|
298
|
+
"Origin, Content-Type, Accept, Authorization, X-Requested-With");
|
|
221
299
|
response.addHeader("Access-Control-Max-Age", "3600");
|
|
222
300
|
// Prevents TCP connection reuse. NanoHTTPD does not handle keep-alive
|
|
223
301
|
// correctly under rapid sequential requests, causing ERR_INVALID_HTTP_RESPONSE.
|
|
@@ -29,14 +29,17 @@ public class HttpLocalServerSwifterPlugin extends Plugin {
|
|
|
29
29
|
@PluginMethod
|
|
30
30
|
public void sendResponse(PluginCall call) {
|
|
31
31
|
String requestId = call.getString("requestId");
|
|
32
|
+
|
|
32
33
|
if (requestId == null || requestId.isEmpty()) {
|
|
33
34
|
call.reject("Missing requestId");
|
|
35
|
+
|
|
34
36
|
return;
|
|
35
37
|
}
|
|
36
|
-
|
|
38
|
+
|
|
37
39
|
// Pass the entire PluginCall data object to handleJsResponse
|
|
38
40
|
// This allows us to capture 'status' and 'headers' along with the 'body'
|
|
39
41
|
HttpLocalServerSwifter.handleJsResponse(requestId, call.getData());
|
|
42
|
+
|
|
40
43
|
call.resolve();
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -7,20 +7,43 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
@objc public class HttpLocalServerSwifter: NSObject {
|
|
10
|
-
|
|
11
|
-
private weak var delegate: HttpLocalServerSwifterDelegate?
|
|
12
|
-
|
|
10
|
+
// Private static properties
|
|
13
11
|
private static var pendingResponses = [String: (String) -> Void]()
|
|
14
12
|
private static let queue = DispatchQueue(
|
|
15
13
|
label: "com.cappitolian.HttpLocalServerSwifter.pendingResponses",
|
|
16
14
|
qos: .userInitiated
|
|
17
15
|
)
|
|
18
16
|
|
|
17
|
+
// Private properties
|
|
18
|
+
private var webServer: HttpServer?
|
|
19
|
+
private weak var delegate: HttpLocalServerSwifterDelegate?
|
|
19
20
|
private let defaultTimeout: TimeInterval = 10.0
|
|
20
21
|
private let defaultPort: UInt16 = 8080
|
|
21
22
|
|
|
23
|
+
private var rateLimitMap = [String: (start: Date, count: Int)]()
|
|
24
|
+
private let rateLimitQueue = DispatchQueue(label: "rateLimit", qos: .userInitiated)
|
|
25
|
+
private let rateLimit = 30
|
|
26
|
+
private let rateWindowSeconds: TimeInterval = 60
|
|
27
|
+
|
|
28
|
+
private func isRateLimited(ip: String) -> Bool {
|
|
29
|
+
return rateLimitQueue.sync {
|
|
30
|
+
let now = Date()
|
|
31
|
+
|
|
32
|
+
if let entry = rateLimitMap[ip], now.timeIntervalSince(entry.start) < rateWindowSeconds {
|
|
33
|
+
if entry.count >= rateLimit { return true }
|
|
34
|
+
|
|
35
|
+
rateLimitMap[ip] = (entry.start, entry.count + 1)
|
|
36
|
+
} else {
|
|
37
|
+
rateLimitMap[ip] = (now, 1)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
22
44
|
public init(delegate: HttpLocalServerSwifterDelegate) {
|
|
23
45
|
self.delegate = delegate
|
|
46
|
+
|
|
24
47
|
super.init()
|
|
25
48
|
}
|
|
26
49
|
|
|
@@ -33,6 +56,7 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
|
|
|
33
56
|
self.stopServer()
|
|
34
57
|
|
|
35
58
|
let server = HttpServer()
|
|
59
|
+
|
|
36
60
|
self.webServer = server
|
|
37
61
|
|
|
38
62
|
server.middleware.append { [weak self] request in
|
|
@@ -41,11 +65,13 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
|
|
|
41
65
|
if request.method == "OPTIONS" {
|
|
42
66
|
return self.corsResponse()
|
|
43
67
|
}
|
|
68
|
+
|
|
44
69
|
return self.processRequest(request)
|
|
45
70
|
}
|
|
46
71
|
|
|
47
72
|
do {
|
|
48
73
|
try server.start(self.defaultPort, forceIPv4: true)
|
|
74
|
+
|
|
49
75
|
let ip = Self.getWiFiAddress() ?? "127.0.0.1"
|
|
50
76
|
|
|
51
77
|
print("🚀 SWIFTER: Server running on http://\(ip):\(self.defaultPort)")
|
|
@@ -56,6 +82,7 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
|
|
|
56
82
|
])
|
|
57
83
|
} catch {
|
|
58
84
|
print("❌ SWIFTER ERROR: \(error)")
|
|
85
|
+
|
|
59
86
|
call.reject("Could not start server: \(error.localizedDescription)")
|
|
60
87
|
}
|
|
61
88
|
}
|
|
@@ -65,6 +92,7 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
|
|
|
65
92
|
@objc public func stopServer() {
|
|
66
93
|
webServer?.stop()
|
|
67
94
|
webServer = nil
|
|
95
|
+
|
|
68
96
|
// Drain pending futures so blocked threads can unblock on their semaphore timeout.
|
|
69
97
|
Self.queue.sync { Self.pendingResponses.removeAll() }
|
|
70
98
|
}
|
|
@@ -72,6 +100,7 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
|
|
|
72
100
|
/// Stops the server and resolves the Capacitor call.
|
|
73
101
|
@objc public func disconnect(resolving call: CAPPluginCall) {
|
|
74
102
|
stopServer()
|
|
103
|
+
|
|
75
104
|
call.resolve()
|
|
76
105
|
}
|
|
77
106
|
|
|
@@ -96,6 +125,18 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
|
|
|
96
125
|
// MARK: - Request processing
|
|
97
126
|
|
|
98
127
|
private func processRequest(_ request: HttpRequest) -> HttpResponse {
|
|
128
|
+
let clientIp = request.headers["x-forwarded-for"] ??
|
|
129
|
+
request.address ?? "unknown"
|
|
130
|
+
|
|
131
|
+
if isRateLimited(ip: clientIp) {
|
|
132
|
+
return .raw(429, "Too Many Requests", [
|
|
133
|
+
"Content-Type": "application/json",
|
|
134
|
+
"Access-Control-Allow-Origin": "*"
|
|
135
|
+
]) { writer in
|
|
136
|
+
try writer.write([UInt8](#"{"success":false,"error":"Too many requests"}"#.utf8))
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
99
140
|
let requestId = UUID().uuidString
|
|
100
141
|
|
|
101
142
|
// Use a protected local variable written only inside `queue.sync` inside
|
|
@@ -184,12 +225,15 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
|
|
|
184
225
|
defer { freeifaddrs(ifaddr) }
|
|
185
226
|
|
|
186
227
|
var ptr = ifaddr
|
|
228
|
+
|
|
187
229
|
while let current = ptr {
|
|
188
230
|
let interface = current.pointee
|
|
231
|
+
|
|
189
232
|
if interface.ifa_addr.pointee.sa_family == UInt8(AF_INET),
|
|
190
233
|
String(cString: interface.ifa_name) == "en0"
|
|
191
234
|
{
|
|
192
235
|
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
|
|
236
|
+
|
|
193
237
|
getnameinfo(
|
|
194
238
|
interface.ifa_addr,
|
|
195
239
|
socklen_t(interface.ifa_addr.pointee.sa_len),
|
|
@@ -198,9 +242,12 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
|
|
|
198
242
|
nil, 0,
|
|
199
243
|
NI_NUMERICHOST
|
|
200
244
|
)
|
|
245
|
+
|
|
201
246
|
address = String(cString: hostname)
|
|
247
|
+
|
|
202
248
|
break
|
|
203
249
|
}
|
|
250
|
+
|
|
204
251
|
ptr = interface.ifa_next
|
|
205
252
|
}
|
|
206
253
|
|
|
@@ -3,6 +3,10 @@ import Capacitor
|
|
|
3
3
|
|
|
4
4
|
@objc(HttpLocalServerSwifterPlugin)
|
|
5
5
|
public class HttpLocalServerSwifterPlugin: CAPPlugin, CAPBridgedPlugin, HttpLocalServerSwifterDelegate {
|
|
6
|
+
// Private properties
|
|
7
|
+
private var localServer: HttpLocalServerSwifter?
|
|
8
|
+
|
|
9
|
+
// Publi properties
|
|
6
10
|
public let identifier = "HttpLocalServerSwifterPlugin"
|
|
7
11
|
public let jsName = "HttpLocalServerSwifter"
|
|
8
12
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -11,14 +15,13 @@ public class HttpLocalServerSwifterPlugin: CAPPlugin, CAPBridgedPlugin, HttpLoca
|
|
|
11
15
|
CAPPluginMethod(name: "sendResponse", returnType: CAPPluginReturnPromise)
|
|
12
16
|
]
|
|
13
17
|
|
|
14
|
-
private var localServer: HttpLocalServerSwifter?
|
|
15
|
-
|
|
16
18
|
// MARK: - Plugin methods
|
|
17
19
|
|
|
18
20
|
@objc func connect(_ call: CAPPluginCall) {
|
|
19
21
|
if localServer == nil {
|
|
20
22
|
localServer = HttpLocalServerSwifter(delegate: self)
|
|
21
23
|
}
|
|
24
|
+
|
|
22
25
|
localServer?.connect(call)
|
|
23
26
|
}
|
|
24
27
|
|
|
@@ -32,6 +35,7 @@ public class HttpLocalServerSwifterPlugin: CAPPlugin, CAPBridgedPlugin, HttpLoca
|
|
|
32
35
|
@objc func sendResponse(_ call: CAPPluginCall) {
|
|
33
36
|
guard let requestId = call.getString("requestId"), !requestId.isEmpty else {
|
|
34
37
|
call.reject("Missing requestId")
|
|
38
|
+
|
|
35
39
|
return
|
|
36
40
|
}
|
|
37
41
|
|
|
@@ -41,6 +45,7 @@ public class HttpLocalServerSwifterPlugin: CAPPlugin, CAPBridgedPlugin, HttpLoca
|
|
|
41
45
|
requestId: requestId,
|
|
42
46
|
responseData: responseData
|
|
43
47
|
)
|
|
48
|
+
|
|
44
49
|
call.resolve()
|
|
45
50
|
}
|
|
46
51
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cappitolian/http-local-server-swifter",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.31",
|
|
4
4
|
"description": "Runs a local HTTP server on your device, accessible over LAN. Supports connect, disconnect, GET, and POST methods with IP and port discovery.",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|