expo-http-server 0.1.0
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/.eslintrc.js +5 -0
- package/README.md +5 -0
- package/android/build.gradle +94 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/expo/modules/httpserver/ExpoHttpServerModule.kt +81 -0
- package/build/ExpoHttpServerModule.d.ts +3 -0
- package/build/ExpoHttpServerModule.d.ts.map +1 -0
- package/build/ExpoHttpServerModule.js +3 -0
- package/build/ExpoHttpServerModule.js.map +1 -0
- package/build/index.d.ts +25 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +29 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/ExpoHttpServer.podspec +29 -0
- package/ios/ExpoHttpServerModule.swift +132 -0
- package/package.json +42 -0
- package/src/ExpoHttpServerModule.ts +3 -0
- package/src/index.ts +73 -0
- package/tsconfig.json +9 -0
package/.eslintrc.js
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
apply plugin: 'com.android.library'
|
|
2
|
+
apply plugin: 'kotlin-android'
|
|
3
|
+
apply plugin: 'maven-publish'
|
|
4
|
+
|
|
5
|
+
group = 'expo.modules.httpserver'
|
|
6
|
+
version = '0.1.0'
|
|
7
|
+
|
|
8
|
+
buildscript {
|
|
9
|
+
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
10
|
+
if (expoModulesCorePlugin.exists()) {
|
|
11
|
+
apply from: expoModulesCorePlugin
|
|
12
|
+
applyKotlinExpoModulesCorePlugin()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Simple helper that allows the root project to override versions declared by this library.
|
|
16
|
+
ext.safeExtGet = { prop, fallback ->
|
|
17
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Ensures backward compatibility
|
|
21
|
+
ext.getKotlinVersion = {
|
|
22
|
+
if (ext.has("kotlinVersion")) {
|
|
23
|
+
ext.kotlinVersion()
|
|
24
|
+
} else {
|
|
25
|
+
ext.safeExtGet("kotlinVersion", "1.8.10")
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
repositories {
|
|
30
|
+
mavenCentral()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
dependencies {
|
|
34
|
+
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}")
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
afterEvaluate {
|
|
39
|
+
publishing {
|
|
40
|
+
publications {
|
|
41
|
+
release(MavenPublication) {
|
|
42
|
+
from components.release
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
repositories {
|
|
46
|
+
maven {
|
|
47
|
+
url = mavenLocal().url
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
android {
|
|
54
|
+
compileSdkVersion safeExtGet("compileSdkVersion", 33)
|
|
55
|
+
|
|
56
|
+
def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
|
|
57
|
+
if (agpVersion.tokenize('.')[0].toInteger() < 8) {
|
|
58
|
+
compileOptions {
|
|
59
|
+
sourceCompatibility JavaVersion.VERSION_11
|
|
60
|
+
targetCompatibility JavaVersion.VERSION_11
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
kotlinOptions {
|
|
64
|
+
jvmTarget = JavaVersion.VERSION_11.majorVersion
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
namespace "expo.modules.httpserver"
|
|
69
|
+
defaultConfig {
|
|
70
|
+
minSdkVersion safeExtGet("minSdkVersion", 21)
|
|
71
|
+
targetSdkVersion safeExtGet("targetSdkVersion", 34)
|
|
72
|
+
versionCode 1
|
|
73
|
+
versionName "0.1.0"
|
|
74
|
+
}
|
|
75
|
+
lintOptions {
|
|
76
|
+
abortOnError false
|
|
77
|
+
}
|
|
78
|
+
publishing {
|
|
79
|
+
singleVariant("release") {
|
|
80
|
+
withSourcesJar()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
repositories {
|
|
86
|
+
mavenCentral()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
dependencies {
|
|
90
|
+
implementation project(':expo-modules-core')
|
|
91
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
|
|
92
|
+
implementation 'com.github.fengzhizi715.AndroidServer:core:1.3.3'
|
|
93
|
+
implementation 'com.github.fengzhizi715.AndroidServer:gson:1.3.3'
|
|
94
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
package expo.modules.httpserver
|
|
2
|
+
|
|
3
|
+
import androidx.core.os.bundleOf
|
|
4
|
+
import expo.modules.kotlin.modules.Module
|
|
5
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
6
|
+
import com.safframework.server.core.AndroidServer
|
|
7
|
+
import com.safframework.server.core.RequestHandler
|
|
8
|
+
import com.safframework.server.core.Server
|
|
9
|
+
import com.safframework.server.core.http.HttpMethod
|
|
10
|
+
import com.safframework.server.core.http.Request
|
|
11
|
+
import com.safframework.server.core.http.Response
|
|
12
|
+
import org.json.JSONObject
|
|
13
|
+
import java.util.UUID
|
|
14
|
+
|
|
15
|
+
class ExpoHttpServerModule : Module() {
|
|
16
|
+
class SimpleHttpResponse(val statusCode: Int,
|
|
17
|
+
val contentType: String,
|
|
18
|
+
val body: String)
|
|
19
|
+
|
|
20
|
+
private lateinit var server: Server
|
|
21
|
+
private val responses = HashMap<String, SimpleHttpResponse>()
|
|
22
|
+
|
|
23
|
+
private val requestHandler: RequestHandler = { request: Request, response: Response ->
|
|
24
|
+
val uuid = UUID.randomUUID().toString()
|
|
25
|
+
val headers: Map<String, String> = request.headers()
|
|
26
|
+
val params: Map<String, String> = request.params()
|
|
27
|
+
val cookies: Map<String, String> = request.cookies().associate { it.name() to it.value() }
|
|
28
|
+
sendEvent("onRequest", bundleOf(
|
|
29
|
+
"uuid" to uuid,
|
|
30
|
+
"method" to request.method().name,
|
|
31
|
+
"path" to request.url(),
|
|
32
|
+
"content" to request.content(),
|
|
33
|
+
"headersJson" to JSONObject(headers).toString(),
|
|
34
|
+
"paramsJson" to JSONObject(params).toString(),
|
|
35
|
+
"cookiesJson" to JSONObject(cookies).toString(),
|
|
36
|
+
))
|
|
37
|
+
while (!responses.containsKey(uuid)) {
|
|
38
|
+
Thread.sleep(10)
|
|
39
|
+
}
|
|
40
|
+
val res = responses[uuid]!!
|
|
41
|
+
responses.remove(uuid);
|
|
42
|
+
response.setBodyText(res.body)
|
|
43
|
+
response.setStatus(res.statusCode)
|
|
44
|
+
response.addHeader("Content-Length", "" + res.body.length)
|
|
45
|
+
response.addHeader("Content-Type", res.contentType)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
override fun definition() = ModuleDefinition {
|
|
49
|
+
|
|
50
|
+
Name("ExpoHttpServer")
|
|
51
|
+
|
|
52
|
+
Events("onRequest")
|
|
53
|
+
|
|
54
|
+
Function("setup") { port: Int ->
|
|
55
|
+
server = AndroidServer.Builder{
|
|
56
|
+
port {
|
|
57
|
+
port
|
|
58
|
+
}
|
|
59
|
+
}.build()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Function("route") { path: String, method: String ->
|
|
63
|
+
server = server.request(HttpMethod.getMethod(method), path, requestHandler);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
Function("start") {
|
|
67
|
+
server.start()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
Function("respond") { uuid: String,
|
|
71
|
+
statusCode: Int,
|
|
72
|
+
contentType: String,
|
|
73
|
+
body: String ->
|
|
74
|
+
responses[uuid] = SimpleHttpResponse(statusCode, contentType, body);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
Function("stop") {
|
|
78
|
+
server.close()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoHttpServerModule.d.ts","sourceRoot":"","sources":["../src/ExpoHttpServerModule.ts"],"names":[],"mappings":";AAEA,wBAAqD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoHttpServerModule.js","sourceRoot":"","sources":["../src/ExpoHttpServerModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,eAAe,mBAAmB,CAAC,gBAAgB,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from \"expo-modules-core\";\n\nexport default requireNativeModule(\"ExpoHttpServer\");\n"]}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
|
|
2
|
+
export interface RequestEvent {
|
|
3
|
+
uuid: string;
|
|
4
|
+
method: string;
|
|
5
|
+
path: string;
|
|
6
|
+
content: string;
|
|
7
|
+
headersJson: string;
|
|
8
|
+
paramsJson: string;
|
|
9
|
+
cookiesJson: string;
|
|
10
|
+
}
|
|
11
|
+
export interface Response {
|
|
12
|
+
statusCode?: number;
|
|
13
|
+
contentType?: string;
|
|
14
|
+
body?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface Callback {
|
|
17
|
+
method: string;
|
|
18
|
+
path: string;
|
|
19
|
+
callback: (request: RequestEvent) => Promise<Response>;
|
|
20
|
+
}
|
|
21
|
+
export declare const start: () => void;
|
|
22
|
+
export declare const route: (path: string, method: HttpMethod, callback: (request: RequestEvent) => Promise<Response>) => void;
|
|
23
|
+
export declare const setup: (port: number) => any;
|
|
24
|
+
export declare const stop: () => any;
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;AAE3D,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,QAAQ;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CACxD;AAED,eAAO,MAAM,KAAK,YAyBjB,CAAC;AAEF,eAAO,MAAM,KAAK,SACV,MAAM,UACJ,UAAU,sBACE,YAAY,KAAK,QAAQ,QAAQ,CAAC,SAQvD,CAAC;AAEF,eAAO,MAAM,KAAK,SAAU,MAAM,QAAqC,CAAC;AACxE,eAAO,MAAM,IAAI,WAAoC,CAAC"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { EventEmitter } from "expo-modules-core";
|
|
2
|
+
import ExpoHttpServerModule from "./ExpoHttpServerModule";
|
|
3
|
+
const emitter = new EventEmitter(ExpoHttpServerModule);
|
|
4
|
+
const requestCallbacks = [];
|
|
5
|
+
export const start = () => {
|
|
6
|
+
ExpoHttpServerModule.start();
|
|
7
|
+
emitter.addListener("onRequest", async (event) => {
|
|
8
|
+
const callbacks = requestCallbacks.filter((c) => event.path === c.path && event.method === c.method);
|
|
9
|
+
if (!callbacks.length) {
|
|
10
|
+
ExpoHttpServerModule.respond(event.uuid, 200, "No callback", "No callback");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
for (const c of callbacks) {
|
|
14
|
+
const response = await c.callback(event);
|
|
15
|
+
ExpoHttpServerModule.respond(event.uuid, response.statusCode || 200, response.contentType || "application/json", response.body || "{}");
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
export const route = (path, method, callback) => {
|
|
20
|
+
requestCallbacks.push({
|
|
21
|
+
method,
|
|
22
|
+
path,
|
|
23
|
+
callback,
|
|
24
|
+
});
|
|
25
|
+
ExpoHttpServerModule.route(path, method);
|
|
26
|
+
};
|
|
27
|
+
export const setup = (port) => ExpoHttpServerModule.setup(port);
|
|
28
|
+
export const stop = () => ExpoHttpServerModule.stop();
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,oBAAoB,MAAM,wBAAwB,CAAC;AAE1D,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,oBAAoB,CAAC,CAAC;AACvD,MAAM,gBAAgB,GAAe,EAAE,CAAC;AA0BxC,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,EAAE;IACxB,oBAAoB,CAAC,KAAK,EAAE,CAAC;IAC7B,OAAO,CAAC,WAAW,CAAe,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QAC7D,MAAM,SAAS,GAAG,gBAAgB,CAAC,MAAM,CACvC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,CAC1D,CAAC;QACF,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;YACtB,oBAAoB,CAAC,OAAO,CAC1B,KAAK,CAAC,IAAI,EACV,GAAG,EACH,aAAa,EACb,aAAa,CACd,CAAC;YACF,OAAO;QACT,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;YAC1B,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YACzC,oBAAoB,CAAC,OAAO,CAC1B,KAAK,CAAC,IAAI,EACV,QAAQ,CAAC,UAAU,IAAI,GAAG,EAC1B,QAAQ,CAAC,WAAW,IAAI,kBAAkB,EAC1C,QAAQ,CAAC,IAAI,IAAI,IAAI,CACtB,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,KAAK,GAAG,CACnB,IAAY,EACZ,MAAkB,EAClB,QAAsD,EACtD,EAAE;IACF,gBAAgB,CAAC,IAAI,CAAC;QACpB,MAAM;QACN,IAAI;QACJ,QAAQ;KACT,CAAC,CAAC;IACH,oBAAoB,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3C,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACxE,MAAM,CAAC,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,oBAAoB,CAAC,IAAI,EAAE,CAAC","sourcesContent":["import { EventEmitter } from \"expo-modules-core\";\n\nimport ExpoHttpServerModule from \"./ExpoHttpServerModule\";\n\nconst emitter = new EventEmitter(ExpoHttpServerModule);\nconst requestCallbacks: Callback[] = [];\n\nexport type HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\";\n\nexport interface RequestEvent {\n uuid: string;\n method: string;\n path: string;\n content: string;\n headersJson: string;\n paramsJson: string;\n cookiesJson: string;\n}\n\nexport interface Response {\n statusCode?: number;\n contentType?: string;\n body?: string;\n}\n\nexport interface Callback {\n method: string;\n path: string;\n callback: (request: RequestEvent) => Promise<Response>;\n}\n\nexport const start = () => {\n ExpoHttpServerModule.start();\n emitter.addListener<RequestEvent>(\"onRequest\", async (event) => {\n const callbacks = requestCallbacks.filter(\n (c) => event.path === c.path && event.method === c.method,\n );\n if (!callbacks.length) {\n ExpoHttpServerModule.respond(\n event.uuid,\n 200,\n \"No callback\",\n \"No callback\",\n );\n return;\n }\n for (const c of callbacks) {\n const response = await c.callback(event);\n ExpoHttpServerModule.respond(\n event.uuid,\n response.statusCode || 200,\n response.contentType || \"application/json\",\n response.body || \"{}\",\n );\n }\n });\n};\n\nexport const route = (\n path: string,\n method: HttpMethod,\n callback: (request: RequestEvent) => Promise<Response>,\n) => {\n requestCallbacks.push({\n method,\n path,\n callback,\n });\n ExpoHttpServerModule.route(path, method);\n};\n\nexport const setup = (port: number) => ExpoHttpServerModule.setup(port);\nexport const stop = () => ExpoHttpServerModule.stop();\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
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 = 'ExpoHttpServer'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.description = package['description']
|
|
10
|
+
s.license = package['license']
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.homepage = package['homepage']
|
|
13
|
+
s.platform = :ios, '13.4'
|
|
14
|
+
s.swift_version = '5.4'
|
|
15
|
+
s.source = { git: 'https://github.com/simonsturge/expo-http-server' }
|
|
16
|
+
s.static_framework = true
|
|
17
|
+
|
|
18
|
+
s.dependency 'ExpoModulesCore'
|
|
19
|
+
s.dependency 'Criollo'
|
|
20
|
+
s.dependency 'CocoaAsyncSocket'
|
|
21
|
+
|
|
22
|
+
# Swift/Objective-C compatibility
|
|
23
|
+
s.pod_target_xcconfig = {
|
|
24
|
+
'DEFINES_MODULE' => 'YES',
|
|
25
|
+
'SWIFT_COMPILATION_MODE' => 'wholemodule'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
s.source_files = "**/*.{h,m,swift}"
|
|
29
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import Criollo
|
|
3
|
+
|
|
4
|
+
public class ExpoHttpServerModule: Module {
|
|
5
|
+
private let server = CRHTTPServer()
|
|
6
|
+
private var port: Int?
|
|
7
|
+
private var responses = [String: CRResponse]()
|
|
8
|
+
private var bgTaskIdentifier = UIBackgroundTaskIdentifier.invalid
|
|
9
|
+
|
|
10
|
+
public func definition() -> ModuleDefinition {
|
|
11
|
+
Name("ExpoHttpServer")
|
|
12
|
+
|
|
13
|
+
Events("onRequest")
|
|
14
|
+
|
|
15
|
+
Function("setup", setupHandler)
|
|
16
|
+
Function("start", startHandler)
|
|
17
|
+
Function("route", routeHandler)
|
|
18
|
+
Function("respond", respondHandler)
|
|
19
|
+
Function("stop", stopHandler)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private func setupHandler(port: Int) {
|
|
23
|
+
NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [unowned self] notification in
|
|
24
|
+
if (self.server.isListening) {
|
|
25
|
+
self.endBackgroundTask()
|
|
26
|
+
self.beginBackgroundTask()
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
self.port = port;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private func startHandler() {
|
|
33
|
+
if (server.isListening) { return }
|
|
34
|
+
beginBackgroundTask()
|
|
35
|
+
var error: NSError?
|
|
36
|
+
if let port = port {
|
|
37
|
+
server.startListening(&error, portNumber: UInt(port))
|
|
38
|
+
} else {
|
|
39
|
+
server.startListening(&error)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private func routeHandler(path: String, method: String) {
|
|
44
|
+
server.add(path, block: { [weak self] (req, res, next) in
|
|
45
|
+
let uuid = UUID().uuidString
|
|
46
|
+
self?.sendEvent("onRequest", [
|
|
47
|
+
"uuid": uuid,
|
|
48
|
+
"method": req.method.toString(),
|
|
49
|
+
"path": path,
|
|
50
|
+
"body": req.body,
|
|
51
|
+
"headersJson": req.allHTTPHeaderFields.jsonString,
|
|
52
|
+
"paramsJson": req.query.jsonString,
|
|
53
|
+
"cookiesJson": req.cookies?.jsonString ?? "{}"
|
|
54
|
+
])
|
|
55
|
+
self?.responses[uuid] = res
|
|
56
|
+
}, recursive: false, method: CRHTTPMethod.fromString(method))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private func respondHandler(udid: String,
|
|
60
|
+
statusCode: Int,
|
|
61
|
+
contentType: String,
|
|
62
|
+
body: String) {
|
|
63
|
+
if let response = responses[udid] {
|
|
64
|
+
response.setStatusCode(UInt(statusCode), description: "Success")
|
|
65
|
+
response.setValue(contentType, forHTTPHeaderField: "Content-type")
|
|
66
|
+
response.setValue("\(body.count)", forHTTPHeaderField: "Content-Length")
|
|
67
|
+
response.send(body);
|
|
68
|
+
responses[udid] = nil;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private func stopHandler() {
|
|
73
|
+
if (server.isListening) {
|
|
74
|
+
server.stopListening()
|
|
75
|
+
endBackgroundTask()
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private func beginBackgroundTask() {
|
|
80
|
+
if (bgTaskIdentifier == UIBackgroundTaskIdentifier.invalid) {
|
|
81
|
+
self.bgTaskIdentifier = UIApplication.shared.beginBackgroundTask(withName: "BgTask", expirationHandler: {
|
|
82
|
+
self.endBackgroundTask()
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private func endBackgroundTask() {
|
|
88
|
+
if (bgTaskIdentifier != UIBackgroundTaskIdentifier.invalid) {
|
|
89
|
+
UIApplication.shared.endBackgroundTask(bgTaskIdentifier)
|
|
90
|
+
bgTaskIdentifier = UIBackgroundTaskIdentifier.invalid
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
extension Dictionary {
|
|
96
|
+
var jsonString: String {
|
|
97
|
+
guard let data = try? JSONSerialization.data(withJSONObject: self) else {
|
|
98
|
+
return "{}";
|
|
99
|
+
}
|
|
100
|
+
return String(data: data, encoding: .utf8) ?? "{}"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
extension CRHTTPMethod {
|
|
105
|
+
func toString() -> String {
|
|
106
|
+
switch self {
|
|
107
|
+
case .post:
|
|
108
|
+
return "POST"
|
|
109
|
+
case .put:
|
|
110
|
+
return "PUT"
|
|
111
|
+
case .delete:
|
|
112
|
+
return "DELETE"
|
|
113
|
+
default:
|
|
114
|
+
return "GET"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
static func fromString(_ string: String) -> Self {
|
|
119
|
+
var httpMethod: CRHTTPMethod
|
|
120
|
+
switch (string) {
|
|
121
|
+
case "POST":
|
|
122
|
+
httpMethod = .post
|
|
123
|
+
case "PUT":
|
|
124
|
+
httpMethod = .put
|
|
125
|
+
case "DELETE":
|
|
126
|
+
httpMethod = .delete
|
|
127
|
+
default:
|
|
128
|
+
httpMethod = .get
|
|
129
|
+
}
|
|
130
|
+
return httpMethod
|
|
131
|
+
}
|
|
132
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "expo-http-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A simple HTTP server expo module (iOS/Android)",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"types": "build/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "expo-module build",
|
|
9
|
+
"clean": "expo-module clean",
|
|
10
|
+
"lint": "expo-module lint",
|
|
11
|
+
"test": "expo-module test",
|
|
12
|
+
"prepare": "expo-module prepare",
|
|
13
|
+
"prepublishOnly": "expo-module prepublishOnly",
|
|
14
|
+
"expo-module": "expo-module",
|
|
15
|
+
"open:ios": "open -a \"Xcode\" example/ios",
|
|
16
|
+
"open:android": "open -a \"Android Studio\" example/android"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"react-native",
|
|
20
|
+
"expo",
|
|
21
|
+
"expo-http-server",
|
|
22
|
+
"ExpoHttpServer"
|
|
23
|
+
],
|
|
24
|
+
"repository": "https://github.com/simonsturge/expo-http-server",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/simonsturge/expo-http-server/issues"
|
|
27
|
+
},
|
|
28
|
+
"author": "Simon <development@simonsturge.com> (https://github.com/simonsturge)",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"homepage": "https://github.com/simonsturge/expo-http-server#readme",
|
|
31
|
+
"dependencies": {},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/react": "^18.0.25",
|
|
34
|
+
"expo-module-scripts": "^3.4.0",
|
|
35
|
+
"expo-modules-core": "^1.11.8"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"expo": "*",
|
|
39
|
+
"react": "*",
|
|
40
|
+
"react-native": "*"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { EventEmitter } from "expo-modules-core";
|
|
2
|
+
|
|
3
|
+
import ExpoHttpServerModule from "./ExpoHttpServerModule";
|
|
4
|
+
|
|
5
|
+
const emitter = new EventEmitter(ExpoHttpServerModule);
|
|
6
|
+
const requestCallbacks: Callback[] = [];
|
|
7
|
+
|
|
8
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
|
|
9
|
+
|
|
10
|
+
export interface RequestEvent {
|
|
11
|
+
uuid: string;
|
|
12
|
+
method: string;
|
|
13
|
+
path: string;
|
|
14
|
+
content: string;
|
|
15
|
+
headersJson: string;
|
|
16
|
+
paramsJson: string;
|
|
17
|
+
cookiesJson: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface Response {
|
|
21
|
+
statusCode?: number;
|
|
22
|
+
contentType?: string;
|
|
23
|
+
body?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface Callback {
|
|
27
|
+
method: string;
|
|
28
|
+
path: string;
|
|
29
|
+
callback: (request: RequestEvent) => Promise<Response>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const start = () => {
|
|
33
|
+
ExpoHttpServerModule.start();
|
|
34
|
+
emitter.addListener<RequestEvent>("onRequest", async (event) => {
|
|
35
|
+
const callbacks = requestCallbacks.filter(
|
|
36
|
+
(c) => event.path === c.path && event.method === c.method,
|
|
37
|
+
);
|
|
38
|
+
if (!callbacks.length) {
|
|
39
|
+
ExpoHttpServerModule.respond(
|
|
40
|
+
event.uuid,
|
|
41
|
+
200,
|
|
42
|
+
"No callback",
|
|
43
|
+
"No callback",
|
|
44
|
+
);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
for (const c of callbacks) {
|
|
48
|
+
const response = await c.callback(event);
|
|
49
|
+
ExpoHttpServerModule.respond(
|
|
50
|
+
event.uuid,
|
|
51
|
+
response.statusCode || 200,
|
|
52
|
+
response.contentType || "application/json",
|
|
53
|
+
response.body || "{}",
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const route = (
|
|
60
|
+
path: string,
|
|
61
|
+
method: HttpMethod,
|
|
62
|
+
callback: (request: RequestEvent) => Promise<Response>,
|
|
63
|
+
) => {
|
|
64
|
+
requestCallbacks.push({
|
|
65
|
+
method,
|
|
66
|
+
path,
|
|
67
|
+
callback,
|
|
68
|
+
});
|
|
69
|
+
ExpoHttpServerModule.route(path, method);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const setup = (port: number) => ExpoHttpServerModule.setup(port);
|
|
73
|
+
export const stop = () => ExpoHttpServerModule.stop();
|