@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.
- package/CappitolianHttpLocalServer.podspec +18 -0
- package/Package.swift +28 -0
- package/README.md +103 -0
- package/android/build.gradle +59 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/cappitolian/plugins/httplocalserver/HttpLocalServer.java +160 -0
- package/android/src/main/java/com/cappitolian/plugins/httplocalserver/HttpLocalServerPlugin.java +92 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +154 -0
- package/dist/esm/definitions.d.ts +22 -0
- package/dist/esm/definitions.js +5 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +12 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/plugin.cjs.js +15 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +16 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/HttpLocalServerPlugin/HttpLocalServer.swift +157 -0
- package/ios/Sources/HttpLocalServerPlugin/HttpLocalServerPlugin.swift +67 -0
- package/ios/Tests/HttpLocalServerPluginTests/HttpLocalServerPluginTests.swift +15 -0
- package/package.json +80 -0
|
@@ -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<<a href="#httpconnectresult">HttpConnectResult</a>></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>) => void</code> |
|
|
70
|
+
|
|
71
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>> & <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>() => Promise<void></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,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
|
+
}
|
package/android/src/main/java/com/cappitolian/plugins/httplocalserver/HttpLocalServerPlugin.java
ADDED
|
@@ -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 @@
|
|
|
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,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
|
+
}
|