JLKernel
The core framework for Jasonelle iOS apps.
Overview
JLKernel provides the runtime backbone for Jasonelle applications. It bundles a SwiftUI WebView backed by WKWebView with a bidirectional JavaScript bridge, a plugin system for native extensions, native Events that plugins can listen to, structured logging via Logger, semantic Version reading, and License verification.
A typical app creates a WebView with a dictionary of Plugin instances. JavaScript code communicates with native plugins through window.jasonelle.post(name, args), and plugins respond by executing JavaScript back in the web view. Native code communicates with plugins through Events.
Quick Start
import JLKernel
struct ContentView: View {
var body: some View {
JLKernel.WebView(
url: URL(string: "https://example.com")!,
plugins: ["myplugin": MyPlugin()]
)
}
}
How the Bridge Works
-
The
WebViewinjects awindow.jasonelleJavaScript object at document start. -
JavaScript calls
window.jasonelle.plugins.<name>.call(args), which posts a message to the native side viawebkit.messageHandlers. -
The
Coordinatorreceives the message, looks up thePluginby name, and invokes itshandle_call(args:callbackId:respond:)method. -
The plugin calls
respond(script)to execute JavaScript back in the web view and resolve the promise returned bywindow.jasonelle.post. Withcall().then(response ⇒ …)a JavaScript caller awaits the native response.
Native Events
Native code can notify plugins about app lifecycle events. The app registers its Plugin dictionary once with Events.register(plugins:) (e.g. in Main.init()), then broadcasts an event with Events.sendOnAppear(). Every registered plugin receives it through handle_event(name:args:respond:), with the event name given by the raw value of the Events case.
Events Pushed Back to JavaScript
To let the page react to an event, a plugin forwards it to JavaScript from its handle_event override using the event(_:plugin:args:respond:) helper:
public override func handle_event(_ event: String, args: [String: Any]? = [:], respond: @escaping (String) -> Void) {
self.event(event, plugin: Plugin.name, args: args ?? [:], respond: respond)
}
The helper merges event and plugin with the plugin’s own args, serializes the result and evaluates it in the page:
window.jasonelle.plugins.hello.handle({"event":"ContentView.onAppear","plugin":"hello"});
The page receives it in the handle function that the plugin’s own Plugin.js assigns, not in a global listener:
plugin.handle = ({ event, ...payload }) => {
if (event === "ContentView.onAppear") cart.refresh(payload);
};
Event Timing
handle_event itself runs natively and is never delayed. Reaching JavaScript is a different matter: plugin scripts are evaluated at document end, so an event broadcast from ContentView.onAppear would arrive before the page could possibly have run them. Evaluating it against an unloaded document drops it silently.
The Coordinator therefore buffers native responses while the page is loading and replays them in order on didFinish navigation. A new navigation clears the buffer, since those scripts belong to a page that never finished loading.