v0.21

native bridge and plugins

introduction

the native bridge provides bidirectional communication between your javascript code and the native runtime. all device apis are exposed through a unified interface that works identically across ios, android, and desktop platforms.

the native bridge

bridge architecture

the bridge operates on a request response model. your javascript code sends method calls to the native layer, which executes the operation and returns a result asynchronously.

all communication uses json serialization. complex objects are automatically marshalled across the boundary. promises provide natural async handling on the javascript side.

calling native methods

invoke native functionality using the global bridge object:

1const result = await window.dstrnBridge.call('storage.get', { key: 'username' });
2console.log(result.value);

the call method accepts a method name and optional data payload. it returns a promise that resolves with the native response or rejects on error.

method names use dot notation to namespace functionality. all built in apis use the pattern category.action.

receiving native events

the native runtime can push events to your javascript code. listen for events using the bridge event emitter:

1window.dstrnBridge.on('app:background', () => {
2 console.log('app moved to background');
3});

lifecycle events fire automatically when the app state changes. custom plugins can emit their own events using the same mechanism.

available apis

storage

persistent key value storage backed by native secure storage apis:

1await window.dstrnBridge.call('storage.set', { key: 'token', value: 'abc123' });
2const result = await window.dstrnBridge.call('storage.get', { key: 'token' });
3const keys = await window.dstrnBridge.call('storage.keys');
4await window.dstrnBridge.call('storage.remove', { key: 'token' });
5await window.dstrnBridge.call('storage.clear');

clipboard

read and write the system clipboard:

1await window.dstrnBridge.call('clipboard.write', { text: 'hello world' });
2const result = await window.dstrnBridge.call('clipboard.read');
3console.log(result.text);

haptics

trigger haptic feedback on supported devices:

1await window.dstrnBridge.call('haptic.impact', { style: 'medium' });
2await window.dstrnBridge.call('haptic.notification', { type: 'success' });
3await window.dstrnBridge.call('haptic.selection');

notifications

request permissions and display local notifications:

1const result = await window.dstrnBridge.call('notification.permission');
2if (result.status === 'granted') {
3 await window.dstrnBridge.call('notification.send', {
4 title: 'update available',
5 body: 'a new version is ready',
6 });
7}

lifecycle events

the runtime emits events when the application state changes:

1window.dstrnBridge.on('app:foreground', () => {
2 console.log('app resumed');
3});
4
5window.dstrnBridge.on('app:background', () => {
6 console.log('app paused');
7});
8
9window.dstrnBridge.on('app:terminate', () => {
10 console.log('app closing');
11});

creating plugins

plugin structure

plugins extend the bridge with custom native functionality. each plugin consists of three platform specific implementations that expose identical javascript apis.

a plugin named camera would provide methods like camera.capture and camera.permissions that work consistently across all platforms despite different underlying implementations.

scaffolding a plugin

generate a plugin template using the cli:

1dstrn make:plugin camera

this creates platform specific files in your project:

1native/plugins/
2├── camera.swift # ios
3├── camera.kt # android
4└── camera.rs # desktop

each file contains boilerplate with method registration and example implementations.

ios implementation

ios plugins extend the bridge class with new methods:

1extension Bridge {
2 @objc func cameraCapture(_ data: [String: Any], _ callback: @escaping RCTResponseSenderBlock) {
3 let picker = UIImagePickerController()
4 picker.sourceType = .camera
5 // implementation
6 callback([["success": true]])
7 }
8}

android implementation

android plugins add methods to the bridge class:

1class Bridge {
2 @JavascriptInterface
3 fun cameraCapture(data: String): String {
4 val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
5 // implementation
6 return JSONObject().put("success", true).toString()
7 }
8}

desktop implementation

desktop plugins use tauri commands:

1#[tauri::command]
2fn camera_capture(data: Value) -> Result<Value, String> {
3 // implementation
4 Ok(json!({"success": true}))
5}

plugin registration

after creating platform implementations, rebuild your native runtimes. the framework automatically discovers and registers plugin methods during the build process.

call plugin methods using the same bridge api:

1const photo = await window.dstrnBridge.call('camera.capture', {
2 quality: 0.8
3});

plugins can emit custom events that your javascript code listens for:

1window.dstrnBridge.on('camera:captured', (data) => {
2 console.log('photo saved:', data.path);
3});