@cappitolian/http-local-server-swifter 0.0.33 → 0.0.35

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/README.md CHANGED
@@ -1,18 +1,24 @@
1
1
  # @cappitolian/http-local-server-swifter
2
2
 
3
- A Capacitor plugin to run a local HTTP server on your device, allowing you to receive and respond to HTTP requests directly from Angular/JavaScript.
3
+ [![npm version](https://img.shields.io/npm/v/@cappitolian/http-local-server-swifter)](https://www.npmjs.com/package/@cappitolian/http-local-server-swifter)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Capacitor 8](https://img.shields.io/badge/Capacitor-8-blue)](https://capacitorjs.com/)
6
+
7
+ A Capacitor plugin that embeds a real HTTP server directly on your device — powered by **NanoHTTPD** on Android and **Swifter** on iOS. It allows you to receive and respond to HTTP requests from the JavaScript layer, enabling local peer-to-peer communication between devices on the same network without a cloud backend.
4
8
 
5
9
  ---
6
10
 
7
- ## Features
11
+ ## Table of Contents
8
12
 
9
- - ✅ Embed a real HTTP server (NanoHTTPD on Android, **Swifter** on iOS)
10
- - Receive requests via events and send responses back from the JS layer
11
- - ✅ CORS support enabled by default for local communication
12
- - ✅ Support for all HTTP methods (GET, POST, PUT, PATCH, DELETE, OPTIONS)
13
- - Dynamic URL routing (e.g. `/orders/:id`) supported via middleware
14
- - Swift Package Manager (SPM) support
15
- - ✅ Tested with **Capacitor 8** and **Ionic 8**
13
+ - [Installation](#installation)
14
+ - [Platform Configuration](#platform-configuration)
15
+ - [Usage](#usage)
16
+ - [Security](#security)
17
+ - [API Reference](#api-reference)
18
+ - [Platform Support](#platform-support)
19
+ - [Contributing](#contributing)
20
+ - [Changelog](#changelog)
21
+ - [License](#license)
16
22
 
17
23
  ---
18
24
 
@@ -25,6 +31,52 @@ npx cap sync
25
31
 
26
32
  ---
27
33
 
34
+ ## Platform Configuration
35
+
36
+ ### Android
37
+
38
+ Add the following permissions to your `AndroidManifest.xml`:
39
+
40
+ ```xml
41
+ <uses-permission android:name="android.permission.INTERNET" />
42
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
43
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
44
+ ```
45
+
46
+ To allow cleartext HTTP traffic on the local network, add or update your `network_security_config.xml`:
47
+
48
+ ```xml
49
+ <!-- res/xml/network_security_config.xml -->
50
+ <network-security-config>
51
+ <domain-config cleartextTrafficPermitted="true">
52
+ <domain includeSubdomains="true">192.168.0.0/16</domain>
53
+ </domain-config>
54
+ </network-security-config>
55
+ ```
56
+
57
+ Then reference it in `AndroidManifest.xml`:
58
+
59
+ ```xml
60
+ <application
61
+ android:networkSecurityConfig="@xml/network_security_config"
62
+ ...>
63
+ ```
64
+
65
+ ### iOS
66
+
67
+ No additional `Info.plist` entries are required for local HTTP servers. However, if your app uses **Bonjour/mDNS** for Network Discovery, add the following:
68
+
69
+ ```xml
70
+ <key>NSLocalNetworkUsageDescription</key>
71
+ <string>This app uses the local network to communicate with nearby devices.</string>
72
+ <key>NSBonjourServices</key>
73
+ <array>
74
+ <string>_http._tcp</string>
75
+ </array>
76
+ ```
77
+
78
+ ---
79
+
28
80
  ## Usage
29
81
 
30
82
  ### Import
@@ -33,130 +85,209 @@ npx cap sync
33
85
  import { HttpLocalServerSwifter } from '@cappitolian/http-local-server-swifter';
34
86
  ```
35
87
 
36
- ### Listen and Respond
88
+ ### Start the server and listen for requests
37
89
 
38
90
  ```typescript
39
- // 1. Set up the listener for incoming requests
91
+ // 1. Register the request listener
40
92
  await HttpLocalServerSwifter.addListener('onRequest', async (data) => {
41
93
  console.log(`${data.method} ${data.path}`);
42
- console.log('Body:', data.body);
43
94
  console.log('Headers:', data.headers);
95
+ console.log('Body:', data.body);
44
96
  console.log('Query:', data.query);
45
97
 
46
- // 2. Send a response back to the client using the requestId
98
+ // 2. Send a response back using the requestId
47
99
  await HttpLocalServerSwifter.sendResponse({
48
100
  requestId: data.requestId,
49
- body: JSON.stringify({
50
- success: true,
51
- message: 'Request processed!'
52
- })
101
+ status: 200,
102
+ headers: {
103
+ 'Content-Type': 'application/json'
104
+ },
105
+ body: JSON.stringify({ success: true, message: 'Hello from the device!' })
53
106
  });
54
107
  });
55
108
 
56
109
  // 3. Start the server
57
- HttpLocalServerSwifter.connect().then(result => {
58
- console.log('Server running at:', result.ip, 'Port:', result.port);
59
- });
110
+ const { ip, port } = await HttpLocalServerSwifter.connect();
111
+ console.log(`Server running at http://${ip}:${port}`);
60
112
  ```
61
113
 
62
- ### Stop Server
114
+ ### Stop the server
63
115
 
64
116
  ```typescript
65
- // 4. Stop the server
66
117
  await HttpLocalServerSwifter.disconnect();
67
118
  ```
68
119
 
69
120
  ---
70
121
 
71
- ## Platforms
122
+ ## Security
123
+
124
+ This plugin runs an HTTP server on the local network. The following mechanisms are built into the native layer and recommended at the application layer.
125
+
126
+ ### Rate Limiting (Native — Android & iOS)
127
+
128
+ IP-based rate limiting is enforced natively before requests reach TypeScript, protecting the server against denial-of-service (DoS) attacks.
129
+
130
+ | Platform | Limit | Window |
131
+ |---|---|---|
132
+ | Android | 30 requests | 60 seconds |
133
+ | iOS | 30 requests | 60 seconds |
134
+
135
+ Requests exceeding the limit receive a `429 Too Many Requests` response automatically:
136
+
137
+ ```json
138
+ { "success": false, "error": "Too many requests" }
139
+ ```
140
+
141
+ ### API Key Pairing (Application layer)
72
142
 
73
- - **iOS** (Swift with Swifter)
74
- - **Android** (Java with NanoHTTPD)
75
- - **Web** (Returns mock values for development)
143
+ To prevent unauthorized clients from accessing your endpoints, implement an API Key pairing flow:
144
+
145
+ 1. The server generates a cryptographically random key on first launch using `crypto.getRandomValues` and persists it with `@capacitor/preferences`.
146
+ 2. The client calls `GET /pair` (the only unauthenticated endpoint) immediately after discovering the server IP.
147
+ 3. The server returns the key. The client stores it locally.
148
+ 4. All subsequent requests must include the `x-api-key` header. The server validates it before processing any route.
149
+
150
+ ```
151
+ Server generates key → Client calls /pair → Client stores key
152
+
153
+ All requests: x-api-key: <key> → Server validates → 401 if invalid
154
+ ```
155
+
156
+ > ⚠️ Since this plugin operates over HTTP, the API key is transmitted in plaintext. For sensitive environments, consider implementing HMAC request signing with short-lived timestamps to prevent replay attacks.
157
+
158
+ ### CORS
159
+
160
+ CORS headers are set by default in the native layer. The `x-api-key` header is explicitly included in `Access-Control-Allow-Headers` on both platforms.
76
161
 
77
162
  ---
78
163
 
79
- ## Requirements
164
+ ## API Reference
165
+
166
+ <!-- Auto-generated by @capacitor/docgen -->
167
+
168
+ ### Methods
80
169
 
81
- - [Capacitor 8](https://capacitorjs.com/)
82
- - iOS 13.0+
83
- - Android API 22+
170
+ #### `connect() => Promise<HttpConnectResult>`
171
+
172
+ Starts the HTTP server and begins listening for incoming requests.
173
+
174
+ **Returns:** `Promise<HttpConnectResult>`
84
175
 
85
176
  ---
86
177
 
87
- ## Migration from v0.1.x
178
+ #### `disconnect() => Promise<void>`
88
179
 
89
- Version 0.2.0 introduces middleware-based routing on iOS and dynamic response support (custom `status` and `headers`) on both platforms. See changes below.
180
+ Stops the HTTP server and releases all resources.
90
181
 
91
182
  ---
92
183
 
93
- ## License
184
+ #### `sendResponse(options: HttpSendResponseOptions) => Promise<void>`
94
185
 
95
- MIT
186
+ Sends an HTTP response back to the client for a given request.
187
+
188
+ | Param | Type | Description |
189
+ |---|---|---|
190
+ | `options` | `HttpSendResponseOptions` | Response options including requestId, status, headers, and body |
96
191
 
97
192
  ---
98
193
 
99
- ## Support
194
+ #### `addListener('onRequest', handler) => Promise<PluginListenerHandle>`
195
+
196
+ Registers a listener for incoming HTTP requests.
100
197
 
101
- If you have any issues or feature requests, please open an issue on the repository.
198
+ | Param | Type | Description |
199
+ |---|---|---|
200
+ | `eventName` | `'onRequest'` | Event name |
201
+ | `handler` | `(data: HttpRequestData) => void` | Callback receiving the request data |
102
202
 
103
203
  ---
104
204
 
105
- ## 📋 Cambios Principales
205
+ #### `removeAllListeners() => Promise<void>`
106
206
 
107
- ### **Route Handlers → Middleware (iOS)**
207
+ Removes all registered listeners.
108
208
 
109
- | Aspecto | v0.1.x | v0.2.0 |
110
- |---------|--------|--------|
111
- | **Routing** | `server["/:path"] = { ... }` | `server.middleware.append { ... }` |
112
- | **Rutas dinámicas** | ❌ Solo un segmento (`/menu`) | ✅ Cualquier ruta (`/orders/:id`) |
113
- | **CORS preflight** | Manejado por handler estático | Interceptado en middleware antes del JS |
114
- | **Thread de inicio** | Main thread | Background thread (`DispatchQueue.global`) |
115
- | **Respuesta dinámica** | Solo `body` | `body` + `status` + `headers` |
209
+ ---
116
210
 
117
- ### **sendResponse — Nuevos campos opcionales**
211
+ ### Interfaces
118
212
 
119
- ```typescript
120
- await HttpLocalServerSwifter.sendResponse({
121
- requestId: data.requestId,
122
- body: JSON.stringify({ success: true }),
123
- status: 200, // NEW: opcional, default 200
124
- headers: { // NEW: opcional, headers custom
125
- 'X-Custom-Header': 'value'
126
- }
127
- });
128
- ```
213
+ #### `HttpConnectResult`
129
214
 
130
- ### **Archivos modificados**
215
+ | Property | Type | Description |
216
+ |---|---|---|
217
+ | `ip` | `string` | Local IP address where the server is bound |
218
+ | `port` | `number` | Port the server is listening on (default: 8080) |
131
219
 
132
- | Archivo | Cambio |
133
- |---------|--------|
134
- | `HttpLocalServerSwifter.swift` | Middleware en lugar de route handlers; `handleJsResponse` acepta `[String: Any]` |
135
- | `HttpLocalServerSwifterPlugin.swift` | `sendResponse` pasa `dictionaryRepresentation` completo |
136
- | `HttpLocalServerSwifterPlugin.java` | `sendResponse` pasa `call.getData()` completo |
137
- | `definitions.ts` | `HttpSendResponseOptions` agrega `status?` y `headers?` |
138
- | `web.ts` | Mock actualizado con los nuevos campos |
220
+ ---
221
+
222
+ #### `HttpRequestData`
223
+
224
+ | Property | Type | Description |
225
+ |---|---|---|
226
+ | `requestId` | `string` | Unique ID used to correlate the response |
227
+ | `method` | `string` | HTTP method (GET, POST, PUT, PATCH, DELETE, OPTIONS) |
228
+ | `path` | `string` | Request path (e.g. `/menu`, `/orders/123`) |
229
+ | `headers` | `Record<string, string>` | Request headers |
230
+ | `query` | `Record<string, string>` | Query string parameters |
231
+ | `body` | `string \| null` | Raw request body (for POST, PUT, PATCH) |
139
232
 
140
233
  ---
141
234
 
142
- ## ✅ Pasos para Aplicar
235
+ #### `HttpSendResponseOptions`
143
236
 
144
- 1. **Reemplaza `HttpLocalServerSwifter.swift`** con la versión nueva (middleware)
145
- 2. **Reemplaza `HttpLocalServerSwifterPlugin.swift`** con la versión nueva
146
- 3. **Reemplaza `HttpLocalServerSwifterPlugin.java`** con la versión nueva
147
- 4. **Actualiza `definitions.ts`**, **`web.ts`** e **`index.ts`**
148
- 5. **En Xcode**:
149
- ```
150
- File → Packages → Reset Package Caches
151
- File → Packages → Resolve Package Versions
152
- Product → Clean Build Folder
153
- Product Run
154
- ```
155
- 6. **En Android Studio**:
237
+ | Property | Type | Required | Description |
238
+ |---|---|---|---|
239
+ | `requestId` | `string` | | ID of the request to respond to |
240
+ | `status` | `number` | | HTTP status code (default: `200`) |
241
+ | `headers` | `Record<string, string>` | ❌ | Custom response headers |
242
+ | `body` | `string` | ❌ | Response body |
243
+
244
+ ---
245
+
246
+ ## Platform Support
247
+
248
+ | Feature | Android | iOS | Web |
249
+ |---|---|---|---|
250
+ | Start HTTP server | ✅ | ✅ | ✅ (mock) |
251
+ | Receive requests | ✅ | ✅ | ✅ (mock) |
252
+ | Send responses | ✅ | ✅ | ✅ (mock) |
253
+ | Custom status codes | ✅ | ✅ | ✅ (mock) |
254
+ | Custom response headers | ✅ | ✅ | ✅ (mock) |
255
+ | Request headers forwarding | ✅ | ✅ | ✅ (mock) |
256
+ | Dynamic routing | ✅ | ✅ | ❌ |
257
+ | IP-based rate limiting | ✅ | ✅ | ❌ |
258
+ | CORS preflight handling | ✅ | ✅ | ❌ |
259
+
260
+ ---
261
+
262
+ ## Contributing
263
+
264
+ Clone the repository and install dependencies:
265
+
266
+ ```bash
267
+ git clone https://github.com/cappitolian/http-local-server-swifter
268
+ cd http-local-server-swifter
269
+ npm install
156
270
  ```
157
- Build → Clean Project
158
- Build Rebuild Project
159
- Run
271
+
272
+ Run the example project:
273
+
274
+ ```bash
275
+ cd example
276
+ npm install
277
+ npx cap sync
278
+ # Open in Android Studio or Xcode
160
279
  ```
161
280
 
162
- > ⚠️ `npx cap sync` solo sincroniza archivos web. Los cambios en código nativo Swift/Java **requieren recompilación desde el IDE**.
281
+ > ⚠️ Changes to native Swift/Java code require recompilation from the IDE. `npx cap sync` only syncs web assets.
282
+
283
+ ---
284
+
285
+ ## Changelog
286
+
287
+ See [CHANGELOG.md](./CHANGELOG.md) for the full list of changes.
288
+
289
+ ---
290
+
291
+ ## License
292
+
293
+ MIT — see [LICENSE](./LICENSE) for details.
@@ -304,7 +304,7 @@ public class HttpLocalServerSwifter {
304
304
  response.addHeader("Access-Control-Allow-Origin", "*");
305
305
  response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
306
306
  response.addHeader("Access-Control-Allow-Headers",
307
- "Origin, Content-Type, Accept, Authorization, X-Requested-With, x-api-key");
307
+ "Origin, Content-Type, Accept, Authorization, X-Requested-With, x-api-key, x-signature, x-timestamp");
308
308
  response.addHeader("Access-Control-Max-Age", "3600");
309
309
  // Prevents TCP connection reuse. NanoHTTPD does not handle keep-alive
310
310
  // correctly under rapid sequential requests, causing ERR_INVALID_HTTP_RESPONSE.
@@ -186,7 +186,7 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
186
186
  "Content-Type": "application/json",
187
187
  "Access-Control-Allow-Origin": "*",
188
188
  "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
189
- "Access-Control-Allow-Headers": "Origin, Content-Type, Accept, Authorization, X-Requested-With, x-api-key"
189
+ "Access-Control-Allow-Headers": "Origin, Content-Type, Accept, Authorization, X-Requested-With, x-api-key, x-signature, x-timestamp"
190
190
  ]
191
191
 
192
192
  if
@@ -210,7 +210,7 @@ public protocol HttpLocalServerSwifterDelegate: AnyObject {
210
210
  return .raw(204, "No Content", [
211
211
  "Access-Control-Allow-Origin": "*",
212
212
  "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
213
- "Access-Control-Allow-Headers": "Origin, Content-Type, Accept, Authorization, X-Requested-With, x-api-key",
213
+ "Access-Control-Allow-Headers": "Origin, Content-Type, Accept, Authorization, X-Requested-With, x-api-key, x-signature, x-timestamp",
214
214
  "Access-Control-Max-Age": "86400"
215
215
  ], nil)
216
216
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cappitolian/http-local-server-swifter",
3
- "version": "0.0.33",
3
+ "version": "0.0.35",
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",