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

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.
@@ -2,338 +2,161 @@ import Foundation
2
2
  import Swifter
3
3
  import Capacitor
4
4
 
5
- // MARK: - Protocol
6
5
  public protocol HttpLocalServerSwifterDelegate: AnyObject {
7
6
  func httpLocalServerSwifterDidReceiveRequest(_ data: [String: Any])
8
7
  }
9
8
 
10
- // MARK: - HttpLocalServerSwifter
11
9
  @objc public class HttpLocalServerSwifter: NSObject {
12
- // MARK: - Properties
13
10
  private var webServer: HttpServer?
14
11
  private weak var delegate: HttpLocalServerSwifterDelegate?
15
12
 
13
+ // Store callbacks that expect a JSON String containing the full response object
16
14
  private static var pendingResponses = [String: (String) -> Void]()
17
15
  private static let queue = DispatchQueue(label: "com.cappitolian.HttpLocalServerSwifter.pendingResponses", qos: .userInitiated)
18
16
 
19
- private let defaultTimeout: TimeInterval = 30.0 // Aumentado para debugging
17
+ private let defaultTimeout: TimeInterval = 10.0
20
18
  private let defaultPort: UInt16 = 8080
21
19
 
22
- // MARK: - Initialization
23
20
  public init(delegate: HttpLocalServerSwifterDelegate) {
24
21
  self.delegate = delegate
25
22
  super.init()
26
23
  }
27
24
 
28
- deinit {
29
- disconnect()
30
- }
31
-
32
- // MARK: - Public Methods
33
25
  @objc public func connect(_ call: CAPPluginCall) {
34
- // Stop existing server if running
35
26
  self.disconnect()
36
-
37
27
  self.webServer = HttpServer()
38
28
  self.setupHandlers()
39
-
40
29
  do {
41
- try self.startServer()
30
+ try self.webServer?.start(defaultPort, forceIPv4: true)
42
31
  let ip = Self.getWiFiAddress() ?? "127.0.0.1"
43
-
44
- print("✅ HttpLocalServerSwifter: Server started on \(ip):\(self.defaultPort)")
45
-
46
- call.resolve([
47
- "ip": ip,
48
- "port": self.defaultPort
49
- ])
32
+ call.resolve(["ip": ip, "port": self.defaultPort])
50
33
  } catch {
51
- print("❌ HttpLocalServerSwifter: Failed to start server - \(error.localizedDescription)")
52
34
  call.reject("Failed to start server: \(error.localizedDescription)")
53
35
  }
54
36
  }
55
37
 
56
38
  @objc public func disconnect(_ call: CAPPluginCall? = nil) {
57
- disconnect()
39
+ webServer?.stop()
40
+ webServer = nil
41
+ Self.queue.async { Self.pendingResponses.removeAll() }
58
42
  call?.resolve()
59
43
  }
60
44
 
61
- // MARK: - Static Methods
62
- static func handleJsResponse(requestId: String, body: String) {
45
+ /**
46
+ * Serializes the JS response dictionary to JSON to pass it back to the processing thread
47
+ */
48
+ static func handleJsResponse(requestId: String, responseData: [String: Any]) {
63
49
  queue.async {
64
50
  if let callback = pendingResponses[requestId] {
65
- callback(body)
51
+ if let jsonData = try? JSONSerialization.data(withJSONObject: responseData),
52
+ let jsonString = String(data: jsonData, encoding: .utf8) {
53
+ callback(jsonString)
54
+ }
66
55
  pendingResponses.removeValue(forKey: requestId)
67
- print("✅ HttpLocalServerSwifter: Response sent for requestId: \(requestId)")
68
- } else {
69
- print("⚠️ HttpLocalServerSwifter: No pending callback for requestId: \(requestId)")
70
56
  }
71
57
  }
72
58
  }
73
59
 
74
- // MARK: - Private Methods
75
- private func disconnect() {
76
- webServer?.stop()
77
- webServer = nil
78
-
79
- // Clear pending responses
80
- Self.queue.async {
81
- Self.pendingResponses.removeAll()
82
- }
83
-
84
- print("🛑 HttpLocalServerSwifter: Server stopped")
85
- }
86
-
87
60
  private func setupHandlers() {
88
61
  guard let server = webServer else { return }
89
-
90
- server["/"] = { [weak self] request in
91
- return self?.handleRequest(request) ?? self?.errorResponse() ?? .internalServerError
62
+ let handler: ((HttpRequest) -> HttpResponse) = { [weak self] request in
63
+ if request.method == "OPTIONS" { return self?.corsResponse() ?? .noContent }
64
+ return self?.processRequest(request) ?? .internalServerError
92
65
  }
93
-
94
- server["/:path"] = { [weak self] request in
95
- return self?.handleRequest(request) ?? self?.errorResponse() ?? .internalServerError
96
- }
97
-
98
- server["/:first/:second"] = { [weak self] request in
99
- return self?.handleRequest(request) ?? self?.errorResponse() ?? .internalServerError
100
- }
101
-
102
- server["/:first/:second/:third"] = { [weak self] request in
103
- return self?.handleRequest(request) ?? self?.errorResponse() ?? .internalServerError
104
- }
105
-
106
- print("✅ HttpLocalServerSwifter: Handlers configured")
107
- }
108
-
109
- private func handleRequest(_ request: HttpRequest) -> HttpResponse {
110
- print("📨 HttpLocalServerSwifter: Received \(request.method) request to \(request.path)")
111
-
112
- // Handle OPTIONS (CORS preflight)
113
- if request.method == "OPTIONS" {
114
- print("🔄 HttpLocalServerSwifter: Handling CORS preflight")
115
- return corsResponse()
116
- }
117
-
118
- return processRequest(request)
66
+ server["/"] = handler
67
+ server["/:path"] = handler
119
68
  }
120
69
 
121
70
  private func processRequest(_ request: HttpRequest) -> HttpResponse {
122
- let method = request.method
123
- let path = request.path
124
- let body = extractBody(from: request)
125
- let headers = extractHeaders(from: request)
126
- let query = extractQuery(from: request)
127
-
128
71
  let requestId = UUID().uuidString
129
72
  var responseString: String?
130
-
131
- // Setup semaphore for synchronous waiting
132
73
  let semaphore = DispatchSemaphore(value: 0)
133
74
 
134
75
  Self.queue.async {
135
- Self.pendingResponses[requestId] = { responseBody in
136
- responseString = responseBody
76
+ Self.pendingResponses[requestId] = { jsResponse in
77
+ responseString = jsResponse
137
78
  semaphore.signal()
138
79
  }
139
80
  }
140
81
 
141
- // Notify delegate with request info
142
- var requestData: [String: Any] = [
82
+ // Map request to dictionary for TS
83
+ let requestData: [String: Any] = [
143
84
  "requestId": requestId,
144
- "method": method,
145
- "path": path
85
+ "method": request.method,
86
+ "path": request.path,
87
+ "headers": request.headers,
88
+ "query": request.queryParams,
89
+ "body": String(bytes: request.body, encoding: .utf8) ?? ""
146
90
  ]
147
91
 
148
- // Only add if they exist (consistent with TypeScript and Android)
149
- if let body = body, !body.isEmpty {
150
- requestData["body"] = body
151
- }
92
+ DispatchQueue.main.async { self.delegate?.httpLocalServerSwifterDidReceiveRequest(requestData) }
152
93
 
153
- if !headers.isEmpty {
154
- requestData["headers"] = headers
155
- }
156
-
157
- if !query.isEmpty {
158
- requestData["query"] = query
159
- }
160
-
161
- print("📤 HttpLocalServerSwifter: Notifying delegate with requestId: \(requestId)")
162
-
163
- // Notify on main thread to ensure proper event delivery
164
- DispatchQueue.main.async { [weak self] in
165
- self?.delegate?.httpLocalServerSwifterDidReceiveRequest(requestData)
166
- }
167
-
168
- // Wait for JS response or timeout
169
94
  let result = semaphore.wait(timeout: .now() + defaultTimeout)
170
95
 
171
- // Cleanup
172
- Self.queue.async {
173
- Self.pendingResponses.removeValue(forKey: requestId)
174
- }
175
-
176
- // Handle timeout
177
96
  if result == .timedOut {
178
- print("⏱️ HttpLocalServerSwifter: Request timeout for requestId: \(requestId)")
179
- let timeoutResponse = "{\"error\":\"Request timeout\",\"requestId\":\"\(requestId)\"}"
180
- return createJsonResponse(timeoutResponse, statusCode: 408)
97
+ return createDynamicResponse("{\"error\":\"timeout\"}", statusCode: 408)
181
98
  }
182
99
 
183
- let reply = responseString ?? "{\"error\":\"No response from handler\"}"
184
- print("✅ HttpLocalServerSwifter: Sending response for requestId: \(requestId)")
185
- return createJsonResponse(reply)
186
- }
187
-
188
- private func extractBody(from request: HttpRequest) -> String? {
189
- let bodyBytes = request.body
190
-
191
- guard !bodyBytes.isEmpty else {
192
- return nil
193
- }
194
-
195
- return String(bytes: bodyBytes, encoding: .utf8)
196
- }
197
-
198
- private func extractHeaders(from request: HttpRequest) -> [String: String] {
199
- var headersDict: [String: String] = [:]
200
- for (key, value) in request.headers {
201
- headersDict[key] = value
202
- }
203
- return headersDict
204
- }
205
-
206
- private func extractQuery(from request: HttpRequest) -> [String: String] {
207
- var queryDict: [String: String] = [:]
208
- for (key, value) in request.queryParams {
209
- queryDict[key] = value
210
- }
211
- return queryDict
100
+ return createDynamicResponse(responseString ?? "{\"error\":\"no_response\"}")
212
101
  }
213
102
 
214
- private func createJsonResponse(_ body: String, statusCode: Int = 200) -> HttpResponse {
215
- let headers: [String: String] = [
103
+ /**
104
+ * Extracts status, headers, and body from the JSON string provided by TypeScript
105
+ */
106
+ private func createDynamicResponse(_ jsonResponse: String, statusCode defaultStatus: Int = 200) -> HttpResponse {
107
+ var finalStatus = defaultStatus
108
+ var finalBody = jsonResponse
109
+ var headers: [String: String] = [
216
110
  "Content-Type": "application/json",
217
111
  "Access-Control-Allow-Origin": "*",
218
112
  "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
219
- "Access-Control-Allow-Headers": "Origin, Content-Type, Accept, Authorization",
220
- "Access-Control-Allow-Credentials": "true",
113
+ "Access-Control-Allow-Headers": "Origin, Content-Type, Accept, Authorization, X-Requested-With",
221
114
  "Access-Control-Max-Age": "3600"
222
115
  ]
223
116
 
224
- let bodyData = [UInt8](body.utf8)
225
-
226
- return HttpResponse.raw(statusCode, statusDescription(for: statusCode), headers) { writer in
227
- try writer.write(bodyData)
117
+ if let data = jsonResponse.data(using: .utf8),
118
+ let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
119
+ finalBody = dict["body"] as? String ?? ""
120
+ finalStatus = dict["status"] as? Int ?? 200
121
+ if let customHeaders = dict["headers"] as? [String: String] {
122
+ for (key, value) in customHeaders { headers[key] = value }
123
+ }
228
124
  }
125
+
126
+ let bodyData = [UInt8](finalBody.utf8)
127
+ return HttpResponse.raw(finalStatus, "OK", headers) { try $0.write(bodyData) }
229
128
  }
230
129
 
231
130
  private func corsResponse() -> HttpResponse {
232
- let headers: [String: String] = [
131
+ return HttpResponse.raw(204, "No Content", [
233
132
  "Access-Control-Allow-Origin": "*",
234
133
  "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
235
- "Access-Control-Allow-Headers": "Origin, Content-Type, Accept, Authorization",
236
- "Access-Control-Allow-Credentials": "true",
134
+ "Access-Control-Allow-Headers": "Origin, Content-Type, Accept, Authorization, X-Requested-With",
237
135
  "Access-Control-Max-Age": "3600"
238
- ]
239
-
240
- return HttpResponse.raw(204, "No Content", headers, nil)
136
+ ], nil)
241
137
  }
242
-
243
- private func errorResponse() -> HttpResponse {
244
- return createJsonResponse("{\"error\":\"Server error\"}", statusCode: 500)
245
- }
246
-
247
- private func statusDescription(for code: Int) -> String {
248
- switch code {
249
- case 200: return "OK"
250
- case 201: return "Created"
251
- case 202: return "Accepted"
252
- case 204: return "No Content"
253
- case 400: return "Bad Request"
254
- case 401: return "Unauthorized"
255
- case 403: return "Forbidden"
256
- case 404: return "Not Found"
257
- case 408: return "Request Timeout"
258
- case 500: return "Internal Server Error"
259
- default: return "Unknown"
260
- }
261
- }
262
-
263
- private func startServer() throws {
264
- guard let server = webServer else {
265
- throw NSError(
266
- domain: "HttpLocalServerSwifter",
267
- code: -1,
268
- userInfo: [NSLocalizedDescriptionKey: "WebServer not initialized"]
269
- )
270
- }
271
-
272
- // Try to start on the default port
273
- do {
274
- try server.start(defaultPort, forceIPv4: true)
275
- print("✅ HttpLocalServerSwifter: Server listening on port \(defaultPort)")
276
- } catch {
277
- print("❌ HttpLocalServerSwifter: Failed to bind to port \(defaultPort): \(error)")
278
- throw error
279
- }
280
- }
281
-
282
- // MARK: - Network Utilities
138
+
139
+ // WiFi address logic remains the same...
283
140
  static func getWiFiAddress() -> String? {
284
141
  var address: String?
285
142
  var ifaddr: UnsafeMutablePointer<ifaddrs>?
286
-
287
- guard getifaddrs(&ifaddr) == 0 else {
288
- print("❌ HttpLocalServerSwifter: Failed to get network interfaces")
289
- return nil
290
- }
291
-
292
- defer {
293
- freeifaddrs(ifaddr)
294
- }
295
-
296
- var ptr = ifaddr
297
- while ptr != nil {
298
- defer { ptr = ptr?.pointee.ifa_next }
299
-
300
- guard let interface = ptr?.pointee else { continue }
301
-
302
- let addrFamily = interface.ifa_addr.pointee.sa_family
303
-
304
- // Check for IPv4 interface
305
- guard addrFamily == UInt8(AF_INET) else { continue }
306
-
307
- let name = String(cString: interface.ifa_name)
308
-
309
- // WiFi interface (en0) or cellular (pdp_ip0)
310
- guard name == "en0" || name == "pdp_ip0" else { continue }
311
-
312
- var addr = interface.ifa_addr.pointee
313
- var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
314
-
315
- let result = getnameinfo(
316
- &addr,
317
- socklen_t(interface.ifa_addr.pointee.sa_len),
318
- &hostname,
319
- socklen_t(hostname.count),
320
- nil,
321
- 0,
322
- NI_NUMERICHOST
323
- )
324
-
325
- guard result == 0 else { continue }
326
-
327
- address = String(cString: hostname)
328
-
329
- print("📡 HttpLocalServerSwifter: Found \(name) interface with IP: \(address ?? "unknown")")
330
-
331
- // Prefer en0 (WiFi) over pdp_ip0 (cellular)
332
- if name == "en0" {
333
- break
143
+ if getifaddrs(&ifaddr) == 0 {
144
+ var ptr = ifaddr
145
+ while ptr != nil {
146
+ let interface = ptr!.pointee
147
+ let addrFamily = interface.ifa_addr.pointee.sa_family
148
+ if addrFamily == UInt8(AF_INET) {
149
+ let name = String(cString: interface.ifa_name)
150
+ if name == "en0" {
151
+ var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
152
+ getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len), &hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST)
153
+ address = String(cString: hostname)
154
+ }
155
+ }
156
+ ptr = interface.ifa_next
334
157
  }
158
+ freeifaddrs(ifaddr)
335
159
  }
336
-
337
160
  return address
338
161
  }
339
162
  }
@@ -3,7 +3,6 @@ import Capacitor
3
3
 
4
4
  @objc(HttpLocalServerSwifterPlugin)
5
5
  public class HttpLocalServerSwifterPlugin: CAPPlugin, CAPBridgedPlugin, HttpLocalServerSwifterDelegate {
6
- // MARK: - CAPBridgedPlugin Properties
7
6
  public let identifier = "HttpLocalServerSwifterPlugin"
8
7
  public let jsName = "HttpLocalServerSwifter"
9
8
  public let pluginMethods: [CAPPluginMethod] = [
@@ -12,59 +11,30 @@ public class HttpLocalServerSwifterPlugin: CAPPlugin, CAPBridgedPlugin, HttpLoca
12
11
  CAPPluginMethod(name: "sendResponse", returnType: CAPPluginReturnPromise)
13
12
  ]
14
13
 
15
- // MARK: - Properties
16
14
  private var localServer: HttpLocalServerSwifter?
17
15
 
18
- // MARK: - Lifecycle
19
- public override func load() {
20
- print("✅ HttpLocalServerSwifterPlugin: Plugin loaded")
21
- }
22
-
23
- // MARK: - Plugin Methods
24
16
  @objc func connect(_ call: CAPPluginCall) {
25
- print("📞 HttpLocalServerSwifterPlugin: connect() called")
26
-
27
- if localServer == nil {
28
- localServer = HttpLocalServerSwifter(delegate: self)
29
- print("✅ HttpLocalServerSwifterPlugin: Server instance created")
30
- }
31
-
17
+ if localServer == nil { localServer = HttpLocalServerSwifter(delegate: self) }
32
18
  localServer?.connect(call)
33
19
  }
34
20
 
35
21
  @objc func disconnect(_ call: CAPPluginCall) {
36
- print("📞 HttpLocalServerSwifterPlugin: disconnect() called")
37
-
38
- if localServer != nil {
39
- localServer?.disconnect(call)
40
- localServer = nil
41
- } else {
42
- call.resolve()
43
- }
22
+ localServer?.disconnect(call)
23
+ localServer = nil
44
24
  }
45
25
 
46
26
  @objc func sendResponse(_ call: CAPPluginCall) {
47
27
  guard let requestId = call.getString("requestId") else {
48
- print("❌ HttpLocalServerSwifterPlugin: Missing requestId")
49
28
  call.reject("Missing requestId")
50
29
  return
51
30
  }
52
-
53
- guard let body = call.getString("body") else {
54
- print("❌ HttpLocalServerSwifterPlugin: Missing body")
55
- call.reject("Missing body")
56
- return
57
- }
58
-
59
- print("📤 HttpLocalServerSwifterPlugin: sendResponse for requestId: \(requestId)")
60
- HttpLocalServerSwifter.handleJsResponse(requestId: requestId, body: body)
31
+ // Send the entire JS dictionary to handleJsResponse
32
+ // This includes 'body', 'status', and 'headers'
33
+ HttpLocalServerSwifter.handleJsResponse(requestId: requestId, responseData: call.dictionaryRepresentation)
61
34
  call.resolve()
62
35
  }
63
36
 
64
- // MARK: - HttpLocalServerSwifterDelegate
65
37
  public func httpLocalServerSwifterDidReceiveRequest(_ data: [String: Any]) {
66
- print("📨 HttpLocalServerSwifterPlugin: Received request, notifying listeners")
67
- print(" Request data: \(data)")
68
38
  notifyListeners("onRequest", data: data)
69
39
  }
70
40
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cappitolian/http-local-server-swifter",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
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",