JADE:// Custom Protocol: Loading Local Web Resources

JadeView registers a custom protocol handler via set_protocol_service_path, mapping JADE://{app_signature} (the domain is the lowercased app_signature) to a local directory or JAPK file to enable secure and efficient loading of local resources.

Prerequisites

  • Download JadeView_x64.dll and JadeView.h from GitHub Releases
  • Basic understanding of the C language

Step 1: Initialize and Set Up the Protocol Service

C
#include <stdio.h>
#include <string.h>
#include "JadeView.h"

// Window control IPC callbacks
const char* minimize_callback(uint32_t window_id, const char* event_data) {
    minimize_window(window_id);
    return jade_text_create("{\"ok\":true}");
}

const char* maximize_callback(uint32_t window_id, const char* event_data) {
    toggle_maximize_window(window_id);
    return jade_text_create("{\"ok\":true}");
}

const char* close_callback(uint32_t window_id, const char* event_data) {
    close_window(window_id);
    return jade_text_create("{\"ok\":true}");
}

const char* app_ready_callback(uint32_t window_id, const char* event_data) {
    if (window_id == 1) {  // window_id==1 means success; event_data is JSON, and on failure (window_id==0) it is a plain-text error code
        printf("JadeView is ready\n");

        // Register window control commands
        register_ipc_handler("minimize-window", minimize_callback);
        register_ipc_handler("toggle-maximize", maximize_callback);
        register_ipc_handler("close-window", close_callback);

        // Set up the local protocol service —— map the ./web directory to JADE://com.example.myapp
        char url_buffer[256];
        int result = set_protocol_service_path(
            "./web",              // local resource directory
            url_buffer,           // output protocol URL buffer
            sizeof(url_buffer),   // buffer size
            0                     // hot_reload: 0 = disabled, 1 = enabled
        );

        if (result == 1) {
            printf("Protocol URL: %s\n", url_buffer);
            // url_buffer content looks like: JADE://com.example.myapp

            WebViewWindowOptions options = {
                .title = "Local Resource Example",
                .width = 800,
                .height = 600,
                .resizable = 1,
                .frame_style = "normal"
            };

            create_webview_window(url_buffer, 0, &options, NULL);
        } else {
            printf("Failed to set up protocol service\n");
        }
    } else {
        printf("Initialization failed: %s\n", event_data ? event_data : "unknown");
    }
    return NULL;
}

int main() {
    // app-ready must be registered before JadeView_init
    jade_on("app-ready", app_ready_callback);

    int result = JadeView_init(
        1,                      // enable_devmod
        NULL,                   // log_path
        NULL,                   // data_directory
        "My App",               // app_name (app display name)
        "com.example.myapp",    // app_signature (lowercased, used as the protocol domain)
        0                       // single_instance
    );

    if (result == 0) {
        printf("Initialization failed\n");
        return 1;
    }

    printf("JadeView has started\n");
    return 0;
}

Step 2: Create the Frontend Page

Create a web folder and an index.html in your project directory:

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Local Resource Example</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            display: flex;
            flex-direction: column;
            height: 100vh;
            overflow: hidden;
        }

        /* Custom title bar */
        .titlebar {
            height: 32px;
            background: #333;
            color: #fff;
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding-left: 12px;
            -webkit-app-region: drag;
            user-select: none;
        }
        .titlebar-controls {
            display: flex;
            height: 100%;
        }
        .titlebar-btn {
            -webkit-app-region: no-drag;
            width: 46px;
            height: 100%;
            border: none;
            background: transparent;
            color: #fff;
            cursor: pointer;
            font-size: 14px;
        }
        .titlebar-btn:hover { background: #555; }
        .titlebar-btn.close:hover { background: #e81123; }

        .content {
            flex: 1;
            padding: 20px;
            background: #f5f5f5;
        }
        .btn {
            padding: 8px 16px;
            background: #333;
            color: #fff;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        .btn:hover { background: #555; }
    </style>
</head>
<body>
    <div class="titlebar">
        <span>Local Resource Example</span>
        <div class="titlebar-controls">
            <button class="titlebar-btn" id="minBtn">─</button>
            <button class="titlebar-btn" id="maxBtn">□</button>
            <button class="titlebar-btn close" id="closeBtn">✕</button>
        </div>
    </div>

    <div class="content">
        <h1>JadeView Local Resources</h1>
        <p>This page is loaded from the local file system via the custom protocol.</p>
        <button class="btn" id="testBtn">Send Test Message</button>
        <pre id="result"></pre>
    </div>

    <script>
        // Window control —— send commands to the host process via jade.invoke
        document.getElementById('minBtn').onclick = () => {
            jade.invoke('minimize-window');
        };
        document.getElementById('maxBtn').onclick = () => {
            jade.invoke('toggle-maximize');
        };
        document.getElementById('closeBtn').onclick = () => {
            jade.invoke('close-window');
        };

        // Test IPC communication
        document.getElementById('testBtn').onclick = async () => {
            try {
                const result = await jade.invoke('test-message',
                    { text: 'Hello from page!', time: Date.now() });
                document.getElementById('result').textContent =
                    JSON.stringify(result, null, 2);
            } catch (e) {
                document.getElementById('result').textContent =
                    'Error: ' + e.message;
            }
        };

        // Listen for pushes from the host process
        jade.on('host-notify', (data) => {
            console.log('Host process notification:', data);
        });
    </script>
</body>
</html>

Custom Protocol in Detail

How It Works

  1. set_protocol_service_path("./web", url_buffer, size, 0) registers the local directory with the protocol route
  2. The returned URL has the format JADE://{app_signature} (lowercased app_signature)
  3. When the WebView accesses JADE://com.example.myapp/index.html, JadeView reads directly from ./web/index.html
  4. The entire process involves no network communication and no port listening, and is completed entirely within the application

Supported File Types

JadeView automatically sets the correct MIME type based on the file extension, with no manual configuration required. Supported types include:

  • HTML, CSS, JavaScript
  • Images (PNG, JPG, GIF, SVG, WebP, etc.)
  • Fonts (WOFF, WOFF2, TTF, etc.)
  • Data formats such as JSON and XML

SPA Routing Support

The protocol service has a built-in SPA fallback: when the requested path does not match any file, it automatically returns index.html, providing seamless support for History-mode routing in frontend frameworks.


Combining with JAPK

In addition to local directories, set_protocol_service_path also supports JAPK files:

C
// Load a JAPK file
char url_buffer[256];
set_protocol_service_path("app.japk", url_buffer, sizeof(url_buffer), 0);
// url_buffer → JADE://com.example.myapp
create_webview_window(url_buffer, 0, &options, NULL);

Communication inside a JAPK also uses jade.invoke / jade.on, with no additional configuration required.

More information: JAPK Resource Bundle | Loading from Memory


Notes

  1. Not an HTTP server: JADE://{app_signature} does not listen on any port and cannot be accessed by external browsers
  2. jade.invoke requires a CORS whitelist (v2.1+): if the page is not loaded through the protocol service, you must set the allowed origins in WebViewSettings.cors_whitelist
  3. app_signature determines the domain: the protocol URL domain is taken from the app_signature parameter of JadeView_init (lowercased), not from app_name
  4. Page path mapping: JADE://com.example.myapp/css/style.css → ./web/css/style.css

Detailed API: Core API - Local Protocol Service | Frontend Communication API