🚀 Socket Launch Week Day 4:Socket MCP Adds Org Alerts, Threat Feed Review, and Package Inspection.Learn more
Sign In

@cantoo/capacitor-http-server

Package Overview
Dependencies
Maintainers
5
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cantoo/capacitor-http-server

Capacitor plugin exposing a local HTTP server on iOS and Android with routing delegated to JavaScript.

latest
Source
npmnpm
Version
1.0.6
Version published
Maintainers
5
Created
Source

@cantoo-scribe/capacitor-http-server

A generic Capacitor plugin that exposes a local HTTP server on iOS and Android with routing delegated to JavaScript.

The plugin has no business logic. It only handles the transport layer: it starts a listener, hands every incoming request to JavaScript via a request event, and writes back whatever JavaScript returns through respond(). This keeps your app code — authentication, routing, payloads, CORS policy — entirely in TypeScript, where it can be tested, updated from a hot-reload bundle, and shared across platforms.

Install

npm install @cantoo-scribe/capacitor-http-server
npx cap sync

Android configuration

The plugin ships all required permissions in its manifest:

  • INTERNET, ACCESS_NETWORK_STATE, ACCESS_WIFI_STATE
  • FOREGROUND_SERVICE and FOREGROUND_SERVICE_DATA_SYNC (API 34+)
  • POST_NOTIFICATIONS (API 33+)

The server runs inside a foreground service with type dataSync, so it survives the screen being locked. On Android 13+ the system requires a visible notification, hence POST_NOTIFICATIONS. If the user declines the permission, the server still starts but the system is free to kill the process at any moment. The plugin requests the permission automatically on the first start() call.

You must supply android.notificationTitle and android.notificationText when starting the server:

await HttpServer.start({
  android: {
    notificationTitle: 'My App',
    notificationText: 'Local server is running.',
    smallIconResourceName: 'ic_notification', // res/drawable/ic_notification.png
    channelId: 'my_app_http',
    channelName: 'Local HTTP server',
  },
});

iOS configuration

Add the following keys to your app's Info.plist:

<key>NSLocalNetworkUsageDescription</key>
<string>Used to expose a local server for sharing files across devices on this network.</string>

<!-- Optional. Only needed if you broadcast your server over Bonjour. -->
<key>NSBonjourServices</key>
<array>
  <string>_myservice._tcp</string>
</array>

Without NSLocalNetworkUsageDescription, iOS 14+ silently drops every incoming connection from other devices.

Note: iOS does not allow long-running HTTP servers in background. The plugin requests a short background task on app backgrounding, then emits a server-error event with fatal: true when the window expires. Restart the server on UIApplication.didBecomeActive.

Usage

Echo server

A complete, 20-line echo server that returns exactly what it received:

import { HttpServer } from '@cantoo-scribe/capacitor-http-server';

await HttpServer.addListener('request', async (req) => {
  await HttpServer.respond({
    requestId: req.requestId,
    status: 200,
    headers: { 'content-type': req.headers['content-type'] ?? 'text/plain' },
    ...(req.bodyText !== undefined && { bodyText: req.bodyText }),
    ...(req.bodyBase64 !== undefined && { bodyBase64: req.bodyBase64 }),
    ...(req.bodyFilePath !== undefined && { bodyFilePath: req.bodyFilePath }),
  });
});

const { url } = await HttpServer.start({
  android: {
    notificationTitle: 'Echo server',
    notificationText: 'Listening for requests',
  },
});
console.log('Listening at', url);

Test it from a laptop on the same network:

curl http://<device-ip>:<port>/ -d 'hello'
# hello

Handling errors

HttpServer.addListener('server-error', ({ message, fatal }) => {
  console.warn('Server error:', message, 'fatal:', fatal);
  if (fatal) {
    // On iOS the server dies in background; restart on foreground.
  }
});

Streaming a large file out

HttpServer.addListener('request', async (req) => {
  if (req.path === '/big.bin') {
    await HttpServer.respond({
      requestId: req.requestId,
      status: 200,
      headers: { 'content-type': 'application/octet-stream' },
      bodyFilePath: '/absolute/path/to/file.bin', // streamed, no RAM spike
    });
  } else {
    await HttpServer.respond({ requestId: req.requestId, status: 404 });
  }
});

Receiving a large upload

Bodies above fileBodyThresholdBytes (default 1 MB) are streamed to a temp file managed by the plugin. JavaScript receives the absolute path via bodyFilePath and is free to move, hash, or re-stream it. The plugin deletes the temp file automatically once respond() returns.

API

start(...)

start(options?: StartOptions | undefined) => any

Start the local HTTP server. Picks a free port if port is omitted. On Android, starts a foreground service so the server survives screen lock. Throws if the server is already running with a different configuration, or if required permissions are missing.

ParamType
optionsStartOptions

Returns: any

stop()

stop() => any

Stop the server and release all resources (port, threads, temp files, service). Safe to call when already stopped.

Returns: any

respond(...)

respond(response: HttpResponse) => any

Reply to a request previously received via the request event. The plugin looks up requestId, writes the response, and cleans up any temporary body file. Calling respond twice for the same requestId is a no-op on the second call. Unanswered requests time out (504) after 60 s.

ParamType
responseHttpResponse

Returns: any

addListener('request', ...)

addListener(eventName: 'request', listener: (event: HttpRequestEvent) => void) => Promise<PluginListenerHandle> & PluginListenerHandle

Subscribe to incoming HTTP requests. Every accepted request fires exactly one request event; JS is expected to call respond exactly once per event.

ParamType
eventName'request'
listener(event: HttpRequestEvent) => void

Returns: any

addListener('server-error', ...)

addListener(eventName: 'server-error', listener: (event: ServerErrorEvent) => void) => Promise<PluginListenerHandle> & PluginListenerHandle

Subscribe to server-level errors that are not tied to a specific request (e.g. the underlying socket died, port was stolen, permission revoked). The server is considered dead when this fires with fatal: true.

ParamType
eventName'server-error'
listener(event: ServerErrorEvent) => void

Returns: any

removeAllListeners()

removeAllListeners() => any

Remove every listener registered on this plugin.

Returns: any

Interfaces

StartOptions

PropTypeDescription
portnumberIf omitted or 0, the OS picks a free port in the dynamic range.
maxBodyBytesnumberMax request body size in bytes. Bodies above this return 413. Default: 50 * 1024 * 1024 (50 MB).
fileBodyThresholdBytesnumberThreshold above which the plugin stores the body in a temp file and exposes it via bodyFilePath instead of bodyBase64. Default: 1 * 1024 * 1024 (1 MB).
androidStartOptionsAndroidAndroid only. Title / text / small icon used by the foreground service notification (mandatory on Android 13+). Ignored on iOS.

StartOptionsAndroid

PropTypeDescription
notificationTitlestringForeground-service notification title. Required on Android 13+.
notificationTextstringForeground-service notification body text.
smallIconResourceNamestringAndroid drawable resource name, e.g. "ic_notification".
channelIdstringNotification channel ID. Plugin creates the channel if missing.
channelNamestringNotification channel display name.

StartResult

PropTypeDescription
portnumberChosen port (same as options.port when provided).
urlstringFull URL, e.g. "http://192.168.1.42:49281". Empty string when no LAN-reachable interface was found (e.g. cellular only). The server is still listening on the port, but cannot be reached by a peer LAN device. A server-error event with fatal: false is emitted in that case so consumers can warn the user.
localIpstringPrimary LAN IPv4. Empty string when no LAN-reachable interface was found. See url.

HttpResponse

PropTypeDescription
dataanyAdditional data received with the Http response.
statusnumberThe status code received from the Http response.
headersHttpHeadersThe headers received from the Http response.
urlstringThe response URL received from the Http response.

HttpHeaders

HttpRequestEvent

PropTypeDescription
requestIdstringOpaque ID. Pass back unchanged in respond.
methodHttpMethod
pathstringDecoded pathname, always starts with "/". No querystring.
queryRecord<string, string>Parsed querystring. Repeated keys keep the last value.
headersRecord<string, string>Lower-cased header names.
clientIpstringRemote peer IP if available (useful for logging only).
bodyTextstringExactly one of the three body fields is set (undefined when the request has no body). The plugin picks the representation based on content type and size: - UTF-8 body with a text-like content type (text/*, application/json, application/x-www-form-urlencoded) below threshold -> bodyText - Any other body below threshold -> bodyBase64 - Body above threshold -> bodyFilePath Temp files referenced by bodyFilePath are deleted automatically once respond is called for the same requestId (or on timeout).
bodyBase64string
bodyFilePathstring

PluginListenerHandle

PropType
remove() => any

ServerErrorEvent

PropTypeDescription
messagestring
fatalbooleanTrue when the server is no longer listening and must be restarted.

Type Aliases

HttpMethod

HTTP methods that the plugin forwards to JavaScript. Any value outside this list produced by a client is still forwarded as-is (upper-cased) but typed as HttpMethod for convenience of the common cases.

'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD'

Limitations

  • No HTTPS. The plugin listens on plain HTTP. Use it over a trusted LAN or add a reverse proxy if you need TLS.
  • No chunked transfer encoding on the request side. Clients must send Content-Length; otherwise the server replies with 501.
  • No WebSocket support (tracked separately; out of scope here).
  • iOS background. The server is suspended when the app leaves the foreground. A server-error event is emitted with fatal: true.

License

MIT © Cantoo Scribe

Keywords

capacitor

FAQs

Package last updated on 30 Apr 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts