@cappitolian/http-local-server-swifter 0.0.15 → 0.0.17

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.
@@ -10,7 +10,6 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
10
10
  private var webServer: HttpServer?
11
11
  private weak var delegate: HttpLocalServerSwifterDelegate?
12
12
 
13
- // Store callbacks that expect a JSON String containing the full response object
14
13
  private static var pendingResponses = [String: (String) -> Void]()
15
14
  private static let queue = DispatchQueue(label: "com.cappitolian.HttpLocalServerSwifter.pendingResponses", qos: .userInitiated)
16
15
 
@@ -23,15 +22,39 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
23
22
  }
24
23
 
25
24
  @objc public func connect(_ call: CAPPluginCall) {
26
- self.disconnect()
27
- self.webServer = HttpServer()
28
- self.setupHandlers()
29
- do {
30
- try self.webServer?.start(defaultPort, forceIPv4: true)
31
- let ip = Self.getWiFiAddress() ?? "127.0.0.1"
32
- call.resolve(["ip": ip, "port": self.defaultPort])
33
- } catch {
34
- call.reject("Failed to start server: \(error.localizedDescription)")
25
+ // IMPORTANT: Move execution to a background thread immediately
26
+ DispatchQueue.global(qos: .userInitiated).async { [weak self] in
27
+ guard let self = self else { return }
28
+
29
+ self.disconnect()
30
+ let server = HttpServer()
31
+ self.webServer = server
32
+
33
+ // Use middleware to catch ALL requests and avoid route misses
34
+ server.middleware.append { [weak self] request in
35
+ if request.method == "OPTIONS" {
36
+ return self?.corsResponse() ?? .raw(204, "No Content", nil, nil)
37
+ }
38
+ return self?.processRequest(request) ?? .raw(500, "Internal Server Error", nil, nil)
39
+ }
40
+
41
+ do {
42
+ // We start the server. This call is non-blocking in Swifter but
43
+ // it's safer to do it here.
44
+ try server.start(self.defaultPort, forceIPv4: true)
45
+ let ip = Self.getWiFiAddress() ?? "127.0.0.1"
46
+
47
+ print("🚀 SWIFTER: Server running on http://\(ip):\(self.defaultPort)")
48
+
49
+ // Resolve back to Angular
50
+ call.resolve([
51
+ "ip": ip,
52
+ "port": Int(self.defaultPort)
53
+ ])
54
+ } catch {
55
+ print("❌ SWIFTER ERROR: \(error)")
56
+ call.reject("Could not start server")
57
+ }
35
58
  }
36
59
  }
37
60
 
@@ -41,39 +64,7 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
41
64
  Self.queue.async { Self.pendingResponses.removeAll() }
42
65
  call?.resolve()
43
66
  }
44
-
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]) {
49
- queue.async {
50
- if let callback = pendingResponses[requestId] {
51
- if let jsonData = try? JSONSerialization.data(withJSONObject: responseData),
52
- let jsonString = String(data: jsonData, encoding: .utf8) {
53
- callback(jsonString)
54
- }
55
- pendingResponses.removeValue(forKey: requestId)
56
- }
57
- }
58
- }
59
-
60
- private func setupHandlers() {
61
- guard let server = webServer else { return }
62
-
63
- // This closure handles EVERY request regardless of the path
64
- let handler: ((HttpRequest) -> HttpResponse) = { [weak self] request in
65
- if request.method == "OPTIONS" {
66
- return self?.corsResponse() ?? .raw(204, "No Content", nil, nil)
67
- }
68
- return self?.processRequest(request) ?? .raw(500, "Internal Server Error", nil, nil)
69
- }
70
67
 
71
- // Swifter needs a wildcard to catch everything including subpaths like /orders/123
72
- server.middleware.append { request in
73
- return handler(request)
74
- }
75
- }
76
-
77
68
  private func processRequest(_ request: HttpRequest) -> HttpResponse {
78
69
  let requestId = UUID().uuidString
79
70
  var responseString: String?
@@ -86,7 +77,6 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
86
77
  }
87
78
  }
88
79
 
89
- // Map request to dictionary for TS
90
80
  let requestData: [String: Any] = [
91
81
  "requestId": requestId,
92
82
  "method": request.method,
@@ -96,29 +86,42 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
96
86
  "body": String(bytes: request.body, encoding: .utf8) ?? ""
97
87
  ]
98
88
 
99
- DispatchQueue.main.async { self.delegate?.httpLocalServerSwifterDidReceiveRequest(requestData) }
89
+ // CRITICAL: notifyListeners MUST be called from the Main Thread
90
+ DispatchQueue.main.async {
91
+ self.delegate?.httpLocalServerSwifterDidReceiveRequest(requestData)
92
+ }
100
93
 
101
94
  let result = semaphore.wait(timeout: .now() + defaultTimeout)
102
95
 
103
96
  if result == .timedOut {
104
- return createDynamicResponse("{\"error\":\"timeout\"}", statusCode: 408)
97
+ Self.queue.async { Self.pendingResponses.removeValue(forKey: requestId) }
98
+ return .raw(408, "Request Timeout", nil, nil)
105
99
  }
106
100
 
107
- return createDynamicResponse(responseString ?? "{\"error\":\"no_response\"}")
101
+ return createDynamicResponse(responseString ?? "")
108
102
  }
109
-
110
- /**
111
- * Extracts status, headers, and body from the JSON string provided by TypeScript
112
- */
113
- private func createDynamicResponse(_ jsonResponse: String, statusCode defaultStatus: Int = 200) -> HttpResponse {
114
- var finalStatus = defaultStatus
103
+
104
+ static func handleJsResponse(requestId: String, responseData: [String: Any]) {
105
+ queue.async {
106
+ if let callback = pendingResponses[requestId] {
107
+ // Extract body, status and headers to pass as a JSON string to the semaphore
108
+ if let jsonData = try? JSONSerialization.data(withJSONObject: responseData),
109
+ let jsonString = String(data: jsonData, encoding: .utf8) {
110
+ callback(jsonString)
111
+ }
112
+ pendingResponses.removeValue(forKey: requestId)
113
+ }
114
+ }
115
+ }
116
+
117
+ private func createDynamicResponse(_ jsonResponse: String) -> HttpResponse {
118
+ var finalStatus = 200
115
119
  var finalBody = jsonResponse
116
120
  var headers: [String: String] = [
117
121
  "Content-Type": "application/json",
118
122
  "Access-Control-Allow-Origin": "*",
119
123
  "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
120
- "Access-Control-Allow-Headers": "Origin, Content-Type, Accept, Authorization, X-Requested-With",
121
- "Access-Control-Max-Age": "3600"
124
+ "Access-Control-Allow-Headers": "Origin, Content-Type, Accept, Authorization, X-Requested-With"
122
125
  ]
123
126
 
124
127
  if let data = jsonResponse.data(using: .utf8),
@@ -130,20 +133,18 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
130
133
  }
131
134
  }
132
135
 
133
- let bodyData = [UInt8](finalBody.utf8)
134
- return HttpResponse.raw(finalStatus, "OK", headers) { try $0.write(bodyData) }
136
+ return .raw(finalStatus, "OK", headers) { try $0.write([UInt8](finalBody.utf8)) }
135
137
  }
136
-
138
+
137
139
  private func corsResponse() -> HttpResponse {
138
- return HttpResponse.raw(204, "No Content", [
140
+ return .raw(204, "No Content", [
139
141
  "Access-Control-Allow-Origin": "*",
140
142
  "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
141
143
  "Access-Control-Allow-Headers": "Origin, Content-Type, Accept, Authorization, X-Requested-With",
142
- "Access-Control-Max-Age": "3600"
144
+ "Access-Control-Max-Age": "86400"
143
145
  ], nil)
144
146
  }
145
147
 
146
- // WiFi address logic remains the same...
147
148
  static func getWiFiAddress() -> String? {
148
149
  var address: String?
149
150
  var ifaddr: UnsafeMutablePointer<ifaddrs>?
@@ -151,8 +152,7 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
151
152
  var ptr = ifaddr
152
153
  while ptr != nil {
153
154
  let interface = ptr!.pointee
154
- let addrFamily = interface.ifa_addr.pointee.sa_family
155
- if addrFamily == UInt8(AF_INET) {
155
+ if interface.ifa_addr.pointee.sa_family == UInt8(AF_INET) {
156
156
  let name = String(cString: interface.ifa_name)
157
157
  if name == "en0" {
158
158
  var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
@@ -166,4 +166,4 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
166
166
  }
167
167
  return address
168
168
  }
169
- }
169
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cappitolian/http-local-server-swifter",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
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",