🌉 A lightweight JavaScript bridge for bi-directional communication between web apps and native apps (iOS/Android) via WebView.
Supports:
-
call()
– Call native methods (Promise-based) -
on()
– Listen to native events (e.g., GPS, push) - Cross-platform: iOS & Android
- Zero dependencies
- Works with React, Vue, vanilla JS
npm install native-bridge-js
import nativeBridge from 'native-bridge-js';
// 1. Call native method
nativeBridge.call('requestLocation')
.then(location => console.log('📍', location))
.catch(err => console.error('❌', err));
// 2. Listen to real-time events
nativeBridge.on('batteryUpdate', (data) => {
console.log(`🔋 ${data.level}% charged`);
});
// 3. Send data to native
nativeBridge.send('logEvent', { name: 'profile_view' });
// 4. Register a custom command (Command Pattern)
nativeBridge.registerCommand('sayHello', (name) => `Hello, ${name}!`);
// 5. Execute a registered command
const greeting = nativeBridge.executeCommand('sayHello', 'World');
console.log(greeting); // "Hello, World!"
// 6. Unregister a command
nativeBridge.unregisterCommand('sayHello');
The command pattern allows you to register, execute, and unregister custom commands on the bridge instance:
-
registerCommand(commandName, handler)
– Register a command handler function. -
executeCommand(commandName, ...args)
– Execute a registered command with arguments. -
unregisterCommand(commandName)
– Remove a command handler.
This is useful for modular, decoupled logic and advanced integrations.
- Setup WebView
import WebKit
class WebViewController: UIViewController, WKScriptMessageHandler {
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Configure WebView
let config = WKWebViewConfiguration()
config.userContentController.add(self, name: "NativeBridge")
webView = WKWebView(frame: view.bounds, configuration: config)
view.addSubview(webView)
// Load your web app
if let url = Bundle.main.url(forResource: "index", withExtension: "html") {
webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())
}
}
}
- Handle Messages from JS
func userContentController(_ controller: WKUserContentController, didReceive message: WKScriptMessage) {
guard let body = message.body as? [String: Any],
let method = body["method"] as? String,
let callbackId = body["callbackId"] as? Int else { return }
switch method {
case "requestLocation":
let response = ["callbackId": callbackId, "data": ["lat": 37.7749, "lng": -122.4194]]
executeJs("window.NativeBridge(\(toJson(response)!))")
case "share":
if let text = (body["data"] as? [String: Any])?["text"] as? String {
showShareSheet(text)
}
default:
let error = ["callbackId": callbackId, "error": "Unknown method"]
executeJs("window.NativeBridge(\(toJson(error)!))")
}
}
- Helper Functions
private func executeJs(_ js: String) {
webView.evaluateJavaScript(js) { _, error in
if let error = error {
print("JS Error: $error)")
}
}
}
private func toJson(_ dict: [String: Any]) -> String? {
guard let data = try? JSONSerialization.data(withJSONObject: dict),
let str = String( data, encoding: .utf8) else { return nil }
return str
}
private func showShareSheet(_ text: String) {
let activity = UIActivityViewController(activityItems: [text], applicationActivities: nil)
present(activity, animated: true)
}
- Push Events to Web
func sendBatteryUpdate(level: Int) {
let event = ["method": "batteryLevel", "data": ["level": level]]
executeJs("window.NativeBridge(\(toJson(event)!))")
}
- Then in JS:
nativeBridge.on('batteryLevel', (data) => {
console.log(`🔋 ${data.level}%`);
});
- Setup WebView
class WebActivity : AppCompatActivity() {
private lateinit var webView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
webView = WebView(this)
setContentView(webView)
webView.settings.javaScriptEnabled = true
webView.addJavascriptInterface(NativeBridgeInterface(), "AndroidBridge")
webView.loadUrl("file:///android_asset/index.html")
}
}
- Handle JS Messages
@Suppress("unused")
inner class NativeBridgeInterface {
@JavascriptInterface
fun postMessage(jsonString: String) {
try {
val json = JSONObject(jsonString)
val method = json.getString("method")
val callbackId = json.getInt("callbackId")
when (method) {
"requestLocation" -> {
val data = JSONObject().apply {
put("lat", 37.7749)
put("lng", -122.4194)
}
val response = JSONObject().apply {
put("callbackId", callbackId)
put("data", data)
}
runJs("window.NativeBridge($response)")
}
"share" -> {
val text = json.getJSONObject("data").getString("text")
shareText(text)
}
else -> {
val error = JSONObject().apply {
put("callbackId", callbackId)
put("error", "Unknown method: $method")
}
runJs("window.NativeBridge($error)")
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
- Helper Functions
private fun runJs(js: String) {
webView.evaluateJavascript(js, null)
}
private fun shareText(text: String) {
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, text)
}
startActivity(Intent.createChooser(intent, "Share"))
}
- Push Events to Web
private fun sendLocationUpdate(lat: Double, lng: Double) {
val js = """
window.NativeBridge({
"method": "locationUpdate",
"data": { "lat": $lat, "lng": $lng }
})
""".trimIndent()
webView.evaluateJavascript(js, null)
}
- Then in JS:
nativeBridge.on('locationUpdate', (loc) => {
console.log('📍', loc.lat, loc.lng);
});