@cappitolian/http-local-server-swifter 0.0.12 → 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,331 +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
62
  let handler: ((HttpRequest) -> HttpResponse) = { [weak self] request in
91
- return self?.handleRequest(request) ?? self?.errorResponse() ?? .internalServerError
63
+ if request.method == "OPTIONS" { return self?.corsResponse() ?? .noContent }
64
+ return self?.processRequest(request) ?? .internalServerError
92
65
  }
93
-
94
66
  server["/"] = handler
95
67
  server["/:path"] = handler
96
- server["/:first/:second"] = handler
97
- server["/:first/:second/:third"] = handler
98
-
99
- print("✅ HttpLocalServerSwifter: Handlers configured")
100
- }
101
-
102
- private func handleRequest(_ request: HttpRequest) -> HttpResponse {
103
- print("📨 HttpLocalServerSwifter: Received \(request.method) request to \(request.path)")
104
-
105
- // Handle OPTIONS (CORS preflight)
106
- if request.method == "OPTIONS" {
107
- print("🔄 HttpLocalServerSwifter: Handling CORS preflight")
108
- return corsResponse()
109
- }
110
-
111
- return processRequest(request)
112
68
  }
113
69
 
114
70
  private func processRequest(_ request: HttpRequest) -> HttpResponse {
115
- let method = request.method
116
- let path = request.path
117
- let body = extractBody(from: request)
118
- let headers = extractHeaders(from: request)
119
- let query = extractQuery(from: request)
120
-
121
71
  let requestId = UUID().uuidString
122
72
  var responseString: String?
123
-
124
- // Setup semaphore for synchronous waiting
125
73
  let semaphore = DispatchSemaphore(value: 0)
126
74
 
127
75
  Self.queue.async {
128
- Self.pendingResponses[requestId] = { responseBody in
129
- responseString = responseBody
76
+ Self.pendingResponses[requestId] = { jsResponse in
77
+ responseString = jsResponse
130
78
  semaphore.signal()
131
79
  }
132
80
  }
133
81
 
134
- // Notify delegate with request info
135
- var requestData: [String: Any] = [
82
+ // Map request to dictionary for TS
83
+ let requestData: [String: Any] = [
136
84
  "requestId": requestId,
137
- "method": method,
138
- "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) ?? ""
139
90
  ]
140
91
 
141
- // Only add if they exist (consistent with TypeScript and Android)
142
- if let body = body, !body.isEmpty {
143
- requestData["body"] = body
144
- }
145
-
146
- if !headers.isEmpty {
147
- requestData["headers"] = headers
148
- }
149
-
150
- if !query.isEmpty {
151
- requestData["query"] = query
152
- }
153
-
154
- print("📤 HttpLocalServerSwifter: Notifying delegate with requestId: \(requestId)")
92
+ DispatchQueue.main.async { self.delegate?.httpLocalServerSwifterDidReceiveRequest(requestData) }
155
93
 
156
- // Notify on main thread to ensure proper event delivery
157
- DispatchQueue.main.async { [weak self] in
158
- self?.delegate?.httpLocalServerSwifterDidReceiveRequest(requestData)
159
- }
160
-
161
- // Wait for JS response or timeout
162
94
  let result = semaphore.wait(timeout: .now() + defaultTimeout)
163
95
 
164
- // Cleanup
165
- Self.queue.async {
166
- Self.pendingResponses.removeValue(forKey: requestId)
167
- }
168
-
169
- // Handle timeout
170
96
  if result == .timedOut {
171
- print("⏱️ HttpLocalServerSwifter: Request timeout for requestId: \(requestId)")
172
- let timeoutResponse = "{\"error\":\"Request timeout\",\"requestId\":\"\(requestId)\"}"
173
- return createJsonResponse(timeoutResponse, statusCode: 408)
97
+ return createDynamicResponse("{\"error\":\"timeout\"}", statusCode: 408)
174
98
  }
175
99
 
176
- let reply = responseString ?? "{\"error\":\"No response from handler\"}"
177
- print("✅ HttpLocalServerSwifter: Sending response for requestId: \(requestId)")
178
- return createJsonResponse(reply)
100
+ return createDynamicResponse(responseString ?? "{\"error\":\"no_response\"}")
179
101
  }
180
102
 
181
- private func extractBody(from request: HttpRequest) -> String? {
182
- let bodyBytes = request.body
183
-
184
- guard !bodyBytes.isEmpty else {
185
- return nil
186
- }
187
-
188
- return String(bytes: bodyBytes, encoding: .utf8)
189
- }
190
-
191
- private func extractHeaders(from request: HttpRequest) -> [String: String] {
192
- var headersDict: [String: String] = [:]
193
- for (key, value) in request.headers {
194
- headersDict[key] = value
195
- }
196
- return headersDict
197
- }
198
-
199
- private func extractQuery(from request: HttpRequest) -> [String: String] {
200
- var queryDict: [String: String] = [:]
201
- for (key, value) in request.queryParams {
202
- queryDict[key] = value
203
- }
204
- return queryDict
205
- }
206
-
207
- private func createJsonResponse(_ body: String, statusCode: Int = 200) -> HttpResponse {
208
- 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] = [
209
110
  "Content-Type": "application/json",
210
111
  "Access-Control-Allow-Origin": "*",
211
112
  "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
212
- "Access-Control-Allow-Headers": "Origin, Content-Type, Accept, Authorization",
213
- "Access-Control-Allow-Credentials": "true",
113
+ "Access-Control-Allow-Headers": "Origin, Content-Type, Accept, Authorization, X-Requested-With",
214
114
  "Access-Control-Max-Age": "3600"
215
115
  ]
216
116
 
217
- let bodyData = [UInt8](body.utf8)
218
-
219
- return HttpResponse.raw(statusCode, statusDescription(for: statusCode), headers) { writer in
220
- 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
+ }
221
124
  }
125
+
126
+ let bodyData = [UInt8](finalBody.utf8)
127
+ return HttpResponse.raw(finalStatus, "OK", headers) { try $0.write(bodyData) }
222
128
  }
223
129
 
224
130
  private func corsResponse() -> HttpResponse {
225
- let headers: [String: String] = [
131
+ return HttpResponse.raw(204, "No Content", [
226
132
  "Access-Control-Allow-Origin": "*",
227
133
  "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
228
- "Access-Control-Allow-Headers": "Origin, Content-Type, Accept, Authorization",
229
- "Access-Control-Allow-Credentials": "true",
134
+ "Access-Control-Allow-Headers": "Origin, Content-Type, Accept, Authorization, X-Requested-With",
230
135
  "Access-Control-Max-Age": "3600"
231
- ]
232
-
233
- return HttpResponse.raw(204, "No Content", headers, nil)
234
- }
235
-
236
- private func errorResponse() -> HttpResponse {
237
- return createJsonResponse("{\"error\":\"Server error\"}", statusCode: 500)
238
- }
239
-
240
- private func statusDescription(for code: Int) -> String {
241
- switch code {
242
- case 200: return "OK"
243
- case 201: return "Created"
244
- case 202: return "Accepted"
245
- case 204: return "No Content"
246
- case 400: return "Bad Request"
247
- case 401: return "Unauthorized"
248
- case 403: return "Forbidden"
249
- case 404: return "Not Found"
250
- case 408: return "Request Timeout"
251
- case 500: return "Internal Server Error"
252
- default: return "Unknown"
253
- }
136
+ ], nil)
254
137
  }
255
-
256
- private func startServer() throws {
257
- guard let server = webServer else {
258
- throw NSError(
259
- domain: "HttpLocalServerSwifter",
260
- code: -1,
261
- userInfo: [NSLocalizedDescriptionKey: "WebServer not initialized"]
262
- )
263
- }
264
-
265
- // Try to start on the default port
266
- do {
267
- try server.start(defaultPort, forceIPv4: true)
268
- print("✅ HttpLocalServerSwifter: Server listening on port \(defaultPort)")
269
- } catch {
270
- print("❌ HttpLocalServerSwifter: Failed to bind to port \(defaultPort): \(error)")
271
- throw error
272
- }
273
- }
274
-
275
- // MARK: - Network Utilities
138
+
139
+ // WiFi address logic remains the same...
276
140
  static func getWiFiAddress() -> String? {
277
141
  var address: String?
278
142
  var ifaddr: UnsafeMutablePointer<ifaddrs>?
279
-
280
- guard getifaddrs(&ifaddr) == 0 else {
281
- print("❌ HttpLocalServerSwifter: Failed to get network interfaces")
282
- return nil
283
- }
284
-
285
- defer {
286
- freeifaddrs(ifaddr)
287
- }
288
-
289
- var ptr = ifaddr
290
- while ptr != nil {
291
- defer { ptr = ptr?.pointee.ifa_next }
292
-
293
- guard let interface = ptr?.pointee else { continue }
294
-
295
- let addrFamily = interface.ifa_addr.pointee.sa_family
296
-
297
- // Check for IPv4 interface
298
- guard addrFamily == UInt8(AF_INET) else { continue }
299
-
300
- let name = String(cString: interface.ifa_name)
301
-
302
- // WiFi interface (en0) or cellular (pdp_ip0)
303
- guard name == "en0" || name == "pdp_ip0" else { continue }
304
-
305
- var addr = interface.ifa_addr.pointee
306
- var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
307
-
308
- let result = getnameinfo(
309
- &addr,
310
- socklen_t(interface.ifa_addr.pointee.sa_len),
311
- &hostname,
312
- socklen_t(hostname.count),
313
- nil,
314
- 0,
315
- NI_NUMERICHOST
316
- )
317
-
318
- guard result == 0 else { continue }
319
-
320
- address = String(cString: hostname)
321
-
322
- print("📡 HttpLocalServerSwifter: Found \(name) interface with IP: \(address ?? "unknown")")
323
-
324
- // Prefer en0 (WiFi) over pdp_ip0 (cellular)
325
- if name == "en0" {
326
- 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
327
157
  }
158
+ freeifaddrs(ifaddr)
328
159
  }
329
-
330
160
  return address
331
161
  }
332
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.12",
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",