Jasonelle is a nice native wrapper for your Web Application.


The project requires purchasing a license if you want to:

  • Use MPLv2 code.
  • Execute in real hardware.

With this small contribution you enable us to continue creating awesome tools for your web applications.

By adquiring a License Key you agree with the following:

  • Use Jasonelle's technologies and licenses in an ethical way.
  • Respect the artifacts license terms and conditions.
  • Must not share the license keys with third parties.
  • Must not create direct competitors to Jasonelle by rebranding, customizing and reselling the framework and related technologies or services.

Please select the License Key that better fits your project.

Jasonelle Friend's Membership SubscriptionThank you Key
Monthly Payment RequirementSingle Payment
Jasonelle's Membership Keys can be used in any amount of projects, as long as the subscription is maintained.This license allows Jasonelle framework to be used by you or one client, in a single commercial end product.
The license expires if the membership is terminatedThank you key licenses do not expire. That's why they are a little more expensive than the subscription.
Join SubscriptionPurchase Thank you Key

Please go to Jasonelle Store to purchase a license and edit Love.h to allow real device execution and MPLv2 licensed code.

If no license is purchased then the code is under AGPLv3. And will not execute using real devices. Although you can activate the full mode if you want to test if Jasonelle is good for your use case (some features like GPS need real hardware for testing).

Please purchase a license key if you want to use Jasonelle's solutions in your project.

Since we cannot enforce this. We rely on your good heart. We trust you are a kind person who will activate this mode ethically.


For help and general chat go to our Telegram group.

The main repository is:


The latest releases are available at:

Current Version

The current version is v3.


The projects documentation is inside the jasonelle.github.io repository. You can access it online at:

Dual License

Jasonelle Project is dual licensed. You can choose between AGPLv3 or MPLv2. MPLv2 is only valid if the software has a unique Jasonelle Key which was purchased in official channels.

Read More.

🤩 Credits

Made with by Ninjas.cl .

iOS Version 3.0.1

This small guide will help you getting started.


  • XCode 13 or greater
  • iOS 14 or greater


1 - Download the project

Download Bleeding Edge or Stable version. We recommend bleeding edge for the latest features and stable for the more battle tested.

Decompress and open App.xcworkspace file (White icon).

Find sources/xcode/App/JS/lib/screens/main.js.

2 - Configure your website

Put your URL for your application. This can be an external url or an HTML file.


Local Files

If you use res:// you can access local html files such as the example index.html.

You can store local files in the Files directory.


Is the example html file with code examples to test different Jasonelle features.

3 - Allowed URLs

By default Jasonelle will open all urls. If you wish to limit this behaviour you can use an allowed urls property in the configuration file.

Any url that is not present in the allowed list will force open using the native browser.

Check the Configuration File

config file


List the allowed urls. Otherwise it will launch native browser If not present will allow all urls

Put the same URL from main.js here to allow it (just the domain)

allowed: ["file://", "google.cl"]

4 - Done

You can now configure your project as a normal XCode iOS. Change your App Icon and other settings.

Happy Coding!.


All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog.

v3.0.1 (next)


  • Ability for extensions to inject Javascript to the WKWebView instance.

  • Ability for the WKWebView instance to load url by using deep links like: jasonelle://href?=https://google.cl.

  • Added JLPhotoLibrary extension that helps requesting access to photo library.

  • $keychain extension: $keychain.set, $keychain.get, $keychain.remove for usage within the WebView. This will enable storing data securely in the iOS's keychain.

  • $cookies extension: $cookies.set, $cookies.get, $cookies.remove for usage within the WebView. This will help with storing cookies in the keychain. Requires $keychain extension to be enabled. Also you can use $cookies.Cookies to access js-cookie library.

  • $contacts extension: $contacts.all for usage within the WebView.

  • Ability to have allowed list of urls in configuration. Not allowed urls will launch native browser.

  • Added LaunchScreen file (Both in SwiftUI and Storyboard file).

  • Added WebView.edgesIgnoringSafeArea(.all). Some websites need this, specially when using a navbar. Thanks to @Mättu in Telegram for pointing this out.

  • Added meta viewport js fix for websites that do not have proper metatag. In webview.js.

  • Added example extension.

  • Added hook triggering for extensions.

  • Added event triggering in webview for extensions.

  • Added Reachability Events Extension.


  • WKWebView triggered appdidLoad event more than once. Now it only triggers the event when loaded.


v3.0.0 (September 2022)

This is a new engine created from scratch by Camilo in 2022. (AGPLv3 or MPLv2 Licenses). It was ditched the old json based approach to a javascript one. Consists of mainly a WebView engine, because there are lots of competition of "native over the wire" frameworks, and was better to focus on the Web App market such as Bubble users.

  • New rewrite of the engine from scratch.
  • Will only focus on webview workflow.
  • No need for Cocoapods, Carthage or Swift PM.
  • Native over the wire workflows delegated to other frameworks like Native Live.


Legacy Versions (2016 - 2022)

These versions are not currently supported, but maybe something can be learned or be useful. These were using the old engine created originally by Ethan. (MIT License).



A template is a pre configured App.xcworkspace file that is tailored for a specific use case.

They are meant to be used as an starting point for your application.

You can easily update the core if needed, just by replacing the directories.

Cocoapods Template

If you want to use Cocoapods you can use this starter template.


OneSignal Template

If you want to use OneSignal SDK you can use this starter template.

It contains the pre configured project. But it needs the specific configurations, such as capabilities, groups and other user related settings.



Extensions are a great way to add new functionality and helpers to Jasonelle. They can be updated separately from the core and be added or removed at will.

Adding an Extension

Extensions are a single *.xcodeproj (blue icon) that contains the project that can be added to the App.xcworkspace (white icon) file.

Step 1

Open App.xcworkspace with Xcode and look for the *.xcodeproj file in Finder.


Step 2

Draw it to the Extensions directory inside Xcode.



Step 3

Add it to the frameworks in App.xcproj file. Be sure that Embed & Sign option is selected.

added frameworks

Step 4

import the extension in AppExtensions.m.


and add it to the extension registry in install method using the class attribute.

Example: [self.extensions add: JLContacts.class];



If everything is ok then the extension was installed successfully, maybe some other configurations would be needed depending on the extension itself.

Removing an Extension

If you don't need an extension you can remove it from the workspace.

Step 1

Remove (or comment) the setup code in AppExtensions.m file described in Adding an Extension: Step 4.



Step 2

Remove the framework from the App.xcproj file.

added frameworks

Step 3

Delete the *.xcodeproj file from the Workspace. It will pop up a confirmation dialog. We recommend Remove Reference option.

remove reference

Create Extensions

Creating extensions requires some knowledge of Objective-C and JavaScript. These instructions are for advanced users.

Step 1

Create a new xcodeproj file.


Select Framework as the template. Be sure that is in the iOS tab.


Add it to the Extensions directory inside the App.xcworkspace.



Step 2

Configure the iOS Deployment Target to 14.0 (or the recommended version for your project).


Add the JLKernel framework. Select Do Not Embed as the option. Since all the frameworks will be embeded in App.xcproj later.


Step 3

Create the *.h and *.m files that inherits from JLExtension.






#import <Foundation/Foundation.h>

//! Project version number for MyExtension.
FOUNDATION_EXPORT double MyExtensionVersionNumber;

//! Project version string for MyExtension.
FOUNDATION_EXPORT const unsigned char MyExtensionVersionString[];

#import <JLKernel/JLKernel.h>


@interface MyExtension : JLExtension



#import "MyExtension.h"

@implementation MyExtension



Now you can install it as any other extension in your App.xcodeproj file.

Extension Coding

When coding an extension you can access some events and properties that will enable to add new features to Jasonelle.

- (void) install

This method will be executed when installing the extension in AppDelegate lifecycle. This is before all the extensions are ready, because a single extension is being installed at the time.

Be sure to call the [super install] method.

- (void) install {
    [super install];
    // your code here


The handlers are the names of the native bridges that will be called when used inside the webview. Must be configured in the install method.

See JLKeychain extension as an example with using handlers.

- (void) install {
  // ...
  self.handlers = @{
        @"$keychain.set": setHandler,
        @"$keychain.get": getHandler,
        @"$keychain.remove": removeHandler,
        @"$keychain.clear": clearHandler

A handler is a native function that is called within the webview.

Handlers will be called using $agent.trigger() inside the webview.

  • Example: $agent.trigger('$keychain.get')

They will return a JS Promise.

A wrapper can be created to call directly.

 (() => {
     if (window && window.$myextension) {
     window.$myextension = {};
     window.$myextension.run = () => $agent.trigger("$myextension.run", {});

- (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(nullable NSDictionary *)launchOptions

This method is called in AppDelegate after the setup and before loading the webview. Normally for post install logic. All the other extensions would be available at this point.

- (nonnull WKWebView *)appDidLoadWithWebView:(nonnull WKWebView *)webView

This method is called when the WKWebView is ready. You can use it to inject JavaScript files or make additional configurations to the webview element.

- (nonnull WKWebView *)appDidLoadWithWebView:(nonnull WKWebView *)webView {
    [super appDidLoadWithWebView:webView];
    return webView;


See JLCookies as an example extension that only loads a JS file.

More methods

See which other methods are available in JLApplicationExtensions

FileSystem Helper

You can access the filesystem by using the app.utils.fs helper.


Reads a file named JLContacts.js inside the self bundle.

NSString * js = [self.app.utils.fs

// If is named the same as the class then can be used like this
NSString * js = [self.app.utils.fs

WebView Helper

To help injecting scripts to webview you can use app.utils.webview helper.

webView = [self.app.utils.webview inject:@"js.cookie.min" intoWebView:webView for:self];

// Reads the filename as Classname.js
return [self.app.utils.webview inject:self intoWebView:webView];

Extension Service Locator

You can fetch the extension instance by using the app.ext service locator.

return (JLATTrackingManager *) [self.app.ext get:JLATTrackingManager.class];

return (JLApplicationBadge *) [self.app.ext get:JLApplicationBadge.class];


Optionally install ATTracking Support Request user authorization to access app-related data for tracking the user or the device. Ensure to configure the plist as well. If your app calls the App Tracking Transparency API, you must provide custom text, known as a usage-description string, which displays as a system-permission alert request. The usage-description string tells the user why the app is requesting permission to use data for tracking the user or the device.

  • Since: 3.0.1


Request user authorization to access app-related data for tracking the user or the device.



The ATTracking message will be triggered on install.


Configure your info.plist and include the NSUserTrackingUsageDescription key and fill it with the description.



Returns the current status for the ATTracking.


Can manipulate Application Icon Badge Number

  • Since: 3.0.1


The number currently set as the badge of the app icon on the Home screen.



  • $badge.clear: Cleans the badge number icon.
  • $badge.set: Sets the badge number icon.




A helper to work with the WebView cookies.

  • Since: 3.0.1


Requires $keychain extension.



  • $cookies.set: Stores document.cookie in keychain.
  • $cookies.get: Gets cookie value stored in keychain.
  • $cookies.remove: Removes cookies value from keychain.
  • $cookies.write: Gets the cookie value stored in keychain and write it to document.cookie.
  • $cookies.Cookies: js-cookie Helper.


Sets the cookie and save it to keychain

// Set a value inside document.cookies
$cookies.Cookies.set('name', 'Ethan'); 

// Save the document.cookies to keychain.
$cookies.set().then(val => alert(val));

Gets the current cookie value from keychain

$cookies.get().then(val => alert(val));

Reads cookie from keychain and then writes to document.cookie

$cookies.write().then(val => alert(val));

Removes cookies from keychain

$cookies.remove().then(val => alert(val));


Keychain extension allows storing values inside iOS Keychain

  • Since: 3.0.1


Keychain is made as a key - value secure storage.



  • $keychain.set: Sets a key to store a value.
  • $keychain.get: Gets the value for a given key.
  • $keychain.remove: Removes a key from the keychain.
  • $keychain.clear: Removes all keys and values from the keychain.


// Stores the number 3 in keychain
$keychain.set('number', 3);

// Get the number 3 and show an alert with it
$keychain.get('number').then(num => alert(num));

// Removes the stored number

// Clears the keychain


Provides methods to requesting Photo Library permissions

  • Since: 3.0.1


It would be required if your app wants to access camera or photo library. Will trigger only in install event. Does not have webview actions yet.




Returns Promise.resolve(Bool) if the permission is granted.


$photolibrary.granted().then(granted => alert(granted));

$photolibrary.authorize(showAlert = false)

Returns Promise.resolve(Int) with the Authorization Status.

If showAlert = true then it will prompt an alert requesting the user go Settings.

The returned number corresponds to PHAuthorizationStatus

  • PHAuthorizationStatusNotDetermined = 0 User has not yet made a choice with regards to this application

  • PHAuthorizationStatusRestricted = 1

This application is not authorized to access photo data. The user cannot change this application’s status, possibly due to active restrictions such as parental controls being in place.

  • PHAuthorizationStatusDenied = 2

User has explicitly denied this application access to photos data.

  • PHAuthorizationStatusAuthorized = 3

User has authorized this application to access photos data.

  • PHAuthorizationStatusLimited = 4

User has authorized this application for limited photo library access. Add PHPhotoLibraryPreventAutomaticLimitedAccessAlert = YES to the application's Info.plist to prevent the automatic alert to update the users limited library selection.


$photolibrary.authorize().then(status => $logger.trace(status));


Add NSPhotoLibraryUsageDescription and NSCameraUsageDescription permissions to info.plist

<string>If you want to use the photolibrary, you have to give permission.</string>
<string>If you want to use the camera, you have to give permission.</string>


The Contacts extension provides APIs to access the user’s contact information.

  • Since: 3.0.1


An iOS app linked on or after iOS 10 needs to include in its Info.plist file the usage description keys for the types of data it needs to access or it crashes. To access Contacts data specifically, it needs to include NSContactsUsageDescription.

More Info



"name": String,
"lastname": String,
"phone": String,
"phones": Array,
"email": String,
"emails: Array,
"address": String,
"addresses": Array


  • $contacts.all(): Returns a promise with all the contacts.

Example Return:

    "phone": "5555648583",
    "phones": ["5555648583", "4155553695"],
    "emails": ["kate-bell@mac.com"],
    "address": "165 Davis Street\nHillsborough CA 94010",
    "lastname": "Bell",
    "addresses": ["165 Davis Street\nHillsborough CA 94010"],
    "email": "kate-bell@mac.com",
    "name": "Kate"
}, {
    "phone": "5554787672",
    "phones": ["5554787672", "4085555270", "4085553514"],
    "emails": ["d-higgins@mac.com"],
    "address": "332 Laguna Street\nCorte Madera CA 94925\nUSA",
    "lastname": "Higgins",
    "addresses": ["332 Laguna Street\nCorte Madera CA 94925\nUSA"],
    "email": "d-higgins@mac.com",
    "name": "Daniel"
}, {
    "phone": "8885555512",
    "phones": ["8885555512", "8885551212"],
    "emails": ["John-Appleseed@mac.com"],
    "address": "3494 Kuhl Avenue\nAtlanta GA 30303\nUSA",
    "lastname": "Appleseed",
    "addresses": ["3494 Kuhl Avenue\nAtlanta GA 30303\nUSA", "1234 Laurel Street\nAtlanta GA 30303\nUSA"],
    "email": "John-Appleseed@mac.com",
    "name": "John"
}, {
    "phone": "5555228243",
    "phones": ["5555228243"],
    "emails": ["anna-haro@mac.com"],
    "address": "1001  Leavenworth Street\nSausalito CA 94965\nUSA",
    "lastname": "Haro",
    "addresses": ["1001  Leavenworth Street\nSausalito CA 94965\nUSA"],
    "email": "anna-haro@mac.com",
    "name": "Anna"
}, {
    "phone": "5557664823",
    "phones": ["5557664823", "7075551854"],
    "emails": ["hank-zakroff@mac.com"],
    "address": "1741 Kearny Street\nSan Rafael CA 94901",
    "lastname": "Zakroff",
    "addresses": ["1741 Kearny Street\nSan Rafael CA 94901"],
    "email": "hank-zakroff@mac.com",
    "name": "Hank"
}, {
    "phone": "5556106679",
    "phones": ["5556106679"],
    "emails": [],
    "address": "1747 Steuart Street\nTiburon CA 94920\nUSA",
    "lastname": "Taylor",
    "addresses": ["1747 Steuart Street\nTiburon CA 94920\nUSA"],
    "email": "",
    "name": "David"



Join all the contacts by the first name in a single string. And present an alert.

$contacts.all().then(val => alert(val.reduce((acc, v) => acc + v.name + ' ', '')))


Logs the resulting object.

$contacts.all().then(info => $logger.trace(JSON.stringify(info)))


Helps to detect when access to internet was lost.

  • Since: 3.0.1


Sends a notification event inside the WebView and NSNotificationCenter.



const reachability = {
    // Apple NetworkStatus Compatible Names.
    status: {
        not_reachable: 0,
        wwan_reachable: 1,
        cellular_reachable: 1, // the same as wwan, just to have ubiquotus language
        wifi_reachable: 2
    status_names: {
       0: "No Connection",
       1: "Cellular",
       2: "Wifi"


  • $reachability.get: Returns the current reachability object.


    "status": Number, // Status of the Connection
    "reachable": Boolean, // true if is reachable
    "label": String // String with the current connection label


$reachability.get().then(result => alert(result.label));


  • $reachability.events.changed: Triggered when a change in reachability is detected.


document.addEventListener("$reachability.events.changed", function(e) {
    $logger.trace("Reachability Changed: " + JSON.stringify(e.detail));


An NSNotification is sent with the name kReachabilityChangedNotification.

// Here we set up a NSNotification observer. The Reachability that caused the notification
// is passed in the object parameter
[[NSNotificationCenter defaultCenter] addObserver:self

See more details at Tony Million's Reachability Docs.