@cappitolian/http-local-server-swifter 0.0.1

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.
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+
3
+ var core = require('@capacitor/core');
4
+
5
+ /**
6
+ * Plugin de servidor HTTP local para Android e iOS.
7
+ *
8
+ * Permite crear un servidor HTTP en el dispositivo que puede recibir
9
+ * peticiones desde otros dispositivos en la misma red local.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { HttpLocalServerSwifter } from '@cappitolian/http-local-server-swifter';
14
+ *
15
+ * // Iniciar servidor
16
+ * const { ip, port } = await HttpLocalServerSwifter.connect();
17
+ * console.log(`Servidor en http://${ip}:${port}`);
18
+ *
19
+ * // Escuchar peticiones
20
+ * await HttpLocalServerSwifter.addListener('onRequest', async (data) => {
21
+ * console.log('Petición recibida:', data);
22
+ *
23
+ * // Procesar y responder
24
+ * await HttpLocalServerSwifter.sendResponse({
25
+ * requestId: data.requestId,
26
+ * body: JSON.stringify({ success: true })
27
+ * });
28
+ * });
29
+ *
30
+ * // Detener servidor
31
+ * await HttpLocalServerSwifter.disconnect();
32
+ * ```
33
+ */
34
+ const HttpLocalServerSwifter = core.registerPlugin('HttpLocalServerSwifter', {
35
+ web: () => Promise.resolve().then(function () { return web; }).then(m => new m.HttpLocalServerSwifterWeb()),
36
+ });
37
+
38
+ /**
39
+ * Implementación web (mock) del plugin HttpLocalServerSwifter.
40
+ *
41
+ * Proporciona funcionalidad simulada para desarrollo en navegador.
42
+ * El servidor real solo funciona en dispositivos iOS/Android nativos.
43
+ */
44
+ class HttpLocalServerSwifterWeb extends core.WebPlugin {
45
+ constructor() {
46
+ super(...arguments);
47
+ this.isRunning = false;
48
+ }
49
+ async connect() {
50
+ if (this.isRunning) {
51
+ console.warn('[HttpLocalServerSwifter Web] El servidor ya está ejecutándose (Mock).');
52
+ return { ip: '127.0.0.1', port: 8080 };
53
+ }
54
+ console.warn('[HttpLocalServerSwifter Web] El servidor HTTP nativo no está disponible en navegador.\n' +
55
+ 'Retornando valores mock para desarrollo.\n' +
56
+ 'Para funcionalidad real, ejecuta en un dispositivo iOS/Android.');
57
+ this.isRunning = true;
58
+ return {
59
+ ip: '127.0.0.1',
60
+ port: 8080
61
+ };
62
+ }
63
+ async disconnect() {
64
+ if (!this.isRunning) {
65
+ console.log('[HttpLocalServerSwifter Web] El servidor ya está detenido (Mock).');
66
+ return;
67
+ }
68
+ console.log('[HttpLocalServerSwifter Web] Servidor detenido (Mock).');
69
+ this.isRunning = false;
70
+ }
71
+ async sendResponse(options) {
72
+ if (!this.isRunning) {
73
+ throw new Error('Server is not running. Call connect() first.');
74
+ }
75
+ const { requestId, body } = options;
76
+ if (!requestId) {
77
+ throw new Error('Missing requestId');
78
+ }
79
+ if (!body) {
80
+ throw new Error('Missing body');
81
+ }
82
+ console.log(`[HttpLocalServerSwifter Web] Mock response sent for requestId: ${requestId}`, '\nBody preview:', body.substring(0, 100) + (body.length > 100 ? '...' : ''));
83
+ }
84
+ }
85
+
86
+ var web = /*#__PURE__*/Object.freeze({
87
+ __proto__: null,
88
+ HttpLocalServerSwifterWeb: HttpLocalServerSwifterWeb
89
+ });
90
+
91
+ exports.HttpLocalServerSwifter = HttpLocalServerSwifter;
92
+ //# sourceMappingURL=plugin.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.cjs.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 local.\n *\n * @example\n * ```typescript\n * import { HttpLocalServerSwifter } from '@cappitolian/http-local-server-swifter';\n *\n * // Iniciar servidor\n * const { ip, port } = await HttpLocalServerSwifter.connect();\n * console.log(`Servidor en http://${ip}:${port}`);\n *\n * // Escuchar peticiones\n * await HttpLocalServerSwifter.addListener('onRequest', async (data) => {\n * console.log('Petición recibida:', data);\n *\n * // Procesar y responder\n * await HttpLocalServerSwifter.sendResponse({\n * requestId: data.requestId,\n * body: JSON.stringify({ success: true })\n * });\n * });\n *\n * // Detener servidor\n * await HttpLocalServerSwifter.disconnect();\n * ```\n */\nconst HttpLocalServerSwifter = registerPlugin('HttpLocalServerSwifter', {\n web: () => import('./web').then(m => new m.HttpLocalServerSwifterWeb()),\n});\nexport * from './definitions';\nexport { HttpLocalServerSwifter };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\n/**\n * Implementación web (mock) del plugin HttpLocalServerSwifter.\n *\n * Proporciona funcionalidad simulada para desarrollo en navegador.\n * El servidor real solo funciona en dispositivos iOS/Android nativos.\n */\nexport class HttpLocalServerSwifterWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.isRunning = false;\n }\n async connect() {\n if (this.isRunning) {\n console.warn('[HttpLocalServerSwifter Web] El servidor ya está ejecutándose (Mock).');\n return { ip: '127.0.0.1', port: 8080 };\n }\n console.warn('[HttpLocalServerSwifter Web] El servidor HTTP nativo no está disponible en navegador.\\n' +\n 'Retornando valores mock para desarrollo.\\n' +\n 'Para funcionalidad real, ejecuta en un dispositivo iOS/Android.');\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('[HttpLocalServerSwifter Web] El servidor ya está detenido (Mock).');\n return;\n }\n console.log('[HttpLocalServerSwifter Web] Servidor detenido (Mock).');\n this.isRunning = false;\n }\n async sendResponse(options) {\n if (!this.isRunning) {\n throw new Error('Server is not running. Call connect() first.');\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(`[HttpLocalServerSwifter Web] Mock response sent for requestId: ${requestId}`, '\\nBody preview:', body.substring(0, 100) + (body.length > 100 ? '...' : ''));\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACK,MAAC,sBAAsB,GAAGA,mBAAc,CAAC,wBAAwB,EAAE;AACxE,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,yBAAyB,EAAE,CAAC;AAC3E,CAAC;;AC/BD;AACA;AACA;AACA;AACA;AACA;AACO,MAAM,yBAAyB,SAASC,cAAS,CAAC;AACzD,IAAI,WAAW,GAAG;AAClB,QAAQ,KAAK,CAAC,GAAG,SAAS,CAAC;AAC3B,QAAQ,IAAI,CAAC,SAAS,GAAG,KAAK;AAC9B,IAAI;AACJ,IAAI,MAAM,OAAO,GAAG;AACpB,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE;AAC5B,YAAY,OAAO,CAAC,IAAI,CAAC,uEAAuE,CAAC;AACjG,YAAY,OAAO,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE;AAClD,QAAQ;AACR,QAAQ,OAAO,CAAC,IAAI,CAAC,yFAAyF;AAC9G,YAAY,4CAA4C;AACxD,YAAY,iEAAiE,CAAC;AAC9E,QAAQ,IAAI,CAAC,SAAS,GAAG,IAAI;AAC7B,QAAQ,OAAO;AACf,YAAY,EAAE,EAAE,WAAW;AAC3B,YAAY,IAAI,EAAE;AAClB,SAAS;AACT,IAAI;AACJ,IAAI,MAAM,UAAU,GAAG;AACvB,QAAQ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE;AAC7B,YAAY,OAAO,CAAC,GAAG,CAAC,mEAAmE,CAAC;AAC5F,YAAY;AACZ,QAAQ;AACR,QAAQ,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC;AAC7E,QAAQ,IAAI,CAAC,SAAS,GAAG,KAAK;AAC9B,IAAI;AACJ,IAAI,MAAM,YAAY,CAAC,OAAO,EAAE;AAChC,QAAQ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE;AAC7B,YAAY,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC;AAC3E,QAAQ;AACR,QAAQ,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO;AAC3C,QAAQ,IAAI,CAAC,SAAS,EAAE;AACxB,YAAY,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC;AAChD,QAAQ;AACR,QAAQ,IAAI,CAAC,IAAI,EAAE;AACnB,YAAY,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC;AAC3C,QAAQ;AACR,QAAQ,OAAO,CAAC,GAAG,CAAC,CAAC,+DAA+D,EAAE,SAAS,CAAC,CAAC,EAAE,iBAAiB,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG,GAAG,KAAK,GAAG,EAAE,CAAC,CAAC;AAChL,IAAI;AACJ;;;;;;;;;"}
package/dist/plugin.js ADDED
@@ -0,0 +1,95 @@
1
+ var capacitorHttpLocalServerSwifter = (function (exports, core) {
2
+ 'use strict';
3
+
4
+ /**
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 local.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { HttpLocalServerSwifter } from '@cappitolian/http-local-server-swifter';
13
+ *
14
+ * // Iniciar servidor
15
+ * const { ip, port } = await HttpLocalServerSwifter.connect();
16
+ * console.log(`Servidor en http://${ip}:${port}`);
17
+ *
18
+ * // Escuchar peticiones
19
+ * await HttpLocalServerSwifter.addListener('onRequest', async (data) => {
20
+ * console.log('Petición recibida:', data);
21
+ *
22
+ * // Procesar y responder
23
+ * await HttpLocalServerSwifter.sendResponse({
24
+ * requestId: data.requestId,
25
+ * body: JSON.stringify({ success: true })
26
+ * });
27
+ * });
28
+ *
29
+ * // Detener servidor
30
+ * await HttpLocalServerSwifter.disconnect();
31
+ * ```
32
+ */
33
+ const HttpLocalServerSwifter = core.registerPlugin('HttpLocalServerSwifter', {
34
+ web: () => Promise.resolve().then(function () { return web; }).then(m => new m.HttpLocalServerSwifterWeb()),
35
+ });
36
+
37
+ /**
38
+ * Implementación web (mock) del plugin HttpLocalServerSwifter.
39
+ *
40
+ * Proporciona funcionalidad simulada para desarrollo en navegador.
41
+ * El servidor real solo funciona en dispositivos iOS/Android nativos.
42
+ */
43
+ class HttpLocalServerSwifterWeb extends core.WebPlugin {
44
+ constructor() {
45
+ super(...arguments);
46
+ this.isRunning = false;
47
+ }
48
+ async connect() {
49
+ if (this.isRunning) {
50
+ console.warn('[HttpLocalServerSwifter Web] El servidor ya está ejecutándose (Mock).');
51
+ return { ip: '127.0.0.1', port: 8080 };
52
+ }
53
+ console.warn('[HttpLocalServerSwifter Web] El servidor HTTP nativo no está disponible en navegador.\n' +
54
+ 'Retornando valores mock para desarrollo.\n' +
55
+ 'Para funcionalidad real, ejecuta en un dispositivo iOS/Android.');
56
+ this.isRunning = true;
57
+ return {
58
+ ip: '127.0.0.1',
59
+ port: 8080
60
+ };
61
+ }
62
+ async disconnect() {
63
+ if (!this.isRunning) {
64
+ console.log('[HttpLocalServerSwifter Web] El servidor ya está detenido (Mock).');
65
+ return;
66
+ }
67
+ console.log('[HttpLocalServerSwifter Web] Servidor detenido (Mock).');
68
+ this.isRunning = false;
69
+ }
70
+ async sendResponse(options) {
71
+ if (!this.isRunning) {
72
+ throw new Error('Server is not running. Call connect() first.');
73
+ }
74
+ const { requestId, body } = options;
75
+ if (!requestId) {
76
+ throw new Error('Missing requestId');
77
+ }
78
+ if (!body) {
79
+ throw new Error('Missing body');
80
+ }
81
+ console.log(`[HttpLocalServerSwifter Web] Mock response sent for requestId: ${requestId}`, '\nBody preview:', body.substring(0, 100) + (body.length > 100 ? '...' : ''));
82
+ }
83
+ }
84
+
85
+ var web = /*#__PURE__*/Object.freeze({
86
+ __proto__: null,
87
+ HttpLocalServerSwifterWeb: HttpLocalServerSwifterWeb
88
+ });
89
+
90
+ exports.HttpLocalServerSwifter = HttpLocalServerSwifter;
91
+
92
+ return exports;
93
+
94
+ })({}, capacitorExports);
95
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
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 local.\n *\n * @example\n * ```typescript\n * import { HttpLocalServerSwifter } from '@cappitolian/http-local-server-swifter';\n *\n * // Iniciar servidor\n * const { ip, port } = await HttpLocalServerSwifter.connect();\n * console.log(`Servidor en http://${ip}:${port}`);\n *\n * // Escuchar peticiones\n * await HttpLocalServerSwifter.addListener('onRequest', async (data) => {\n * console.log('Petición recibida:', data);\n *\n * // Procesar y responder\n * await HttpLocalServerSwifter.sendResponse({\n * requestId: data.requestId,\n * body: JSON.stringify({ success: true })\n * });\n * });\n *\n * // Detener servidor\n * await HttpLocalServerSwifter.disconnect();\n * ```\n */\nconst HttpLocalServerSwifter = registerPlugin('HttpLocalServerSwifter', {\n web: () => import('./web').then(m => new m.HttpLocalServerSwifterWeb()),\n});\nexport * from './definitions';\nexport { HttpLocalServerSwifter };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\n/**\n * Implementación web (mock) del plugin HttpLocalServerSwifter.\n *\n * Proporciona funcionalidad simulada para desarrollo en navegador.\n * El servidor real solo funciona en dispositivos iOS/Android nativos.\n */\nexport class HttpLocalServerSwifterWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.isRunning = false;\n }\n async connect() {\n if (this.isRunning) {\n console.warn('[HttpLocalServerSwifter Web] El servidor ya está ejecutándose (Mock).');\n return { ip: '127.0.0.1', port: 8080 };\n }\n console.warn('[HttpLocalServerSwifter Web] El servidor HTTP nativo no está disponible en navegador.\\n' +\n 'Retornando valores mock para desarrollo.\\n' +\n 'Para funcionalidad real, ejecuta en un dispositivo iOS/Android.');\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('[HttpLocalServerSwifter Web] El servidor ya está detenido (Mock).');\n return;\n }\n console.log('[HttpLocalServerSwifter Web] Servidor detenido (Mock).');\n this.isRunning = false;\n }\n async sendResponse(options) {\n if (!this.isRunning) {\n throw new Error('Server is not running. Call connect() first.');\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(`[HttpLocalServerSwifter Web] Mock response sent for requestId: ${requestId}`, '\\nBody preview:', 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;IACA;IACA;IACA;AACK,UAAC,sBAAsB,GAAGA,mBAAc,CAAC,wBAAwB,EAAE;IACxE,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,yBAAyB,EAAE,CAAC;IAC3E,CAAC;;IC/BD;IACA;IACA;IACA;IACA;IACA;IACO,MAAM,yBAAyB,SAASC,cAAS,CAAC;IACzD,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,uEAAuE,CAAC;IACjG,YAAY,OAAO,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE;IAClD,QAAQ;IACR,QAAQ,OAAO,CAAC,IAAI,CAAC,yFAAyF;IAC9G,YAAY,4CAA4C;IACxD,YAAY,iEAAiE,CAAC;IAC9E,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,mEAAmE,CAAC;IAC5F,YAAY;IACZ,QAAQ;IACR,QAAQ,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC;IAC7E,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,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC;IAC3E,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,+DAA+D,EAAE,SAAS,CAAC,CAAC,EAAE,iBAAiB,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG,GAAG,KAAK,GAAG,EAAE,CAAC,CAAC;IAChL,IAAI;IACJ;;;;;;;;;;;;;;;"}
@@ -0,0 +1,406 @@
1
+ import Foundation
2
+ import Swifter
3
+ import Capacitor
4
+
5
+ // MARK: - Protocol
6
+ public protocol HttpLocalServerSwifterDelegate: AnyObject {
7
+ func httpLocalServerSwifterDidReceiveRequest(_ data: [String: Any])
8
+ }
9
+
10
+ // MARK: - HttpLocalServerSwifter
11
+ @objc public class HttpLocalServerSwifter: NSObject {
12
+ // MARK: - Properties
13
+ private var webServer: HttpServer?
14
+ private weak var delegate: HttpLocalServerSwifterDelegate?
15
+
16
+ private static var pendingResponses = [String: (String) -> Void]()
17
+ private static let queue = DispatchQueue(label: "com.cappitolian.HttpLocalServerSwifter.pendingResponses", qos: .userInitiated)
18
+
19
+ private let defaultTimeout: TimeInterval = 5.0
20
+ private let defaultPort: UInt16 = 8080
21
+
22
+ // MARK: - Initialization
23
+ public init(delegate: HttpLocalServerSwifterDelegate) {
24
+ self.delegate = delegate
25
+ super.init()
26
+ }
27
+
28
+ deinit {
29
+ disconnect()
30
+ }
31
+
32
+ // MARK: - Public Methods
33
+ @objc public func connect(_ call: CAPPluginCall) {
34
+ DispatchQueue.main.async { [weak self] in
35
+ guard let self = self else {
36
+ call.reject("Server instance deallocated")
37
+ return
38
+ }
39
+
40
+ // Stop existing server if running
41
+ self.disconnect()
42
+
43
+ self.webServer = HttpServer()
44
+ self.setupHandlers()
45
+
46
+ do {
47
+ try self.startServer()
48
+ let ip = Self.getWiFiAddress() ?? "127.0.0.1"
49
+ call.resolve([
50
+ "ip": ip,
51
+ "port": self.defaultPort
52
+ ])
53
+ } catch {
54
+ call.reject("Failed to start server: \(error.localizedDescription)")
55
+ }
56
+ }
57
+ }
58
+
59
+ @objc public func disconnect(_ call: CAPPluginCall? = nil) {
60
+ DispatchQueue.main.async { [weak self] in
61
+ guard let self = self else {
62
+ call?.reject("Server instance deallocated")
63
+ return
64
+ }
65
+
66
+ self.disconnect()
67
+ call?.resolve()
68
+ }
69
+ }
70
+
71
+ // MARK: - Static Methods
72
+ static func handleJsResponse(requestId: String, body: String) {
73
+ queue.async {
74
+ if let callback = pendingResponses[requestId] {
75
+ callback(body)
76
+ pendingResponses.removeValue(forKey: requestId)
77
+ }
78
+ }
79
+ }
80
+
81
+ // MARK: - Private Methods
82
+ private func disconnect() {
83
+ webServer?.stop()
84
+ webServer = nil
85
+
86
+ // Clear pending responses
87
+ Self.queue.async {
88
+ Self.pendingResponses.removeAll()
89
+ }
90
+ }
91
+
92
+ private func setupHandlers() {
93
+ guard let server = webServer else { return }
94
+
95
+ // Catch-all handler for all HTTP methods
96
+ server["/(.*)"] = { [weak self] request in
97
+ guard let self = self else {
98
+ return self?.errorResponse() ?? .internalServerError
99
+ }
100
+
101
+ // Handle OPTIONS (CORS preflight)
102
+ if request.method == "OPTIONS" {
103
+ return self.corsResponse()
104
+ }
105
+
106
+ return self.processRequest(request)
107
+ }
108
+ }
109
+
110
+ private func processRequest(_ request: HttpRequest) -> HttpResponse {
111
+ let method = request.method
112
+ let path = request.path
113
+ let body = extractBody(from: request)
114
+ let headers = extractHeaders(from: request)
115
+ let query = extractQuery(from: request)
116
+
117
+ let requestId = UUID().uuidString
118
+ var responseString: String?
119
+
120
+ // Setup semaphore for synchronous waiting
121
+ let semaphore = DispatchSemaphore(value: 0)
122
+
123
+ Self.queue.async {
124
+ Self.pendingResponses[requestId] = { responseBody in
125
+ responseString = responseBody
126
+ semaphore.signal()
127
+ }
128
+ }
129
+
130
+ // Notify delegate with request info
131
+ var requestData: [String: Any] = [
132
+ "requestId": requestId,
133
+ "method": method,
134
+ "path": path
135
+ ]
136
+
137
+ // Only add if they exist (consistent with TypeScript and Android)
138
+ if let body = body, !body.isEmpty {
139
+ requestData["body"] = body
140
+ }
141
+
142
+ if !headers.isEmpty {
143
+ requestData["headers"] = headers
144
+ }
145
+
146
+ if !query.isEmpty {
147
+ requestData["query"] = query
148
+ }
149
+
150
+ delegate?.httpLocalServerSwifterDidReceiveRequest(requestData)
151
+
152
+ // Wait for JS response or timeout
153
+ let result = semaphore.wait(timeout: .now() + defaultTimeout)
154
+
155
+ // Cleanup
156
+ Self.queue.async {
157
+ Self.pendingResponses.removeValue(forKey: requestId)
158
+ }
159
+
160
+ // Handle timeout
161
+ if result == .timedOut {
162
+ let timeoutResponse = "{\"error\":\"Request timeout\",\"requestId\":\"\(requestId)\"}"
163
+ return createJsonResponse(timeoutResponse, statusCode: 408)
164
+ }
165
+
166
+ let reply = responseString ?? "{\"error\":\"No response from handler\"}"
167
+ return createJsonResponse(reply)
168
+ }
169
+
170
+ private func extractBody(from request: HttpRequest) -> String? {
171
+ guard let bodyBytes = request.body else {
172
+ return nil
173
+ }
174
+
175
+ return String(bytes: bodyBytes, encoding: .utf8)
176
+ }
177
+
178
+ private func extractHeaders(from request: HttpRequest) -> [String: String] {
179
+ return request.headers
180
+ }
181
+
182
+ private func extractQuery(from request: HttpRequest) -> [String: String] {
183
+ return request.queryParams
184
+ }
185
+
186
+ private func createJsonResponse(_ body: String, statusCode: Int = 200) -> HttpResponse {
187
+ var response: HttpResponse
188
+
189
+ switch statusCode {
190
+ case 200:
191
+ response = .ok(.text(body))
192
+ case 204:
193
+ response = HttpResponse.raw(204, "No Content", [:], nil)
194
+ case 408:
195
+ response = HttpResponse.raw(408, "Request Timeout", [:], { writer in
196
+ try writer.write([UInt8](body.utf8))
197
+ })
198
+ case 500:
199
+ response = .internalServerError
200
+ default:
201
+ response = HttpResponse.raw(statusCode, "Custom Status", [:], { writer in
202
+ try writer.write([UInt8](body.utf8))
203
+ })
204
+ }
205
+
206
+ // Add CORS headers
207
+ return response.withHeaders([
208
+ "Content-Type": "application/json",
209
+ "Access-Control-Allow-Origin": "*",
210
+ "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
211
+ "Access-Control-Allow-Headers": "Origin, Content-Type, Accept, Authorization",
212
+ "Access-Control-Allow-Credentials": "true",
213
+ "Access-Control-Max-Age": "3600"
214
+ ])
215
+ }
216
+
217
+ private func corsResponse() -> HttpResponse {
218
+ return createJsonResponse("{}", statusCode: 204)
219
+ }
220
+
221
+ private func errorResponse() -> HttpResponse {
222
+ return createJsonResponse("{\"error\":\"Server error\"}", statusCode: 500)
223
+ }
224
+
225
+ private func startServer() throws {
226
+ guard let server = webServer else {
227
+ throw NSError(
228
+ domain: "HttpLocalServerSwifter",
229
+ code: -1,
230
+ userInfo: [NSLocalizedDescriptionKey: "WebServer not initialized"]
231
+ )
232
+ }
233
+
234
+ // Swifter starts on all interfaces (0.0.0.0) by default
235
+ try server.start(defaultPort, forceIPv4: true)
236
+ }
237
+
238
+ // MARK: - Network Utilities
239
+ static func getWiFiAddress() -> String? {
240
+ var address: String?
241
+ var ifaddr: UnsafeMutablePointer<ifaddrs>?
242
+
243
+ guard getifaddrs(&ifaddr) == 0 else {
244
+ return nil
245
+ }
246
+
247
+ defer {
248
+ freeifaddrs(ifaddr)
249
+ }
250
+
251
+ var ptr = ifaddr
252
+ while ptr != nil {
253
+ defer { ptr = ptr?.pointee.ifa_next }
254
+
255
+ guard let interface = ptr?.pointee else { continue }
256
+
257
+ let addrFamily = interface.ifa_addr.pointee.sa_family
258
+
259
+ // Check for IPv4 interface
260
+ guard addrFamily == UInt8(AF_INET) else { continue }
261
+
262
+ let name = String(cString: interface.ifa_name)
263
+
264
+ // WiFi interface (en0) or cellular (pdp_ip0)
265
+ guard name == "en0" || name == "pdp_ip0" else { continue }
266
+
267
+ var addr = interface.ifa_addr.pointee
268
+ var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
269
+
270
+ let result = getnameinfo(
271
+ &addr,
272
+ socklen_t(interface.ifa_addr.pointee.sa_len),
273
+ &hostname,
274
+ socklen_t(hostname.count),
275
+ nil,
276
+ 0,
277
+ NI_NUMERICHOST
278
+ )
279
+
280
+ guard result == 0 else { continue }
281
+
282
+ address = String(cString: hostname)
283
+
284
+ // Prefer en0 (WiFi) over pdp_ip0 (cellular)
285
+ if name == "en0" {
286
+ break
287
+ }
288
+ }
289
+
290
+ return address
291
+ }
292
+ }
293
+
294
+ // MARK: - HttpResponse Extension
295
+ extension HttpResponse {
296
+ func withHeaders(_ headers: [String: String]) -> HttpResponse {
297
+ return HttpResponse.raw(self.statusCode(), self.reasonPhrase(), headers, { writer in
298
+ if let bodyData = try? self.content() {
299
+ try writer.write(bodyData)
300
+ }
301
+ })
302
+ }
303
+
304
+ func statusCode() -> Int {
305
+ switch self {
306
+ case .ok: return 200
307
+ case .created: return 201
308
+ case .accepted: return 202
309
+ case .movedPermanently: return 301
310
+ case .notModified: return 304
311
+ case .badRequest: return 400
312
+ case .unauthorized: return 401
313
+ case .forbidden: return 403
314
+ case .notFound: return 404
315
+ case .internalServerError: return 500
316
+ case .raw(let code, _, _, _): return code
317
+ default: return 200
318
+ }
319
+ }
320
+
321
+ func reasonPhrase() -> String {
322
+ switch self {
323
+ case .ok: return "OK"
324
+ case .created: return "Created"
325
+ case .accepted: return "Accepted"
326
+ case .movedPermanently: return "Moved Permanently"
327
+ case .notModified: return "Not Modified"
328
+ case .badRequest: return "Bad Request"
329
+ case .unauthorized: return "Unauthorized"
330
+ case .forbidden: return "Forbidden"
331
+ case .notFound: return "Not Found"
332
+ case .internalServerError: return "Internal Server Error"
333
+ case .raw(_, let phrase, _, _): return phrase
334
+ default: return "OK"
335
+ }
336
+ }
337
+
338
+ func content() throws -> [UInt8] {
339
+ switch self {
340
+ case .ok(let body), .created(let body), .accepted(let body):
341
+ return try bodyToBytes(body)
342
+ case .badRequest(let body), .notFound(let body), .internalServerError:
343
+ if let body = body {
344
+ return try bodyToBytes(body)
345
+ }
346
+ return []
347
+ case .raw(_, _, _, let writer):
348
+ var data = [UInt8]()
349
+ let mockWriter = MockResponseWriter { bytes in
350
+ data.append(contentsOf: bytes)
351
+ }
352
+ try writer?(mockWriter)
353
+ return data
354
+ default:
355
+ return []
356
+ }
357
+ }
358
+
359
+ private func bodyToBytes(_ body: HttpResponseBody) throws -> [UInt8] {
360
+ switch body {
361
+ case .text(let string):
362
+ return [UInt8](string.utf8)
363
+ case .html(let string):
364
+ return [UInt8](string.utf8)
365
+ case .json(let object):
366
+ let data = try JSONSerialization.data(withJSONObject: object)
367
+ return [UInt8](data)
368
+ case .data(let data, _):
369
+ return [UInt8](data)
370
+ case .custom(let writer):
371
+ var bytes = [UInt8]()
372
+ let mockWriter = MockResponseWriter { data in
373
+ bytes.append(contentsOf: data)
374
+ }
375
+ try writer(mockWriter)
376
+ return bytes
377
+ }
378
+ }
379
+ }
380
+
381
+ // MARK: - Mock Response Writer
382
+ private class MockResponseWriter: HttpResponseBodyWriter {
383
+ let writeHandler: ([UInt8]) -> Void
384
+
385
+ init(writeHandler: @escaping ([UInt8]) -> Void) {
386
+ self.writeHandler = writeHandler
387
+ }
388
+
389
+ func write(_ data: [UInt8]) throws {
390
+ writeHandler(data)
391
+ }
392
+
393
+ func write(_ data: ArraySlice<UInt8>) throws {
394
+ writeHandler(Array(data))
395
+ }
396
+
397
+ func write(_ data: NSData) throws {
398
+ var bytes = [UInt8](repeating: 0, count: data.length)
399
+ data.getBytes(&bytes, length: data.length)
400
+ writeHandler(bytes)
401
+ }
402
+
403
+ func write(_ data: Data) throws {
404
+ writeHandler([UInt8](data))
405
+ }
406
+ }
@@ -0,0 +1,68 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ /**
5
+ * Capacitor plugin for running a local HTTP server on the device.
6
+ * Allows receiving HTTP requests and sending responses from JavaScript.
7
+ */
8
+ @objc(HttpLocalServerSwifterPlugin)
9
+ public class HttpLocalServerSwifterPlugin: CAPPlugin, CAPBridgedPlugin, HttpLocalServerSwifterDelegate {
10
+
11
+ public let identifier = "HttpLocalServerSwifterPlugin"
12
+ public let jsName = "HttpLocalServerSwifter"
13
+ public let pluginMethods: [CAPPluginMethod] = [
14
+ CAPPluginMethod(name: "connect", returnType: CAPPluginReturnPromise),
15
+ CAPPluginMethod(name: "disconnect", returnType: CAPPluginReturnPromise),
16
+ CAPPluginMethod(name: "sendResponse", returnType: CAPPluginReturnPromise)
17
+ ]
18
+
19
+ private var localServer: HttpLocalServerSwifter?
20
+
21
+ /**
22
+ * Starts the local HTTP server.
23
+ * @return ip: The server's IP address
24
+ * @return port: The port the server is listening on
25
+ */
26
+ @objc func connect(_ call: CAPPluginCall) {
27
+ if localServer == nil {
28
+ localServer = HttpLocalServerSwifter(delegate: self)
29
+ }
30
+ localServer?.connect(call)
31
+ }
32
+
33
+ /**
34
+ * Stops the local HTTP server.
35
+ * Cleans up all resources and pending requests.
36
+ */
37
+ @objc func disconnect(_ call: CAPPluginCall) {
38
+ if localServer != nil {
39
+ localServer?.disconnect(call)
40
+ } else {
41
+ call.resolve()
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Sends a response back to the client that made the request.
47
+ * @param requestId Unique request ID (received in 'onRequest')
48
+ * @param body Response body (typically stringified JSON)
49
+ */
50
+ @objc func sendResponse(_ call: CAPPluginCall) {
51
+ guard let requestId = call.getString("requestId") else {
52
+ call.reject("Missing requestId")
53
+ return
54
+ }
55
+
56
+ guard let body = call.getString("body") else {
57
+ call.reject("Missing body")
58
+ return
59
+ }
60
+
61
+ HttpLocalServerSwifter.handleJsResponse(requestId: requestId, body: body)
62
+ call.resolve()
63
+ }
64
+
65
+ public func httpLocalServerSwifterDidReceiveRequest(_ data: [String: Any]) {
66
+ notifyListeners("onRequest", data: data)
67
+ }
68
+ }