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