@cappitolian/http-local-server 0.0.7 → 0.0.9

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.
package/dist/plugin.js CHANGED
@@ -2,25 +2,78 @@ var capacitorHttpLocalServer = (function (exports, core) {
2
2
  'use strict';
3
3
 
4
4
  /**
5
- * Registramos el plugin.
6
- * El nombre 'HttpLocalServer' debe coincidir con el @CapacitorPlugin en Java
7
- * y el @objc(HttpLocalServerPlugin) en Swift.
5
+ * Plugin de servidor HTTP local para Android e iOS.
6
+ *
7
+ * Permite crear un servidor HTTP en el dispositivo que puede recibir
8
+ * peticiones desde otros dispositivos en la misma red.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { HttpLocalServer } from '@cappitolian/http-local-server';
13
+ *
14
+ * // Iniciar servidor
15
+ * const { ip, port } = await HttpLocalServer.connect();
16
+ * console.log(`Servidor en http://${ip}:${port}`);
17
+ *
18
+ * // Escuchar peticiones
19
+ * await HttpLocalServer.addListener('onRequest', async (data) => {
20
+ * console.log('Petición recibida:', data);
21
+ *
22
+ * // Procesar y responder
23
+ * await HttpLocalServer.sendResponse({
24
+ * requestId: data.requestId,
25
+ * body: JSON.stringify({ success: true })
26
+ * });
27
+ * });
28
+ * ```
8
29
  */
9
30
  const HttpLocalServer = core.registerPlugin('HttpLocalServer', {
10
31
  web: () => Promise.resolve().then(function () { return web; }).then(m => new m.HttpLocalServerWeb()),
11
32
  });
12
33
 
34
+ /**
35
+ * Implementación web del plugin HttpLocalServer.
36
+ * Proporciona funcionalidad mock para desarrollo en navegador.
37
+ */
13
38
  class HttpLocalServerWeb extends core.WebPlugin {
39
+ constructor() {
40
+ super(...arguments);
41
+ this.isRunning = false;
42
+ }
14
43
  async connect() {
15
- console.warn('HttpLocalServer: El servidor nativo no se puede iniciar en el navegador.');
16
- // Devolvemos un valor por defecto para no romper la ejecución en web
17
- return { ip: '127.0.0.1', port: 8080 };
44
+ if (this.isRunning) {
45
+ console.warn('HttpLocalServer: El servidor ya está ejecutándose (Mock).');
46
+ return { ip: '127.0.0.1', port: 8080 };
47
+ }
48
+ console.warn('HttpLocalServer: El servidor nativo no está disponible en navegador. ' +
49
+ 'Retornando valores mock para desarrollo.');
50
+ this.isRunning = true;
51
+ return {
52
+ ip: '127.0.0.1',
53
+ port: 8080
54
+ };
18
55
  }
19
56
  async disconnect() {
57
+ if (!this.isRunning) {
58
+ console.log('HttpLocalServer: El servidor ya está detenido (Mock).');
59
+ return;
60
+ }
20
61
  console.log('HttpLocalServer: Servidor detenido (Mock).');
62
+ this.isRunning = false;
21
63
  }
22
64
  async sendResponse(options) {
23
- console.log('HttpLocalServer: Respuesta mock enviada al requestId:', options.requestId);
65
+ if (!this.isRunning) {
66
+ console.warn('HttpLocalServer: El servidor no está ejecutándose (Mock).');
67
+ return;
68
+ }
69
+ const { requestId, body } = options;
70
+ if (!requestId) {
71
+ throw new Error('Missing requestId');
72
+ }
73
+ if (!body) {
74
+ throw new Error('Missing body');
75
+ }
76
+ console.log(`HttpLocalServer: Respuesta mock enviada para requestId: ${requestId}`, '\nBody:', body.substring(0, 100) + (body.length > 100 ? '...' : ''));
24
77
  }
25
78
  }
26
79
 
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\n/**\n * Registramos el plugin.\n * El nombre 'HttpLocalServer' debe coincidir con el @CapacitorPlugin en Java\n * y el @objc(HttpLocalServerPlugin) en Swift.\n */\nconst HttpLocalServer = registerPlugin('HttpLocalServer', {\n web: () => import('./web').then(m => new m.HttpLocalServerWeb()),\n});\n// Exportamos las interfaces para que el usuario pueda tipar sus variables\nexport * from './definitions';\n// Exportamos el objeto del plugin\nexport { HttpLocalServer };\n// Exportación por defecto opcional para mayor compatibilidad\nexport default HttpLocalServer;\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class HttpLocalServerWeb extends WebPlugin {\n async connect() {\n console.warn('HttpLocalServer: El servidor nativo no se puede iniciar en el navegador.');\n // Devolvemos un valor por defecto para no romper la ejecución en web\n return { ip: '127.0.0.1', port: 8080 };\n }\n async disconnect() {\n console.log('HttpLocalServer: Servidor detenido (Mock).');\n }\n async sendResponse(options) {\n console.log('HttpLocalServer: Respuesta mock enviada al requestId:', options.requestId);\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;IACA;IACA;IACA;IACA;IACA;AACK,UAAC,eAAe,GAAGA,mBAAc,CAAC,iBAAiB,EAAE;IAC1D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,kBAAkB,EAAE,CAAC;IACpE,CAAC;;ICPM,MAAM,kBAAkB,SAASC,cAAS,CAAC;IAClD,IAAI,MAAM,OAAO,GAAG;IACpB,QAAQ,OAAO,CAAC,IAAI,CAAC,0EAA0E,CAAC;IAChG;IACA,QAAQ,OAAO,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE;IAC9C,IAAI;IACJ,IAAI,MAAM,UAAU,GAAG;IACvB,QAAQ,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC;IACjE,IAAI;IACJ,IAAI,MAAM,YAAY,CAAC,OAAO,EAAE;IAChC,QAAQ,OAAO,CAAC,GAAG,CAAC,uDAAuD,EAAE,OAAO,CAAC,SAAS,CAAC;IAC/F,IAAI;IACJ;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\n/**\n * Plugin de servidor HTTP local para Android e iOS.\n *\n * Permite crear un servidor HTTP en el dispositivo que puede recibir\n * peticiones desde otros dispositivos en la misma red.\n *\n * @example\n * ```typescript\n * import { HttpLocalServer } from '@cappitolian/http-local-server';\n *\n * // Iniciar servidor\n * const { ip, port } = await HttpLocalServer.connect();\n * console.log(`Servidor en http://${ip}:${port}`);\n *\n * // Escuchar peticiones\n * await HttpLocalServer.addListener('onRequest', async (data) => {\n * console.log('Petición recibida:', data);\n *\n * // Procesar y responder\n * await HttpLocalServer.sendResponse({\n * requestId: data.requestId,\n * body: JSON.stringify({ success: true })\n * });\n * });\n * ```\n */\nconst HttpLocalServer = registerPlugin('HttpLocalServer', {\n web: () => import('./web').then(m => new m.HttpLocalServerWeb()),\n});\nexport * from './definitions';\nexport { HttpLocalServer };\nexport default HttpLocalServer;\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\n/**\n * Implementación web del plugin HttpLocalServer.\n * Proporciona funcionalidad mock para desarrollo en navegador.\n */\nexport class HttpLocalServerWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.isRunning = false;\n }\n async connect() {\n if (this.isRunning) {\n console.warn('HttpLocalServer: El servidor ya está ejecutándose (Mock).');\n return { ip: '127.0.0.1', port: 8080 };\n }\n console.warn('HttpLocalServer: El servidor nativo no está disponible en navegador. ' +\n 'Retornando valores mock para desarrollo.');\n this.isRunning = true;\n return {\n ip: '127.0.0.1',\n port: 8080\n };\n }\n async disconnect() {\n if (!this.isRunning) {\n console.log('HttpLocalServer: El servidor ya está detenido (Mock).');\n return;\n }\n console.log('HttpLocalServer: Servidor detenido (Mock).');\n this.isRunning = false;\n }\n async sendResponse(options) {\n if (!this.isRunning) {\n console.warn('HttpLocalServer: El servidor no está ejecutándose (Mock).');\n return;\n }\n const { requestId, body } = options;\n if (!requestId) {\n throw new Error('Missing requestId');\n }\n if (!body) {\n throw new Error('Missing body');\n }\n console.log(`HttpLocalServer: Respuesta mock enviada para requestId: ${requestId}`, '\\nBody:', body.substring(0, 100) + (body.length > 100 ? '...' : ''));\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;AACK,UAAC,eAAe,GAAGA,mBAAc,CAAC,iBAAiB,EAAE;IAC1D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,kBAAkB,EAAE,CAAC;IACpE,CAAC;;IC5BD;IACA;IACA;IACA;IACO,MAAM,kBAAkB,SAASC,cAAS,CAAC;IAClD,IAAI,WAAW,GAAG;IAClB,QAAQ,KAAK,CAAC,GAAG,SAAS,CAAC;IAC3B,QAAQ,IAAI,CAAC,SAAS,GAAG,KAAK;IAC9B,IAAI;IACJ,IAAI,MAAM,OAAO,GAAG;IACpB,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE;IAC5B,YAAY,OAAO,CAAC,IAAI,CAAC,2DAA2D,CAAC;IACrF,YAAY,OAAO,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE;IAClD,QAAQ;IACR,QAAQ,OAAO,CAAC,IAAI,CAAC,uEAAuE;IAC5F,YAAY,0CAA0C,CAAC;IACvD,QAAQ,IAAI,CAAC,SAAS,GAAG,IAAI;IAC7B,QAAQ,OAAO;IACf,YAAY,EAAE,EAAE,WAAW;IAC3B,YAAY,IAAI,EAAE;IAClB,SAAS;IACT,IAAI;IACJ,IAAI,MAAM,UAAU,GAAG;IACvB,QAAQ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE;IAC7B,YAAY,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC;IAChF,YAAY;IACZ,QAAQ;IACR,QAAQ,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC;IACjE,QAAQ,IAAI,CAAC,SAAS,GAAG,KAAK;IAC9B,IAAI;IACJ,IAAI,MAAM,YAAY,CAAC,OAAO,EAAE;IAChC,QAAQ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE;IAC7B,YAAY,OAAO,CAAC,IAAI,CAAC,2DAA2D,CAAC;IACrF,YAAY;IACZ,QAAQ;IACR,QAAQ,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO;IAC3C,QAAQ,IAAI,CAAC,SAAS,EAAE;IACxB,YAAY,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC;IAChD,QAAQ;IACR,QAAQ,IAAI,CAAC,IAAI,EAAE;IACnB,YAAY,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC;IAC3C,QAAQ;IACR,QAAQ,OAAO,CAAC,GAAG,CAAC,CAAC,wDAAwD,EAAE,SAAS,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG,GAAG,KAAK,GAAG,EAAE,CAAC,CAAC;IACjK,IAAI;IACJ;;;;;;;;;;;;;;;;;;"}
@@ -20,104 +20,76 @@ import Foundation
20
20
  import GCDWebServer
21
21
  import Capacitor
22
22
 
23
+ // MARK: - Protocol
23
24
  public protocol HttpLocalServerDelegate: AnyObject {
24
25
  func httpLocalServerDidReceiveRequest(_ data: [String: Any])
25
26
  }
26
27
 
28
+ // MARK: - HttpLocalServer
27
29
  @objc public class HttpLocalServer: NSObject {
28
- var webServer: GCDWebServer?
29
- weak var delegate: HttpLocalServerDelegate?
30
- static var pendingResponses = [String: (String) -> Void]()
31
- static let queue = DispatchQueue(label: "HttpLocalServer.pendingResponses")
32
-
30
+
31
+ // MARK: - Properties
32
+ private var webServer: GCDWebServer?
33
+ private weak var delegate: HttpLocalServerDelegate?
34
+
35
+ private static var pendingResponses = [String: (String) -> Void]()
36
+ private static let queue = DispatchQueue(label: "com.cappitolian.HttpLocalServer.pendingResponses", qos: .userInitiated)
37
+
38
+ private let defaultTimeout: TimeInterval = 5.0
39
+ private let defaultPort: UInt = 8080
40
+
41
+ // MARK: - Initialization
33
42
  public init(delegate: HttpLocalServerDelegate) {
34
43
  self.delegate = delegate
44
+ super.init()
35
45
  }
36
-
46
+
47
+ deinit {
48
+ disconnect()
49
+ }
50
+
51
+ // MARK: - Public Methods
37
52
  @objc public func connect(_ call: CAPPluginCall) {
38
53
  DispatchQueue.main.async { [weak self] in
39
- guard let self = self else { return }
40
-
54
+ guard let self = self else {
55
+ call.reject("Server instance deallocated")
56
+ return
57
+ }
58
+
59
+ // Stop existing server if running
60
+ if self.webServer?.isRunning == true {
61
+ self.webServer?.stop()
62
+ }
63
+
41
64
  self.webServer = GCDWebServer()
42
-
43
- self.webServer?.addHandler(
44
- match: { method, url, headers, path, query in
45
- GCDWebServerRequest(method: method, url: url, headers: headers, path: path, query: query)
46
- },
47
- processBlock: { request in
48
- let method = request.method
49
- let path = request.url.path
50
- var body: String? = nil
51
-
52
- if let dataRequest = request as? GCDWebServerDataRequest, let text = String(data: dataRequest.data, encoding: .utf8) {
53
- body = text
54
- }
55
-
56
- let requestId = UUID().uuidString
57
- var responseString: String? = nil
58
-
59
- // Set up a semaphore so we can block until JS responds or timeout (3s)
60
- let semaphore = DispatchSemaphore(value: 0)
61
- Self.queue.async {
62
- Self.pendingResponses[requestId] = { responseBody in
63
- responseString = responseBody
64
- semaphore.signal()
65
- }
66
- }
67
-
68
- // Notify delegate (plugin) with the request info
69
- let req: [String: Any?] = [
70
- "requestId": requestId,
71
- "method": method,
72
- "path": path,
73
- "body": body
74
- ]
75
- self.delegate?.httpLocalServerDidReceiveRequest(req.compactMapValues { $0 })
76
-
77
- // Wait for JS response or timeout
78
- _ = semaphore.wait(timeout: .now() + 3.0)
79
- Self.queue.async {
80
- Self.pendingResponses.removeValue(forKey: requestId)
81
- }
82
- let reply = responseString ?? "{\"error\":\"Timeout waiting for JS response\"}"
83
-
84
- let response = GCDWebServerDataResponse(text: reply)
85
- response?.setValue("*", forAdditionalHeader: "Access-Control-Allow-Origin")
86
- response?.setValue("GET,POST,OPTIONS", forAdditionalHeader: "Access-Control-Allow-Methods")
87
- response?.setValue("origin, content-type, accept, authorization", forAdditionalHeader: "Access-Control-Allow-Headers")
88
- response?.setValue("3600", forAdditionalHeader: "Access-Control-Max-Age")
89
- response?.contentType = "application/json"
90
- return response!
91
- }
92
- )
93
-
94
- let port: UInt = 8080
65
+ self.setupHandlers()
66
+
95
67
  do {
96
- try self.webServer?.start(options: [
97
- GCDWebServerOption_Port: port,
98
- GCDWebServerOption_BonjourName: "",
99
- GCDWebServerOption_BindToLocalhost: false
100
- ])
68
+ try self.startServer()
101
69
  let ip = Self.getWiFiAddress() ?? "127.0.0.1"
102
70
  call.resolve([
103
71
  "ip": ip,
104
- "port": port
72
+ "port": self.defaultPort
105
73
  ])
106
74
  } catch {
107
75
  call.reject("Failed to start server: \(error.localizedDescription)")
108
76
  }
109
77
  }
110
78
  }
111
-
112
- @objc public func disconnect(_ call: CAPPluginCall) {
79
+
80
+ @objc public func disconnect(_ call: CAPPluginCall? = nil) {
113
81
  DispatchQueue.main.async { [weak self] in
114
- self?.webServer?.stop()
115
- self?.webServer = nil
116
- call.resolve()
82
+ guard let self = self else {
83
+ call?.reject("Server instance deallocated")
84
+ return
85
+ }
86
+
87
+ self.disconnect()
88
+ call?.resolve()
117
89
  }
118
90
  }
119
-
120
- // Called by plugin when JS responds
91
+
92
+ // MARK: - Static Methods
121
93
  static func handleJsResponse(requestId: String, body: String) {
122
94
  queue.async {
123
95
  if let callback = pendingResponses[requestId] {
@@ -126,32 +98,238 @@ public protocol HttpLocalServerDelegate: AnyObject {
126
98
  }
127
99
  }
128
100
  }
129
-
130
- // Helper: get WiFi IP address (IPv4)
101
+
102
+ // MARK: - Private Methods
103
+ private func disconnect() {
104
+ webServer?.stop()
105
+ webServer = nil
106
+
107
+ // Clear pending responses
108
+ Self.queue.async {
109
+ Self.pendingResponses.removeAll()
110
+ }
111
+ }
112
+
113
+ private func setupHandlers() {
114
+ guard let webServer = webServer else { return }
115
+
116
+ // GET requests
117
+ webServer.addDefaultHandler(
118
+ forMethod: "GET",
119
+ request: GCDWebServerRequest.self,
120
+ processBlock: { [weak self] request in
121
+ return self?.processRequest(request) ?? self?.errorResponse() ?? GCDWebServerResponse()
122
+ }
123
+ )
124
+
125
+ // POST requests (with body)
126
+ webServer.addDefaultHandler(
127
+ forMethod: "POST",
128
+ request: GCDWebServerDataRequest.self,
129
+ processBlock: { [weak self] request in
130
+ return self?.processRequest(request) ?? self?.errorResponse() ?? GCDWebServerResponse()
131
+ }
132
+ )
133
+
134
+ // PUT requests (with body)
135
+ webServer.addDefaultHandler(
136
+ forMethod: "PUT",
137
+ request: GCDWebServerDataRequest.self,
138
+ processBlock: { [weak self] request in
139
+ return self?.processRequest(request) ?? self?.errorResponse() ?? GCDWebServerResponse()
140
+ }
141
+ )
142
+
143
+ // PATCH requests (with body)
144
+ webServer.addDefaultHandler(
145
+ forMethod: "PATCH",
146
+ request: GCDWebServerDataRequest.self,
147
+ processBlock: { [weak self] request in
148
+ return self?.processRequest(request) ?? self?.errorResponse() ?? GCDWebServerResponse()
149
+ }
150
+ )
151
+
152
+ // DELETE requests
153
+ webServer.addDefaultHandler(
154
+ forMethod: "DELETE",
155
+ request: GCDWebServerRequest.self,
156
+ processBlock: { [weak self] request in
157
+ return self?.processRequest(request) ?? self?.errorResponse() ?? GCDWebServerResponse()
158
+ }
159
+ )
160
+
161
+ // OPTIONS requests (CORS preflight)
162
+ webServer.addDefaultHandler(
163
+ forMethod: "OPTIONS",
164
+ request: GCDWebServerRequest.self,
165
+ processBlock: { [weak self] request in
166
+ return self?.corsResponse() ?? GCDWebServerResponse()
167
+ }
168
+ )
169
+ }
170
+
171
+ private func processRequest(_ request: GCDWebServerRequest) -> GCDWebServerResponse {
172
+ let method = request.method
173
+ let path = request.url.path
174
+ let body = extractBody(from: request)
175
+ let headers = request.headers
176
+ let query = request.query
177
+
178
+ let requestId = UUID().uuidString
179
+ var responseString: String?
180
+
181
+ // Setup semaphore for synchronous waiting
182
+ let semaphore = DispatchSemaphore(value: 0)
183
+
184
+ Self.queue.async {
185
+ Self.pendingResponses[requestId] = { responseBody in
186
+ responseString = responseBody
187
+ semaphore.signal()
188
+ }
189
+ }
190
+
191
+ // Notify delegate with request info
192
+ var requestData: [String: Any] = [
193
+ "requestId": requestId,
194
+ "method": method,
195
+ "path": path
196
+ ]
197
+
198
+ // Solo agregar si existen (consistente con TypeScript y Android)
199
+ if let body = body, !body.isEmpty {
200
+ requestData["body"] = body
201
+ }
202
+
203
+ if let headers = headers as? [String: String], !headers.isEmpty {
204
+ requestData["headers"] = headers
205
+ }
206
+
207
+ if let query = query, !query.isEmpty {
208
+ requestData["query"] = query
209
+ }
210
+
211
+ delegate?.httpLocalServerDidReceiveRequest(requestData)
212
+
213
+ // Wait for JS response or timeout
214
+ let result = semaphore.wait(timeout: .now() + defaultTimeout)
215
+
216
+ // Cleanup
217
+ Self.queue.async {
218
+ Self.pendingResponses.removeValue(forKey: requestId)
219
+ }
220
+
221
+ // Handle timeout
222
+ if result == .timedOut {
223
+ let timeoutResponse = "{\"error\":\"Request timeout\",\"requestId\":\"\(requestId)\"}"
224
+ return createJsonResponse(timeoutResponse, statusCode: 408)
225
+ }
226
+
227
+ let reply = responseString ?? "{\"error\":\"No response from handler\"}"
228
+ return createJsonResponse(reply)
229
+ }
230
+
231
+ private func extractBody(from request: GCDWebServerRequest) -> String? {
232
+ guard let dataRequest = request as? GCDWebServerDataRequest else {
233
+ return nil
234
+ }
235
+
236
+ return String(data: dataRequest.data, encoding: .utf8)
237
+ }
238
+
239
+ private func createJsonResponse(_ body: String, statusCode: Int = 200) -> GCDWebServerDataResponse {
240
+ let response = GCDWebServerDataResponse(text: body)
241
+ response?.statusCode = statusCode
242
+ response?.contentType = "application/json"
243
+
244
+ // CORS headers
245
+ response?.setValue("*", forAdditionalHeader: "Access-Control-Allow-Origin")
246
+ response?.setValue("GET, POST, PUT, PATCH, DELETE, OPTIONS", forAdditionalHeader: "Access-Control-Allow-Methods")
247
+ response?.setValue("Origin, Content-Type, Accept, Authorization", forAdditionalHeader: "Access-Control-Allow-Headers")
248
+ response?.setValue("true", forAdditionalHeader: "Access-Control-Allow-Credentials")
249
+ response?.setValue("3600", forAdditionalHeader: "Access-Control-Max-Age")
250
+
251
+ return response ?? GCDWebServerDataResponse()
252
+ }
253
+
254
+ private func corsResponse() -> GCDWebServerDataResponse {
255
+ return createJsonResponse("{}", statusCode: 204)
256
+ }
257
+
258
+ private func errorResponse() -> GCDWebServerDataResponse {
259
+ return createJsonResponse("{\"error\":\"Server error\"}", statusCode: 500)
260
+ }
261
+
262
+ private func startServer() throws {
263
+ guard let webServer = webServer else {
264
+ throw NSError(
265
+ domain: "HttpLocalServer",
266
+ code: -1,
267
+ userInfo: [NSLocalizedDescriptionKey: "WebServer not initialized"]
268
+ )
269
+ }
270
+
271
+ let options: [String: Any] = [
272
+ GCDWebServerOption_Port: defaultPort,
273
+ GCDWebServerOption_BonjourName: "",
274
+ GCDWebServerOption_BindToLocalhost: false,
275
+ GCDWebServerOption_AutomaticallySuspendInBackground: false
276
+ ]
277
+
278
+ try webServer.start(options: options)
279
+ }
280
+
281
+ // MARK: - Network Utilities
131
282
  static func getWiFiAddress() -> String? {
132
283
  var address: String?
133
284
  var ifaddr: UnsafeMutablePointer<ifaddrs>?
134
- if getifaddrs(&ifaddr) == 0 {
135
- var ptr = ifaddr
136
- while ptr != nil {
137
- let interface = ptr!.pointee
138
- let addrFamily = interface.ifa_addr.pointee.sa_family
139
- if addrFamily == UInt8(AF_INET) {
140
- let name = String(cString: interface.ifa_name)
141
- if name == "en0" { // WiFi interface
142
- var addr = interface.ifa_addr.pointee
143
- var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
144
- getnameinfo(&addr, socklen_t(interface.ifa_addr.pointee.sa_len),
145
- &hostname, socklen_t(hostname.count),
146
- nil, socklen_t(0), NI_NUMERICHOST)
147
- address = String(cString: hostname)
148
- break
149
- }
150
- }
151
- ptr = interface.ifa_next
152
- }
285
+
286
+ guard getifaddrs(&ifaddr) == 0 else {
287
+ return nil
288
+ }
289
+
290
+ defer {
153
291
  freeifaddrs(ifaddr)
154
292
  }
293
+
294
+ var ptr = ifaddr
295
+ while ptr != nil {
296
+ defer { ptr = ptr?.pointee.ifa_next }
297
+
298
+ guard let interface = ptr?.pointee else { continue }
299
+
300
+ let addrFamily = interface.ifa_addr.pointee.sa_family
301
+
302
+ // Check for IPv4 interface
303
+ guard addrFamily == UInt8(AF_INET) else { continue }
304
+
305
+ let name = String(cString: interface.ifa_name)
306
+
307
+ // WiFi interface (en0) or cellular (pdp_ip0)
308
+ guard name == "en0" || name == "pdp_ip0" else { continue }
309
+
310
+ var addr = interface.ifa_addr.pointee
311
+ var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
312
+
313
+ let result = getnameinfo(
314
+ &addr,
315
+ socklen_t(interface.ifa_addr.pointee.sa_len),
316
+ &hostname,
317
+ socklen_t(hostname.count),
318
+ nil,
319
+ 0,
320
+ NI_NUMERICHOST
321
+ )
322
+
323
+ guard result == 0 else { continue }
324
+
325
+ address = String(cString: hostname)
326
+
327
+ // Prefer en0 (WiFi) over pdp_ip0 (cellular)
328
+ if name == "en0" {
329
+ break
330
+ }
331
+ }
332
+
155
333
  return address
156
334
  }
157
- }
335
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cappitolian/http-local-server",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
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",