expo-http-server 0.1.14-beta.4 → 0.1.14-beta.5

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.
@@ -10,6 +10,7 @@ import com.safframework.server.core.http.Request
10
10
  import com.safframework.server.core.http.Response
11
11
  import org.json.JSONObject
12
12
  import java.net.BindException
13
+ import java.util.UUID
13
14
  import java.util.concurrent.ConcurrentHashMap
14
15
 
15
16
  class ExpoHttpServerModule : Module() {
@@ -60,6 +61,7 @@ class ExpoHttpServerModule : Module() {
60
61
  // rebuilding in that case only adds an unnecessary close/rebind race.
61
62
  private var hasBoundSocket = false
62
63
  private var configuredPort: Int = 0
64
+ private var configuredLoopbackOnly = false
63
65
  private val routeConfigs = mutableListOf<RouteConfig>()
64
66
  private val responses = ConcurrentHashMap<String, SimpleHttpResponse>()
65
67
  private val onBindFailure: (String) -> Unit = { message ->
@@ -75,8 +77,10 @@ class ExpoHttpServerModule : Module() {
75
77
  val headers: Map<String, String> = request.headers()
76
78
  val params: Map<String, String> = request.params()
77
79
  val cookies: Map<String, String> = request.cookies().associate { it.name() to it.value() }
80
+ val requestId = UUID.randomUUID().toString()
78
81
  sendEvent("onRequest", bundleOf(
79
82
  "uuid" to uuid,
83
+ "requestId" to requestId,
80
84
  "method" to request.method().name,
81
85
  "path" to request.url(),
82
86
  "body" to request.content(),
@@ -85,13 +89,13 @@ class ExpoHttpServerModule : Module() {
85
89
  "cookiesJson" to JSONObject(cookies).toString(),
86
90
  ))
87
91
  val deadline = System.currentTimeMillis() + 30_000
88
- while (!responses.containsKey(uuid) && System.currentTimeMillis() < deadline) {
92
+ while (!responses.containsKey(requestId) && System.currentTimeMillis() < deadline) {
89
93
  Thread.sleep(10)
90
94
  }
91
- val res = responses.remove(uuid) ?: return response
95
+ val res = responses.remove(requestId) ?: return response
92
96
  response.setBodyText(res.body)
93
97
  response.setStatus(res.statusCode)
94
- response.addHeader("Content-Length", "" + res.body.length)
98
+ response.addHeader("Content-Length", "" + res.body.toByteArray(Charsets.UTF_8).size)
95
99
  response.addHeader("Content-Type", res.contentType)
96
100
  for ((key, value) in res.headers) {
97
101
  response.addHeader(key, value)
@@ -99,10 +103,15 @@ class ExpoHttpServerModule : Module() {
99
103
  return response
100
104
  }
101
105
 
106
+ private fun buildServer(): Server = AndroidServer.Builder {
107
+ port { configuredPort }
108
+ if (configuredLoopbackOnly) {
109
+ address { "127.0.0.1" }
110
+ }
111
+ }.build()
112
+
102
113
  private fun buildServerWithRoutes(): Server {
103
- var s: Server = AndroidServer.Builder {
104
- port { configuredPort }
105
- }.build()
114
+ var s: Server = buildServer()
106
115
  for (route in routeConfigs) {
107
116
  s = s.request(HttpMethod.getMethod(route.method), route.path) { request: Request, response: Response ->
108
117
  handleRequest(route.uuid, request, response)
@@ -126,15 +135,14 @@ class ExpoHttpServerModule : Module() {
126
135
  statusUpdateListeners.remove(onBindFailure)
127
136
  }
128
137
 
129
- Function("setup") { port: Int ->
138
+ Function("setup") { port: Int, loopbackOnly: Boolean ->
130
139
  configuredPort = port
140
+ configuredLoopbackOnly = loopbackOnly
131
141
  server?.close()
132
142
  started = false
133
143
  hasBoundSocket = false
134
144
  routeConfigs.clear()
135
- server = AndroidServer.Builder {
136
- port { configuredPort }
137
- }.build()
145
+ server = buildServer()
138
146
  }
139
147
 
140
148
  Function("route") { path: String, method: String, uuid: String ->
package/build/index.d.ts CHANGED
@@ -9,6 +9,7 @@ export interface StatusEvent {
9
9
  }
10
10
  export interface RequestEvent {
11
11
  uuid: string;
12
+ requestId: string;
12
13
  method: string;
13
14
  path: string;
14
15
  body: string;
@@ -31,6 +32,9 @@ export interface Callback {
31
32
  }
32
33
  export declare const start: () => void;
33
34
  export declare const route: (path: string, method: HttpMethod, callback: (request: RequestEvent) => Promise<Response>) => void;
34
- export declare const setup: (port: number, onStatusUpdate?: ((event: StatusEvent) => void) | undefined) => void;
35
+ export interface SetupOptions {
36
+ loopbackOnly?: boolean;
37
+ }
38
+ export declare const setup: (port: number, onStatusUpdate?: ((event: StatusEvent) => void) | undefined, options?: SetupOptions) => void;
35
39
  export declare const stop: () => void;
36
40
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;AACvE;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAE5E,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,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,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CACxD;AAED,eAAO,MAAM,KAAK,YA4BjB,CAAC;AAEF,eAAO,MAAM,KAAK,SACV,MAAM,UACJ,UAAU,sBACE,YAAY,KAAK,QAAQ,QAAQ,CAAC,SAUvD,CAAC;AAEF,eAAO,MAAM,KAAK,SACV,MAAM,4BACa,WAAW,KAAK,IAAI,sBAU9C,CAAC;AAEF,eAAO,MAAM,IAAI,YAIhB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;AACvE;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAE5E,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,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,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CACxD;AAED,eAAO,MAAM,KAAK,YAqCjB,CAAC;AAEF,eAAO,MAAM,KAAK,SACV,MAAM,UACJ,UAAU,sBACE,YAAY,KAAK,QAAQ,QAAQ,CAAC,SAUvD,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,KAAK,SACV,MAAM,4BACa,WAAW,KAAK,IAAI,yBACpC,YAAY,SAUtB,CAAC;AAEF,eAAO,MAAM,IAAI,YAIhB,CAAC"}
package/build/index.js CHANGED
@@ -9,13 +9,15 @@ export const start = () => {
9
9
  try {
10
10
  const responseHandler = requestCallbacks.find((c) => c.uuid === event.uuid);
11
11
  if (!responseHandler) {
12
- ExpoHttpServerModule.respond(event.uuid, 404, "Not Found", "application/json", {}, JSON.stringify({ error: "Handler not found" }));
12
+ ExpoHttpServerModule.respond(event.requestId, 404, "Not Found", "application/json", {}, JSON.stringify({ error: "Handler not found" }));
13
13
  return;
14
14
  }
15
15
  const response = await responseHandler.callback(event);
16
- ExpoHttpServerModule.respond(event.uuid, response.statusCode || 200, response.statusDescription || "OK", response.contentType || "application/json", response.headers || {}, response.body || "{}");
16
+ ExpoHttpServerModule.respond(event.requestId, response.statusCode || 200, response.statusDescription || "OK", response.contentType || "application/json", response.headers || {}, response.body || "{}");
17
+ }
18
+ catch {
19
+ ExpoHttpServerModule.respond(event.requestId, 500, "Internal Server Error", "application/json", {}, JSON.stringify({ error: "Handler failed" }));
17
20
  }
18
- catch { }
19
21
  });
20
22
  ExpoHttpServerModule.start();
21
23
  };
@@ -29,7 +31,7 @@ export const route = (path, method, callback) => {
29
31
  });
30
32
  ExpoHttpServerModule.route(path, method, uuid);
31
33
  };
32
- export const setup = (port, onStatusUpdate) => {
34
+ export const setup = (port, onStatusUpdate, options = {}) => {
33
35
  if (onStatusUpdate) {
34
36
  emitter.addListener("onStatusUpdate", async (event) => {
35
37
  try {
@@ -38,7 +40,7 @@ export const setup = (port, onStatusUpdate) => {
38
40
  catch { }
39
41
  });
40
42
  }
41
- ExpoHttpServerModule.setup(port);
43
+ ExpoHttpServerModule.setup(port, options.loopbackOnly ?? false);
42
44
  };
43
45
  export const stop = () => {
44
46
  requestSubscription?.remove();
@@ -1 +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;AACxC,IAAI,mBAAmB,GAAkD,IAAI,CAAC;AAsC9E,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,EAAE;IACxB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,mBAAmB,GAAG,OAAO,CAAC,WAAW,CAAe,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QACnF,IAAI,CAAC;YACH,MAAM,eAAe,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC;YAC5E,IAAI,CAAC,eAAe,EAAE,CAAC;gBACrB,oBAAoB,CAAC,OAAO,CAC1B,KAAK,CAAC,IAAI,EACV,GAAG,EACH,WAAW,EACX,kBAAkB,EAClB,EAAE,EACF,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAC/C,CAAC;gBACF,OAAO;YACT,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YACvD,oBAAoB,CAAC,OAAO,CAC1B,KAAK,CAAC,IAAI,EACV,QAAQ,CAAC,UAAU,IAAI,GAAG,EAC1B,QAAQ,CAAC,iBAAiB,IAAI,IAAI,EAClC,QAAQ,CAAC,WAAW,IAAI,kBAAkB,EAC1C,QAAQ,CAAC,OAAO,IAAI,EAAE,EACtB,QAAQ,CAAC,IAAI,IAAI,IAAI,CACtB,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC,CAAC,CAAC;IACH,oBAAoB,CAAC,KAAK,EAAE,CAAC;AAC/B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,KAAK,GAAG,CACnB,IAAY,EACZ,MAAkB,EAClB,QAAsD,EACtD,EAAE;IACF,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACjD,gBAAgB,CAAC,IAAI,CAAC;QACpB,MAAM;QACN,IAAI;QACJ,IAAI;QACJ,QAAQ;KACT,CAAC,CAAC;IACH,oBAAoB,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;AACjD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,KAAK,GAAG,CACnB,IAAY,EACZ,cAA6C,EAC7C,EAAE;IACF,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO,CAAC,WAAW,CAAc,gBAAgB,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YACjE,IAAI,CAAC;gBACH,MAAM,cAAc,CAAC,KAAK,CAAC,CAAC;YAC9B,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC;IACD,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,IAAI,GAAG,GAAG,EAAE;IACvB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,mBAAmB,GAAG,IAAI,CAAC;IAC3B,oBAAoB,CAAC,IAAI,EAAE,CAAC;AAC9B,CAAC,CAAC","sourcesContent":["import { EventEmitter } from \"expo-modules-core\";\n\nimport ExpoHttpServerModule from \"./ExpoHttpServerModule\";\n\nconst emitter = new EventEmitter(ExpoHttpServerModule);\nconst requestCallbacks: Callback[] = [];\nlet requestSubscription: ReturnType<typeof emitter.addListener> | null = null;\n\nexport type HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"OPTIONS\";\n/**\n * PAUSED AND RESUMED are iOS only\n */\nexport type Status = \"STARTED\" | \"PAUSED\" | \"RESUMED\" | \"STOPPED\" | \"ERROR\";\n\nexport interface StatusEvent {\n status: Status;\n message: string;\n}\n\nexport interface RequestEvent {\n uuid: string;\n method: string;\n path: string;\n body: string;\n headersJson: string;\n paramsJson: string;\n cookiesJson: string;\n}\n\nexport interface Response {\n statusCode?: number;\n statusDescription?: string;\n contentType?: string;\n headers?: Record<string, string>;\n body?: string;\n}\n\nexport interface Callback {\n method: string;\n path: string;\n uuid: string;\n callback: (request: RequestEvent) => Promise<Response>;\n}\n\nexport const start = () => {\n requestSubscription?.remove();\n requestSubscription = emitter.addListener<RequestEvent>(\"onRequest\", async (event) => {\n try {\n const responseHandler = requestCallbacks.find((c) => c.uuid === event.uuid);\n if (!responseHandler) {\n ExpoHttpServerModule.respond(\n event.uuid,\n 404,\n \"Not Found\",\n \"application/json\",\n {},\n JSON.stringify({ error: \"Handler not found\" }),\n );\n return;\n }\n const response = await responseHandler.callback(event);\n ExpoHttpServerModule.respond(\n event.uuid,\n response.statusCode || 200,\n response.statusDescription || \"OK\",\n response.contentType || \"application/json\",\n response.headers || {},\n response.body || \"{}\",\n );\n } catch {}\n });\n ExpoHttpServerModule.start();\n};\n\nexport const route = (\n path: string,\n method: HttpMethod,\n callback: (request: RequestEvent) => Promise<Response>,\n) => {\n const uuid = Math.random().toString(16).slice(2);\n requestCallbacks.push({\n method,\n path,\n uuid,\n callback,\n });\n ExpoHttpServerModule.route(path, method, uuid);\n};\n\nexport const setup = (\n port: number,\n onStatusUpdate?: (event: StatusEvent) => void,\n) => {\n if (onStatusUpdate) {\n emitter.addListener<StatusEvent>(\"onStatusUpdate\", async (event) => {\n try {\n await onStatusUpdate(event);\n } catch {}\n });\n }\n ExpoHttpServerModule.setup(port);\n};\n\nexport const stop = () => {\n requestSubscription?.remove();\n requestSubscription = null;\n ExpoHttpServerModule.stop();\n};\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from \"expo-modules-core\";\n\nimport ExpoHttpServerModule from \"./ExpoHttpServerModule\";\n\nconst emitter = new EventEmitter(ExpoHttpServerModule);\nconst requestCallbacks: Callback[] = [];\nlet requestSubscription: ReturnType<typeof emitter.addListener> | null = null;\n\nexport type HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"OPTIONS\";\n/**\n * PAUSED AND RESUMED are iOS only\n */\nexport type Status = \"STARTED\" | \"PAUSED\" | \"RESUMED\" | \"STOPPED\" | \"ERROR\";\n\nexport interface StatusEvent {\n status: Status;\n message: string;\n}\n\nexport interface RequestEvent {\n uuid: string;\n requestId: string;\n method: string;\n path: string;\n body: string;\n headersJson: string;\n paramsJson: string;\n cookiesJson: string;\n}\n\nexport interface Response {\n statusCode?: number;\n statusDescription?: string;\n contentType?: string;\n headers?: Record<string, string>;\n body?: string;\n}\n\nexport interface Callback {\n method: string;\n path: string;\n uuid: string;\n callback: (request: RequestEvent) => Promise<Response>;\n}\n\nexport const start = () => {\n requestSubscription?.remove();\n requestSubscription = emitter.addListener<RequestEvent>(\"onRequest\", async (event) => {\n try {\n const responseHandler = requestCallbacks.find((c) => c.uuid === event.uuid);\n if (!responseHandler) {\n ExpoHttpServerModule.respond(\n event.requestId,\n 404,\n \"Not Found\",\n \"application/json\",\n {},\n JSON.stringify({ error: \"Handler not found\" }),\n );\n return;\n }\n const response = await responseHandler.callback(event);\n ExpoHttpServerModule.respond(\n event.requestId,\n response.statusCode || 200,\n response.statusDescription || \"OK\",\n response.contentType || \"application/json\",\n response.headers || {},\n response.body || \"{}\",\n );\n } catch {\n ExpoHttpServerModule.respond(\n event.requestId,\n 500,\n \"Internal Server Error\",\n \"application/json\",\n {},\n JSON.stringify({ error: \"Handler failed\" }),\n );\n }\n });\n ExpoHttpServerModule.start();\n};\n\nexport const route = (\n path: string,\n method: HttpMethod,\n callback: (request: RequestEvent) => Promise<Response>,\n) => {\n const uuid = Math.random().toString(16).slice(2);\n requestCallbacks.push({\n method,\n path,\n uuid,\n callback,\n });\n ExpoHttpServerModule.route(path, method, uuid);\n};\n\nexport interface SetupOptions {\n loopbackOnly?: boolean;\n}\n\nexport const setup = (\n port: number,\n onStatusUpdate?: (event: StatusEvent) => void,\n options: SetupOptions = {},\n) => {\n if (onStatusUpdate) {\n emitter.addListener<StatusEvent>(\"onStatusUpdate\", async (event) => {\n try {\n await onStatusUpdate(event);\n } catch {}\n });\n }\n ExpoHttpServerModule.setup(port, options.loopbackOnly ?? false);\n};\n\nexport const stop = () => {\n requestSubscription?.remove();\n requestSubscription = null;\n ExpoHttpServerModule.stop();\n};\n"]}
@@ -4,7 +4,10 @@ import Criollo
4
4
  public class ExpoHttpServerModule: Module {
5
5
  private let server = CRHTTPServer()
6
6
  private var port: Int?
7
+ private var loopbackOnly = false
7
8
  private var stopped = false
9
+ private var isListening = false
10
+ private var foregroundObserver: NSObjectProtocol?
8
11
  private var responses = [String: CRResponse]()
9
12
  private var bgTaskIdentifier = UIBackgroundTaskIdentifier.invalid
10
13
 
@@ -20,13 +23,17 @@ public class ExpoHttpServerModule: Module {
20
23
  Function("stop", stopHandler)
21
24
  }
22
25
 
23
- private func setupHandler(port: Int) {
26
+ private func setupHandler(port: Int, loopbackOnly: Bool) {
24
27
  self.port = port;
28
+ self.loopbackOnly = loopbackOnly;
25
29
  }
26
30
 
27
31
  private func startHandler() {
28
- NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [unowned self] notification in
29
- if (!self.stopped) {
32
+ if (foregroundObserver == nil) {
33
+ foregroundObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [weak self] _ in
34
+ guard let self = self, !self.stopped, !self.isListening else {
35
+ return
36
+ }
30
37
  self.startServer(status: "RESUMED", message: "Server resumed")
31
38
  }
32
39
  }
@@ -41,9 +48,11 @@ public class ExpoHttpServerModule: Module {
41
48
  if let body = req.body, let bodyData = try? JSONSerialization.data(withJSONObject: body) {
42
49
  bodyString = String(data: bodyData, encoding: .utf8) ?? "{}"
43
50
  }
44
- self.responses[uuid] = res
51
+ let requestId = UUID().uuidString
52
+ self.responses[requestId] = res
45
53
  self.sendEvent("onRequest", [
46
54
  "uuid": uuid,
55
+ "requestId": requestId,
47
56
  "method": req.method.toString(),
48
57
  "path": path,
49
58
  "body": bodyString,
@@ -55,22 +64,22 @@ public class ExpoHttpServerModule: Module {
55
64
  }, recursive: false, method: CRHTTPMethod.fromString(method))
56
65
  }
57
66
 
58
- private func respondHandler(udid: String,
67
+ private func respondHandler(requestId: String,
59
68
  statusCode: Int,
60
69
  statusDescription: String,
61
70
  contentType: String,
62
71
  headers: [String: String],
63
72
  body: String) {
64
73
  DispatchQueue.main.async {
65
- if let response = self.responses[udid] {
74
+ if let response = self.responses[requestId] {
66
75
  response.setStatusCode(UInt(statusCode), description: statusDescription)
67
76
  response.setValue(contentType, forHTTPHeaderField: "Content-type")
68
- response.setValue("\(body.count)", forHTTPHeaderField: "Content-Length")
77
+ response.setValue("\(body.utf8.count)", forHTTPHeaderField: "Content-Length")
69
78
  for (key, value) in headers {
70
79
  response.setValue(value, forHTTPHeaderField: key)
71
80
  }
72
81
  response.send(body);
73
- self.responses[udid] = nil;
82
+ self.responses[requestId] = nil;
74
83
  }
75
84
  }
76
85
 
@@ -78,6 +87,10 @@ public class ExpoHttpServerModule: Module {
78
87
 
79
88
  private func stopHandler() {
80
89
  stopped = true;
90
+ if let observer = foregroundObserver {
91
+ NotificationCenter.default.removeObserver(observer)
92
+ foregroundObserver = nil
93
+ }
81
94
  stopServer(status: "STOPPED", message: "Server stopped")
82
95
  }
83
96
 
@@ -85,13 +98,14 @@ public class ExpoHttpServerModule: Module {
85
98
  stopServer()
86
99
  if let port = port {
87
100
  var error: NSError?
88
- server.startListening(&error, portNumber: UInt(port))
101
+ server.startListening(&error, portNumber: UInt(port), interface: loopbackOnly ? "localhost" : nil)
89
102
  if (error != nil) {
90
103
  sendEvent("onStatusUpdate", [
91
104
  "status": "ERROR",
92
105
  "message": error?.localizedDescription ?? "Unknown error starting server"
93
106
  ])
94
107
  } else {
108
+ isListening = true
95
109
  beginBackgroundTask()
96
110
  sendEvent("onStatusUpdate", [
97
111
  "status": status,
@@ -108,6 +122,7 @@ public class ExpoHttpServerModule: Module {
108
122
 
109
123
  private func stopServer(status: String? = nil, message: String? = nil) {
110
124
  server.stopListening()
125
+ isListening = false
111
126
  endBackgroundTask()
112
127
  if let status = status, let message = message {
113
128
  sendEvent("onStatusUpdate", [
@@ -151,6 +166,8 @@ extension CRHTTPMethod {
151
166
  return "PUT"
152
167
  case .delete:
153
168
  return "DELETE"
169
+ case .options:
170
+ return "OPTIONS"
154
171
  default:
155
172
  return "GET"
156
173
  }
@@ -165,6 +182,8 @@ extension CRHTTPMethod {
165
182
  httpMethod = .put
166
183
  case "DELETE":
167
184
  httpMethod = .delete
185
+ case "OPTIONS":
186
+ httpMethod = .options
168
187
  default:
169
188
  httpMethod = .get
170
189
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-http-server",
3
- "version": "0.1.14-beta.4",
3
+ "version": "0.1.14-beta.5",
4
4
  "description": "A simple HTTP server expo module (iOS/Android)",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
package/src/index.ts CHANGED
@@ -19,6 +19,7 @@ export interface StatusEvent {
19
19
 
20
20
  export interface RequestEvent {
21
21
  uuid: string;
22
+ requestId: string;
22
23
  method: string;
23
24
  path: string;
24
25
  body: string;
@@ -49,7 +50,7 @@ export const start = () => {
49
50
  const responseHandler = requestCallbacks.find((c) => c.uuid === event.uuid);
50
51
  if (!responseHandler) {
51
52
  ExpoHttpServerModule.respond(
52
- event.uuid,
53
+ event.requestId,
53
54
  404,
54
55
  "Not Found",
55
56
  "application/json",
@@ -60,14 +61,23 @@ export const start = () => {
60
61
  }
61
62
  const response = await responseHandler.callback(event);
62
63
  ExpoHttpServerModule.respond(
63
- event.uuid,
64
+ event.requestId,
64
65
  response.statusCode || 200,
65
66
  response.statusDescription || "OK",
66
67
  response.contentType || "application/json",
67
68
  response.headers || {},
68
69
  response.body || "{}",
69
70
  );
70
- } catch {}
71
+ } catch {
72
+ ExpoHttpServerModule.respond(
73
+ event.requestId,
74
+ 500,
75
+ "Internal Server Error",
76
+ "application/json",
77
+ {},
78
+ JSON.stringify({ error: "Handler failed" }),
79
+ );
80
+ }
71
81
  });
72
82
  ExpoHttpServerModule.start();
73
83
  };
@@ -87,9 +97,14 @@ export const route = (
87
97
  ExpoHttpServerModule.route(path, method, uuid);
88
98
  };
89
99
 
100
+ export interface SetupOptions {
101
+ loopbackOnly?: boolean;
102
+ }
103
+
90
104
  export const setup = (
91
105
  port: number,
92
106
  onStatusUpdate?: (event: StatusEvent) => void,
107
+ options: SetupOptions = {},
93
108
  ) => {
94
109
  if (onStatusUpdate) {
95
110
  emitter.addListener<StatusEvent>("onStatusUpdate", async (event) => {
@@ -98,7 +113,7 @@ export const setup = (
98
113
  } catch {}
99
114
  });
100
115
  }
101
- ExpoHttpServerModule.setup(port);
116
+ ExpoHttpServerModule.setup(port, options.loopbackOnly ?? false);
102
117
  };
103
118
 
104
119
  export const stop = () => {