@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
|
-
//
|
|
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
|
-
|
|
30
|
-
self.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ?? "
|
|
101
|
+
return createDynamicResponse(responseString ?? "")
|
|
121
102
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
|
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": "
|
|
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
|
-
|
|
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.
|
|
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",
|