@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
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@cappitolian/http-local-server-swifter)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](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
|
-
##
|
|
11
|
+
## Table of Contents
|
|
8
12
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
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
|
-
###
|
|
88
|
+
### Start the server and listen for requests
|
|
37
89
|
|
|
38
90
|
```typescript
|
|
39
|
-
// 1.
|
|
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
|
|
98
|
+
// 2. Send a response back using the requestId
|
|
47
99
|
await HttpLocalServerSwifter.sendResponse({
|
|
48
100
|
requestId: data.requestId,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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()
|
|
58
|
-
|
|
59
|
-
});
|
|
110
|
+
const { ip, port } = await HttpLocalServerSwifter.connect();
|
|
111
|
+
console.log(`Server running at http://${ip}:${port}`);
|
|
60
112
|
```
|
|
61
113
|
|
|
62
|
-
### Stop
|
|
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
|
-
##
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
##
|
|
164
|
+
## API Reference
|
|
165
|
+
|
|
166
|
+
<!-- Auto-generated by @capacitor/docgen -->
|
|
167
|
+
|
|
168
|
+
### Methods
|
|
80
169
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
178
|
+
#### `disconnect() => Promise<void>`
|
|
88
179
|
|
|
89
|
-
|
|
180
|
+
Stops the HTTP server and releases all resources.
|
|
90
181
|
|
|
91
182
|
---
|
|
92
183
|
|
|
93
|
-
|
|
184
|
+
#### `sendResponse(options: HttpSendResponseOptions) => Promise<void>`
|
|
94
185
|
|
|
95
|
-
|
|
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
|
-
|
|
194
|
+
#### `addListener('onRequest', handler) => Promise<PluginListenerHandle>`
|
|
195
|
+
|
|
196
|
+
Registers a listener for incoming HTTP requests.
|
|
100
197
|
|
|
101
|
-
|
|
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
|
-
|
|
205
|
+
#### `removeAllListeners() => Promise<void>`
|
|
106
206
|
|
|
107
|
-
|
|
207
|
+
Removes all registered listeners.
|
|
108
208
|
|
|
109
|
-
|
|
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
|
-
###
|
|
211
|
+
### Interfaces
|
|
118
212
|
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
|
137
|
-
|
|
138
|
-
| `
|
|
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
|
-
|
|
235
|
+
#### `HttpSendResponseOptions`
|
|
143
236
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
> ⚠️
|
|
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.
|
|
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",
|