@cappitolian/http-local-server 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,18 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'CappitolianHttpLocalServer'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.license = package['license']
10
+ s.homepage = package['repository']['url']
11
+ s.author = package['author']
12
+ s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13
+ s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
14
+ s.ios.deployment_target = '14.0'
15
+ s.dependency 'Capacitor'
16
+ s.dependency 'GCDWebServer', '~> 3.5'
17
+ s.swift_version = '5.1'
18
+ end
package/Package.swift ADDED
@@ -0,0 +1,28 @@
1
+ // swift-tools-version: 5.9
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "CappitolianHttpLocalServer",
6
+ platforms: [.iOS(.v14)],
7
+ products: [
8
+ .library(
9
+ name: "CappitolianHttpLocalServer",
10
+ targets: ["HttpLocalServerPlugin"])
11
+ ],
12
+ dependencies: [
13
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0")
14
+ ],
15
+ targets: [
16
+ .target(
17
+ name: "HttpLocalServerPlugin",
18
+ dependencies: [
19
+ .product(name: "Capacitor", package: "capacitor-swift-pm"),
20
+ .product(name: "Cordova", package: "capacitor-swift-pm")
21
+ ],
22
+ path: "ios/Sources/HttpLocalServerPlugin"),
23
+ .testTarget(
24
+ name: "HttpLocalServerPluginTests",
25
+ dependencies: ["HttpLocalServerPlugin"],
26
+ path: "ios/Tests/HttpLocalServerPluginTests")
27
+ ]
28
+ )
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # @cappitolian/http-local-server
2
+
3
+ Runs a local HTTP server on your device, accessible over LAN. Supports connect, disconnect, GET, and POST methods with IP and port discovery.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @cappitolian/http-local-server
9
+ npx cap sync
10
+ ```
11
+
12
+ ## API
13
+
14
+ <docgen-index>
15
+
16
+ * [`connect()`](#connect)
17
+ * [`disconnect()`](#disconnect)
18
+ * [`sendResponse(...)`](#sendresponse)
19
+ * [`addListener('onRequest', ...)`](#addlisteneronrequest-)
20
+ * [Interfaces](#interfaces)
21
+
22
+ </docgen-index>
23
+
24
+ <docgen-api>
25
+ <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
26
+
27
+ ### connect()
28
+
29
+ ```typescript
30
+ connect() => Promise<HttpConnectResult>
31
+ ```
32
+
33
+ **Returns:** <code>Promise&lt;<a href="#httpconnectresult">HttpConnectResult</a>&gt;</code>
34
+
35
+ --------------------
36
+
37
+
38
+ ### disconnect()
39
+
40
+ ```typescript
41
+ disconnect() => Promise<void>
42
+ ```
43
+
44
+ --------------------
45
+
46
+
47
+ ### sendResponse(...)
48
+
49
+ ```typescript
50
+ sendResponse(options: { requestId: string; body: string; }) => Promise<void>
51
+ ```
52
+
53
+ | Param | Type |
54
+ | ------------- | ------------------------------------------------- |
55
+ | **`options`** | <code>{ requestId: string; body: string; }</code> |
56
+
57
+ --------------------
58
+
59
+
60
+ ### addListener('onRequest', ...)
61
+
62
+ ```typescript
63
+ addListener(eventName: 'onRequest', listenerFunc: (info: HttpRequestInfo) => void) => Promise<PluginListenerHandle> & PluginListenerHandle
64
+ ```
65
+
66
+ | Param | Type |
67
+ | ------------------ | ------------------------------------------------------------------------------ |
68
+ | **`eventName`** | <code>'onRequest'</code> |
69
+ | **`listenerFunc`** | <code>(info: <a href="#httprequestinfo">HttpRequestInfo</a>) =&gt; void</code> |
70
+
71
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt; & <a href="#pluginlistenerhandle">PluginListenerHandle</a></code>
72
+
73
+ --------------------
74
+
75
+
76
+ ### Interfaces
77
+
78
+
79
+ #### HttpConnectResult
80
+
81
+ | Prop | Type |
82
+ | ---------- | ------------------- |
83
+ | **`ip`** | <code>string</code> |
84
+ | **`port`** | <code>number</code> |
85
+
86
+
87
+ #### PluginListenerHandle
88
+
89
+ | Prop | Type |
90
+ | ------------ | ----------------------------------------- |
91
+ | **`remove`** | <code>() =&gt; Promise&lt;void&gt;</code> |
92
+
93
+
94
+ #### HttpRequestInfo
95
+
96
+ | Prop | Type |
97
+ | --------------- | ------------------- |
98
+ | **`requestId`** | <code>string</code> |
99
+ | **`method`** | <code>string</code> |
100
+ | **`path`** | <code>string</code> |
101
+ | **`body`** | <code>string</code> |
102
+
103
+ </docgen-api>
@@ -0,0 +1,59 @@
1
+ ext {
2
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
4
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1'
5
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1'
6
+ }
7
+
8
+ buildscript {
9
+ repositories {
10
+ google()
11
+ mavenCentral()
12
+ }
13
+ dependencies {
14
+ classpath 'com.android.tools.build:gradle:8.7.2'
15
+ }
16
+ }
17
+
18
+ apply plugin: 'com.android.library'
19
+
20
+ android {
21
+ namespace "com.cappitolian.plugins.httplocalserver"
22
+ compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
23
+ defaultConfig {
24
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
25
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
26
+ versionCode 1
27
+ versionName "1.0"
28
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
29
+ }
30
+ buildTypes {
31
+ release {
32
+ minifyEnabled false
33
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
34
+ }
35
+ }
36
+ lintOptions {
37
+ abortOnError false
38
+ }
39
+ compileOptions {
40
+ sourceCompatibility JavaVersion.VERSION_21
41
+ targetCompatibility JavaVersion.VERSION_21
42
+ }
43
+ }
44
+
45
+ repositories {
46
+ google()
47
+ mavenCentral()
48
+ }
49
+
50
+
51
+ dependencies {
52
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
53
+ implementation project(':capacitor-android')
54
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
55
+ implementation 'org.nanohttpd:nanohttpd:2.3.1'
56
+ testImplementation "junit:junit:$junitVersion"
57
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
58
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
59
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,160 @@
1
+ // package com.cappitolian.plugins.httplocalservice;
2
+
3
+ // import android.util.Log;
4
+
5
+ // public class LocalIp {
6
+
7
+ // public String echo(String value) {
8
+ // Log.i("Echo", value);
9
+ // return value;
10
+ // }
11
+ // }
12
+
13
+ // package com.cappitolian.plugins.httplocalservice;
14
+
15
+ // import android.util.Log;
16
+
17
+ // public class LocalIp {
18
+
19
+ // public String echo(String value) {
20
+ // Log.i("Echo", value);
21
+ // return value;
22
+ // }
23
+ // }
24
+
25
+ package com.cappitolian.plugins.httplocalservice;
26
+
27
+ import android.content.Context;
28
+ import android.net.wifi.WifiManager;
29
+ import android.text.format.Formatter;
30
+ import com.getcapacitor.JSObject;
31
+ import com.getcapacitor.Plugin;
32
+ import com.getcapacitor.PluginCall;
33
+ import fi.iki.elonen.NanoHTTPD;
34
+ import java.io.IOException;
35
+ import java.util.UUID;
36
+ import java.util.concurrent.*;
37
+ import java.util.HashMap;
38
+
39
+ public class HttpLocalServer {
40
+ private LocalNanoServer server;
41
+ private String localIp;
42
+ private int port = 8080;
43
+ private Plugin plugin;
44
+
45
+ // Map to wait for responses from JS (key: requestId)
46
+ private static final ConcurrentHashMap<String, CompletableFuture<String>> pendingResponses = new ConcurrentHashMap<>();
47
+
48
+ public HttpLocalServer(Plugin plugin) {
49
+ this.plugin = plugin;
50
+ }
51
+
52
+ public void connect(PluginCall call) {
53
+ if (server == null) {
54
+ localIp = getLocalIpAddress(plugin.getContext());
55
+ server = new LocalNanoServer(localIp, port, plugin);
56
+ try {
57
+ server.start();
58
+ JSObject ret = new JSObject();
59
+ ret.put("ip", localIp);
60
+ ret.put("port", port);
61
+ call.resolve(ret);
62
+ } catch (Exception e) {
63
+ call.reject("Failed to start server: " + e.getMessage());
64
+ }
65
+ } else {
66
+ call.reject("Server is already running");
67
+ }
68
+ }
69
+
70
+ public void disconnect(PluginCall call) {
71
+ if (server != null) {
72
+ server.stop();
73
+ server = null;
74
+ }
75
+ call.resolve();
76
+ }
77
+
78
+ // Called by plugin when JS responds
79
+ public static void handleJsResponse(String requestId, String body) {
80
+ CompletableFuture<String> future = pendingResponses.remove(requestId);
81
+ if (future != null) {
82
+ future.complete(body);
83
+ }
84
+ }
85
+
86
+ // Helper to get local WiFi IP Address
87
+ private String getLocalIpAddress(Context context) {
88
+ WifiManager wm = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
89
+ if (wm != null && wm.getConnectionInfo() != null) {
90
+ return Formatter.formatIpAddress(wm.getConnectionInfo().getIpAddress());
91
+ }
92
+ return "127.0.0.1"; // fallback
93
+ }
94
+
95
+ private static class LocalNanoServer extends NanoHTTPD {
96
+ private Plugin plugin;
97
+
98
+ public LocalNanoServer(String hostname, int port, Plugin plugin) {
99
+ super(hostname, port);
100
+ this.plugin = plugin;
101
+ }
102
+
103
+ @Override
104
+ public Response serve(IHTTPSession session) {
105
+ String path = session.getUri();
106
+ String method = session.getMethod().name();
107
+
108
+ // Read body if needed
109
+ String body = "";
110
+ if (session.getMethod() == Method.POST || session.getMethod() == Method.PUT) {
111
+ try {
112
+ session.parseBody(new HashMap<>());
113
+ body = session.getQueryParameterString();
114
+ } catch (Exception e) { }
115
+ }
116
+
117
+ // Generate a unique requestId
118
+ String requestId = UUID.randomUUID().toString();
119
+
120
+ // Prepare data for JS
121
+ JSObject req = new JSObject();
122
+ req.put("requestId", requestId);
123
+ req.put("method", method);
124
+ req.put("path", path);
125
+ req.put("body", body);
126
+
127
+ // Future to wait for JS response
128
+ CompletableFuture<String> future = new CompletableFuture<>();
129
+ pendingResponses.put(requestId, future);
130
+
131
+ // Send event to JS
132
+ if (plugin instanceof com.cappitolian.plugins.httplocalservice.HttpLocalServerPlugin) {
133
+ ((com.cappitolian.plugins.httplocalservice.HttpLocalServerPlugin) plugin).fireOnRequest(req);
134
+ }
135
+
136
+ String jsResponse = null;
137
+ try {
138
+ // Wait up to 3 seconds for JS response
139
+ jsResponse = future.get(3, TimeUnit.SECONDS);
140
+ } catch (TimeoutException e) {
141
+ jsResponse = "{\"error\": \"Timeout waiting for JS response\"}";
142
+ } catch (Exception e) {
143
+ jsResponse = "{\"error\": \"Error waiting for JS response\"}";
144
+ } finally {
145
+ pendingResponses.remove(requestId);
146
+ }
147
+
148
+ Response response = newFixedLengthResponse(Response.Status.OK, "application/json", jsResponse);
149
+ addCorsHeaders(response);
150
+ return response;
151
+ }
152
+
153
+ private void addCorsHeaders(Response response) {
154
+ response.addHeader("Access-Control-Allow-Origin", "*");
155
+ response.addHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
156
+ response.addHeader("Access-Control-Allow-Headers", "origin, content-type, accept, authorization");
157
+ response.addHeader("Access-Control-Max-Age", "3600");
158
+ }
159
+ }
160
+ }
@@ -0,0 +1,92 @@
1
+ // package com.cappitolian.plugins.httplocalserver;
2
+
3
+ // import com.getcapacitor.JSObject;
4
+ // import com.getcapacitor.Plugin;
5
+ // import com.getcapacitor.PluginCall;
6
+ // import com.getcapacitor.PluginMethod;
7
+ // import com.getcapacitor.annotation.CapacitorPlugin;
8
+
9
+ // @CapacitorPlugin(name = "HttpLocalServer")
10
+ // public class HttpLocalServerPlugin extends Plugin {
11
+
12
+ // private HttpLocalServer implementation = new HttpLocalServer();
13
+
14
+ // @PluginMethod
15
+ // public void echo(PluginCall call) {
16
+ // String value = call.getString("value");
17
+
18
+ // JSObject ret = new JSObject();
19
+ // ret.put("value", implementation.echo(value));
20
+ // call.resolve(ret);
21
+ // }
22
+ // }
23
+
24
+ // package com.cappitolian.plugins.httplocalserver;
25
+
26
+ // import com.getcapacitor.JSObject;
27
+ // import com.getcapacitor.Plugin;
28
+ // import com.getcapacitor.PluginCall;
29
+ // import com.getcapacitor.PluginMethod;
30
+ // import com.getcapacitor.annotation.CapacitorPlugin;
31
+
32
+ // @CapacitorPlugin(name = "HttpLocalServer")
33
+ // public class HttpLocalServerPlugin extends Plugin {
34
+
35
+ // private HttpLocalServer implementation = new HttpLocalServer();
36
+
37
+ // @PluginMethod
38
+ // public void echo(PluginCall call) {
39
+ // String value = call.getString("value");
40
+
41
+ // JSObject ret = new JSObject();
42
+ // ret.put("value", implementation.echo(value));
43
+ // call.resolve(ret);
44
+ // }
45
+ // }
46
+
47
+ package com.cappitolian.plugins.httplocalservice;
48
+
49
+ import com.getcapacitor.*;
50
+ import com.getcapacitor.annotation.CapacitorPlugin;
51
+ import org.json.JSONException;
52
+ import org.json.JSONObject;
53
+
54
+ @CapacitorPlugin(name = "HttpLocalServer")
55
+ public class HttpLocalServerPlugin extends Plugin {
56
+
57
+ private HttpLocalServer localServer;
58
+
59
+ @PluginMethod
60
+ public void connect(PluginCall call) {
61
+ if (localServer == null) {
62
+ localServer = new HttpLocalServer(this);
63
+ }
64
+ localServer.connect(call);
65
+ }
66
+
67
+ // Add this method:
68
+ public void fireOnRequest(JSObject req) {
69
+ notifyListeners("onRequest", req, true);
70
+ }
71
+
72
+ @PluginMethod
73
+ public void disconnect(PluginCall call) {
74
+ if (localServer != null) {
75
+ localServer.disconnect(call);
76
+ } else {
77
+ call.resolve();
78
+ }
79
+ }
80
+
81
+ @PluginMethod
82
+ public void sendResponse(PluginCall call) {
83
+ String requestId = call.getString("requestId");
84
+ String body = call.getString("body");
85
+ if (requestId == null || body == null) {
86
+ call.reject("Missing requestId or body");
87
+ return;
88
+ }
89
+ HttpLocalServer.handleJsResponse(requestId, body);
90
+ call.resolve();
91
+ }
92
+ }
File without changes
package/dist/docs.json ADDED
@@ -0,0 +1,154 @@
1
+ {
2
+ "api": {
3
+ "name": "HttpLocalServerPlugin",
4
+ "slug": "httplocalserverplugin",
5
+ "docs": "",
6
+ "tags": [],
7
+ "methods": [
8
+ {
9
+ "name": "connect",
10
+ "signature": "() => Promise<HttpConnectResult>",
11
+ "parameters": [],
12
+ "returns": "Promise<HttpConnectResult>",
13
+ "tags": [],
14
+ "docs": "",
15
+ "complexTypes": [
16
+ "HttpConnectResult"
17
+ ],
18
+ "slug": "connect"
19
+ },
20
+ {
21
+ "name": "disconnect",
22
+ "signature": "() => Promise<void>",
23
+ "parameters": [],
24
+ "returns": "Promise<void>",
25
+ "tags": [],
26
+ "docs": "",
27
+ "complexTypes": [],
28
+ "slug": "disconnect"
29
+ },
30
+ {
31
+ "name": "sendResponse",
32
+ "signature": "(options: { requestId: string; body: string; }) => Promise<void>",
33
+ "parameters": [
34
+ {
35
+ "name": "options",
36
+ "docs": "",
37
+ "type": "{ requestId: string; body: string; }"
38
+ }
39
+ ],
40
+ "returns": "Promise<void>",
41
+ "tags": [],
42
+ "docs": "",
43
+ "complexTypes": [],
44
+ "slug": "sendresponse"
45
+ },
46
+ {
47
+ "name": "addListener",
48
+ "signature": "(eventName: 'onRequest', listenerFunc: (info: HttpRequestInfo) => void) => Promise<PluginListenerHandle> & PluginListenerHandle",
49
+ "parameters": [
50
+ {
51
+ "name": "eventName",
52
+ "docs": "",
53
+ "type": "'onRequest'"
54
+ },
55
+ {
56
+ "name": "listenerFunc",
57
+ "docs": "",
58
+ "type": "(info: HttpRequestInfo) => void"
59
+ }
60
+ ],
61
+ "returns": "Promise<PluginListenerHandle> & PluginListenerHandle",
62
+ "tags": [],
63
+ "docs": "",
64
+ "complexTypes": [
65
+ "PluginListenerHandle",
66
+ "HttpRequestInfo"
67
+ ],
68
+ "slug": "addlisteneronrequest-"
69
+ }
70
+ ],
71
+ "properties": []
72
+ },
73
+ "interfaces": [
74
+ {
75
+ "name": "HttpConnectResult",
76
+ "slug": "httpconnectresult",
77
+ "docs": "",
78
+ "tags": [],
79
+ "methods": [],
80
+ "properties": [
81
+ {
82
+ "name": "ip",
83
+ "tags": [],
84
+ "docs": "",
85
+ "complexTypes": [],
86
+ "type": "string"
87
+ },
88
+ {
89
+ "name": "port",
90
+ "tags": [],
91
+ "docs": "",
92
+ "complexTypes": [],
93
+ "type": "number"
94
+ }
95
+ ]
96
+ },
97
+ {
98
+ "name": "PluginListenerHandle",
99
+ "slug": "pluginlistenerhandle",
100
+ "docs": "",
101
+ "tags": [],
102
+ "methods": [],
103
+ "properties": [
104
+ {
105
+ "name": "remove",
106
+ "tags": [],
107
+ "docs": "",
108
+ "complexTypes": [],
109
+ "type": "() => Promise<void>"
110
+ }
111
+ ]
112
+ },
113
+ {
114
+ "name": "HttpRequestInfo",
115
+ "slug": "httprequestinfo",
116
+ "docs": "",
117
+ "tags": [],
118
+ "methods": [],
119
+ "properties": [
120
+ {
121
+ "name": "requestId",
122
+ "tags": [],
123
+ "docs": "",
124
+ "complexTypes": [],
125
+ "type": "string"
126
+ },
127
+ {
128
+ "name": "method",
129
+ "tags": [],
130
+ "docs": "",
131
+ "complexTypes": [],
132
+ "type": "string"
133
+ },
134
+ {
135
+ "name": "path",
136
+ "tags": [],
137
+ "docs": "",
138
+ "complexTypes": [],
139
+ "type": "string"
140
+ },
141
+ {
142
+ "name": "body",
143
+ "tags": [],
144
+ "docs": "",
145
+ "complexTypes": [],
146
+ "type": "string | undefined"
147
+ }
148
+ ]
149
+ }
150
+ ],
151
+ "enums": [],
152
+ "typeAliases": [],
153
+ "pluginConfigs": []
154
+ }
@@ -0,0 +1,22 @@
1
+ export interface HttpConnectResult {
2
+ ip: string;
3
+ port: number;
4
+ }
5
+ export interface HttpRequestInfo {
6
+ requestId: string;
7
+ method: string;
8
+ path: string;
9
+ body?: string;
10
+ }
11
+ export interface HttpLocalServerPlugin {
12
+ connect(): Promise<HttpConnectResult>;
13
+ disconnect(): Promise<void>;
14
+ sendResponse(options: {
15
+ requestId: string;
16
+ body: string;
17
+ }): Promise<void>;
18
+ addListener(eventName: 'onRequest', listenerFunc: (info: HttpRequestInfo) => void): Promise<PluginListenerHandle> & PluginListenerHandle;
19
+ }
20
+ export interface PluginListenerHandle {
21
+ remove: () => Promise<void>;
22
+ }
@@ -0,0 +1,5 @@
1
+ // export interface HttpLocalServerPlugin {
2
+ // echo(options: { value: string }): Promise<{ value: string }>;
3
+ // }
4
+ export {};
5
+ //# sourceMappingURL=definitions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,kEAAkE;AAClE,IAAI","sourcesContent":["// export interface HttpLocalServerPlugin {\n// echo(options: { value: string }): Promise<{ value: string }>;\n// }\n\nexport interface HttpConnectResult {\n ip: string;\n port: number;\n}\n\nexport interface HttpRequestInfo {\n requestId: string;\n method: string;\n path: string;\n body?: string;\n}\n\nexport interface HttpLocalServerPlugin {\n connect(): Promise<HttpConnectResult>;\n disconnect(): Promise<void>;\n sendResponse(options: { requestId: string; body: string }): Promise<void>;\n addListener(\n eventName: 'onRequest',\n listenerFunc: (info: HttpRequestInfo) => void,\n ): Promise<PluginListenerHandle> & PluginListenerHandle;\n}\n\nexport interface PluginListenerHandle {\n remove: () => Promise<void>;\n}"]}
@@ -0,0 +1,4 @@
1
+ import type { HttpLocalServerPlugin } from './definitions';
2
+ declare const HttpLocalServer: HttpLocalServerPlugin;
3
+ export * from './definitions';
4
+ export default HttpLocalServer;
@@ -0,0 +1,12 @@
1
+ // import { registerPlugin } from '@capacitor/core';
2
+ // import type { HttpLocalServerPlugin } from './definitions';
3
+ // const HttpLocalServer = registerPlugin<HttpLocalServerPlugin>('HttpLocalServer', {
4
+ // web: () => import('./web').then((m) => new m.HttpLocalServerWeb()),
5
+ // });
6
+ // export * from './definitions';
7
+ // export { HttpLocalServer };
8
+ import { registerPlugin } from '@capacitor/core';
9
+ const HttpLocalServer = registerPlugin('HttpLocalServer');
10
+ export * from './definitions';
11
+ export default HttpLocalServer;
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,oDAAoD;AAEpD,8DAA8D;AAE9D,qFAAqF;AACrF,wEAAwE;AACxE,MAAM;AAEN,iCAAiC;AACjC,8BAA8B;AAE9B,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjD,MAAM,eAAe,GAAG,cAAc,CAAwB,iBAAiB,CAAC,CAAC;AAEjF,cAAc,eAAe,CAAC;AAC9B,eAAe,eAAe,CAAC","sourcesContent":["// import { registerPlugin } from '@capacitor/core';\n\n// import type { HttpLocalServerPlugin } from './definitions';\n\n// const HttpLocalServer = registerPlugin<HttpLocalServerPlugin>('HttpLocalServer', {\n// web: () => import('./web').then((m) => new m.HttpLocalServerWeb()),\n// });\n\n// export * from './definitions';\n// export { HttpLocalServer };\n\nimport { registerPlugin } from '@capacitor/core';\nimport type { HttpLocalServerPlugin } from './definitions';\n\nconst HttpLocalServer = registerPlugin<HttpLocalServerPlugin>('HttpLocalServer');\n\nexport * from './definitions';\nexport default HttpLocalServer;"]}
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ var core = require('@capacitor/core');
4
+
5
+ // import { registerPlugin } from '@capacitor/core';
6
+ // import type { HttpLocalServerPlugin } from './definitions';
7
+ // const HttpLocalServer = registerPlugin<HttpLocalServerPlugin>('HttpLocalServer', {
8
+ // web: () => import('./web').then((m) => new m.HttpLocalServerWeb()),
9
+ // });
10
+ // export * from './definitions';
11
+ // export { HttpLocalServer };
12
+ const HttpLocalServer = core.registerPlugin('HttpLocalServer');
13
+
14
+ module.exports = HttpLocalServer;
15
+ //# sourceMappingURL=plugin.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.cjs.js","sources":["esm/index.js"],"sourcesContent":["// import { registerPlugin } from '@capacitor/core';\n// import type { HttpLocalServerPlugin } from './definitions';\n// const HttpLocalServer = registerPlugin<HttpLocalServerPlugin>('HttpLocalServer', {\n// web: () => import('./web').then((m) => new m.HttpLocalServerWeb()),\n// });\n// export * from './definitions';\n// export { HttpLocalServer };\nimport { registerPlugin } from '@capacitor/core';\nconst HttpLocalServer = registerPlugin('HttpLocalServer');\nexport * from './definitions';\nexport default HttpLocalServer;\n//# sourceMappingURL=index.js.map"],"names":["registerPlugin"],"mappings":";;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEK,MAAC,eAAe,GAAGA,mBAAc,CAAC,iBAAiB;;;;"}
package/dist/plugin.js ADDED
@@ -0,0 +1,16 @@
1
+ var capacitorHttpLocalServer = (function (core) {
2
+ 'use strict';
3
+
4
+ // import { registerPlugin } from '@capacitor/core';
5
+ // import type { HttpLocalServerPlugin } from './definitions';
6
+ // const HttpLocalServer = registerPlugin<HttpLocalServerPlugin>('HttpLocalServer', {
7
+ // web: () => import('./web').then((m) => new m.HttpLocalServerWeb()),
8
+ // });
9
+ // export * from './definitions';
10
+ // export { HttpLocalServer };
11
+ const HttpLocalServer = core.registerPlugin('HttpLocalServer');
12
+
13
+ return HttpLocalServer;
14
+
15
+ })(capacitorExports);
16
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sources":["esm/index.js"],"sourcesContent":["// import { registerPlugin } from '@capacitor/core';\n// import type { HttpLocalServerPlugin } from './definitions';\n// const HttpLocalServer = registerPlugin<HttpLocalServerPlugin>('HttpLocalServer', {\n// web: () => import('./web').then((m) => new m.HttpLocalServerWeb()),\n// });\n// export * from './definitions';\n// export { HttpLocalServer };\nimport { registerPlugin } from '@capacitor/core';\nconst HttpLocalServer = registerPlugin('HttpLocalServer');\nexport * from './definitions';\nexport default HttpLocalServer;\n//# sourceMappingURL=index.js.map"],"names":["registerPlugin"],"mappings":";;;CAAA;CACA;CACA;CACA;CACA;CACA;CACA;AAEK,OAAC,eAAe,GAAGA,mBAAc,CAAC,iBAAiB;;;;;;;;"}
@@ -0,0 +1,157 @@
1
+ // import Foundation
2
+
3
+ // @objc public class HttpLocalServer: NSObject {
4
+ // @objc public func echo(_ value: String) -> String {
5
+ // print(value)
6
+ // return value
7
+ // }
8
+ // }
9
+
10
+ // import Foundation
11
+
12
+ // @objc public class HttpLocalServer: NSObject {
13
+ // @objc public func echo(_ value: String) -> String {
14
+ // print(value)
15
+ // return value
16
+ // }
17
+ // }
18
+
19
+ import Foundation
20
+ import GCDWebServer
21
+ import Capacitor
22
+
23
+ public protocol HttpLocalServerDelegate: AnyObject {
24
+ func httpLocalServerDidReceiveRequest(_ data: [String: Any])
25
+ }
26
+
27
+ @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
+
33
+ public init(delegate: HttpLocalServerDelegate) {
34
+ self.delegate = delegate
35
+ }
36
+
37
+ @objc public func connect(_ call: CAPPluginCall) {
38
+ DispatchQueue.main.async { [weak self] in
39
+ guard let self = self else { return }
40
+
41
+ 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
95
+ do {
96
+ try self.webServer?.start(options: [
97
+ GCDWebServerOption_Port: port,
98
+ GCDWebServerOption_BonjourName: "",
99
+ GCDWebServerOption_BindToLocalhost: false
100
+ ])
101
+ let ip = Self.getWiFiAddress() ?? "127.0.0.1"
102
+ call.resolve([
103
+ "ip": ip,
104
+ "port": port
105
+ ])
106
+ } catch {
107
+ call.reject("Failed to start server: \(error.localizedDescription)")
108
+ }
109
+ }
110
+ }
111
+
112
+ @objc public func disconnect(_ call: CAPPluginCall) {
113
+ DispatchQueue.main.async { [weak self] in
114
+ self?.webServer?.stop()
115
+ self?.webServer = nil
116
+ call.resolve()
117
+ }
118
+ }
119
+
120
+ // Called by plugin when JS responds
121
+ static func handleJsResponse(requestId: String, body: String) {
122
+ queue.async {
123
+ if let callback = pendingResponses[requestId] {
124
+ callback(body)
125
+ pendingResponses.removeValue(forKey: requestId)
126
+ }
127
+ }
128
+ }
129
+
130
+ // Helper: get WiFi IP address (IPv4)
131
+ static func getWiFiAddress() -> String? {
132
+ var address: String?
133
+ 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
+ }
153
+ freeifaddrs(ifaddr)
154
+ }
155
+ return address
156
+ }
157
+ }
@@ -0,0 +1,67 @@
1
+ // import Foundation
2
+ // import Capacitor
3
+
4
+ // /**
5
+ // * Please read the Capacitor iOS Plugin Development Guide
6
+ // * here: https://capacitorjs.com/docs/plugins/ios
7
+ // */
8
+ // @objc(HttpLocalServerPlugin)
9
+ // public class HttpLocalServerPlugin: CAPPlugin, CAPBridgedPlugin {
10
+ // public let identifier = "HttpLocalServerPlugin"
11
+ // public let jsName = "HttpLocalServer"
12
+ // public let pluginMethods: [CAPPluginMethod] = [
13
+ // CAPPluginMethod(name: "echo", returnType: CAPPluginReturnPromise)
14
+ // ]
15
+ // private let implementation = HttpLocalServer()
16
+
17
+ // @objc func echo(_ call: CAPPluginCall) {
18
+ // let value = call.getString("value") ?? ""
19
+ // call.resolve([
20
+ // "value": implementation.echo(value)
21
+ // ])
22
+ // }
23
+ // }
24
+
25
+ import Foundation
26
+ import Capacitor
27
+ @objc(HttpLocalServerPlugin)
28
+ public class HttpLocalServerPlugin: CAPPlugin, CAPBridgedPlugin, HttpLocalServerDelegate {
29
+ public let identifier = "HttpLocalServerPlugin"
30
+ public let jsName = "HttpLocalServer"
31
+ public let pluginMethods: [CAPPluginMethod] = [
32
+ CAPPluginMethod(name: "connect", returnType: CAPPluginReturnPromise),
33
+ CAPPluginMethod(name: "disconnect", returnType: CAPPluginReturnPromise),
34
+ CAPPluginMethod(name: "sendResponse", returnType: CAPPluginReturnPromise)
35
+ ]
36
+ var localServer: HttpLocalServer?
37
+
38
+ @objc func connect(_ call: CAPPluginCall) {
39
+ if localServer == nil {
40
+ localServer = HttpLocalServer(delegate: self)
41
+ }
42
+ localServer?.connect(call)
43
+ }
44
+
45
+ @objc func disconnect(_ call: CAPPluginCall) {
46
+ if localServer != nil {
47
+ localServer?.disconnect(call)
48
+ } else {
49
+ call.resolve()
50
+ }
51
+ }
52
+
53
+ @objc func sendResponse(_ call: CAPPluginCall) {
54
+ guard let requestId = call.getString("requestId"),
55
+ let body = call.getString("body") else {
56
+ call.reject("Missing requestId or body")
57
+ return
58
+ }
59
+ HttpLocalServer.handleJsResponse(requestId: requestId, body: body)
60
+ call.resolve()
61
+ }
62
+
63
+ // Delegate method
64
+ public func httpLocalServerDidReceiveRequest(_ data: [String: Any]) {
65
+ notifyListeners("onRequest", data: data)
66
+ }
67
+ }
@@ -0,0 +1,15 @@
1
+ import XCTest
2
+ @testable import HttpLocalServerPlugin
3
+
4
+ class HttpLocalServerTests: XCTestCase {
5
+ func testEcho() {
6
+ // This is an example of a functional test case for a plugin.
7
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
8
+
9
+ let implementation = HttpLocalServer()
10
+ let value = "Hello, World!"
11
+ let result = implementation.echo(value)
12
+
13
+ XCTAssertEqual(value, result)
14
+ }
15
+ }
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@cappitolian/http-local-server",
3
+ "version": "0.0.1",
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
+ "main": "dist/plugin.cjs.js",
6
+ "module": "dist/esm/index.js",
7
+ "types": "dist/esm/index.d.ts",
8
+ "unpkg": "dist/plugin.js",
9
+ "files": [
10
+ "android/src/main/",
11
+ "android/build.gradle",
12
+ "dist/",
13
+ "ios/Sources",
14
+ "ios/Tests",
15
+ "Package.swift",
16
+ "CappitolianHttpLocalServer.podspec"
17
+ ],
18
+ "author": "Cappitolian",
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/alessandrycruz1987/cappitolian/plugins/http-local-server.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/alessandrycruz1987/cappitolian/plugins/http-local-server/issues"
26
+ },
27
+ "keywords": [
28
+ "capacitor",
29
+ "plugin",
30
+ "native"
31
+ ],
32
+ "scripts": {
33
+ "verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
34
+ "verify:ios": "xcodebuild -scheme CappitolianHttpLocalServer -destination generic/platform=iOS",
35
+ "verify:android": "cd android && ./gradlew clean build test && cd ..",
36
+ "verify:web": "npm run build",
37
+ "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
38
+ "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
39
+ "eslint": "eslint . --ext ts",
40
+ "prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
41
+ "swiftlint": "node-swiftlint",
42
+ "docgen": "docgen --api HttpLocalServerPlugin --output-readme README.md --output-json dist/docs.json",
43
+ "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
44
+ "clean": "rimraf ./dist",
45
+ "watch": "tsc --watch",
46
+ "prepublishOnly": "npm run build"
47
+ },
48
+ "devDependencies": {
49
+ "@capacitor/android": "^7.0.0",
50
+ "@capacitor/core": "^7.0.0",
51
+ "@capacitor/docgen": "^0.3.0",
52
+ "@capacitor/ios": "^7.0.0",
53
+ "@ionic/eslint-config": "^0.4.0",
54
+ "@ionic/prettier-config": "^4.0.0",
55
+ "@ionic/swiftlint-config": "^2.0.0",
56
+ "eslint": "^8.57.0",
57
+ "prettier": "^3.4.2",
58
+ "prettier-plugin-java": "^2.6.6",
59
+ "rimraf": "^6.0.1",
60
+ "rollup": "^4.30.1",
61
+ "swiftlint": "^2.0.0",
62
+ "typescript": "~4.1.5"
63
+ },
64
+ "peerDependencies": {
65
+ "@capacitor/core": ">=7.0.0"
66
+ },
67
+ "prettier": "@ionic/prettier-config",
68
+ "swiftlint": "@ionic/swiftlint-config",
69
+ "eslintConfig": {
70
+ "extends": "@ionic/eslint-config/recommended"
71
+ },
72
+ "capacitor": {
73
+ "ios": {
74
+ "src": "ios"
75
+ },
76
+ "android": {
77
+ "src": "android"
78
+ }
79
+ }
80
+ }