@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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
let
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ?? "
|
|
101
|
+
return createDynamicResponse(responseString ?? "")
|
|
108
102
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
|
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": "
|
|
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
|
-
|
|
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.
|
|
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",
|