Custom protocols and routing
Some apps need more than a single https://-style origin. Custom protocols let you give internal resources a stable
shape and route them through service-worker logic instead of hard-coding filesystem paths or special-case branches in UI code.
1) Declare the protocol and enable service workers#
[permissions]
allow_service_worker = true
[webview]
url_protocols = "notes"
protocol-handlers = "notes"
url_protocols teaches the JavaScript URL shim that notes: is part of the app’s URL model. protocol-handlers
registers the scheme with the runtime protocol-handler table so navigation and fetch routing can treat it as app-owned.
Use the hyphenated protocol-handlers TOML key for current Runtime builds; it maps to the flattened runtime key
webview_protocol-handlers.
2) Ship and register a service worker for that scheme#
copy-map.toml:
"./src/index.html" = "index.html"
"./src/main.js" = "main.js"
"./src/sw.js" = "sw.js"
src/main.js:
if ('serviceWorker' in navigator) {
await navigator.serviceWorker.register('/sw.js', {
scope: '/',
scheme: 'notes',
})
await navigator.serviceWorker.ready
}
The scheme option is the important part. Without it, the worker is registered for the default runtime scheme and will
not receive notes: requests.
If you want the runtime to bind the script from config instead of registering it from JavaScript, use a per-scheme
handler entry instead of the scalar protocol-handlers list:
[webview]
url_protocols = "notes"
[webview.protocol-handlers]
notes = "/sw.js"
3) Route the protocol in the service worker#
src/sw.js:
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url)
if (url.protocol !== 'notes:') return
const noteId = url.pathname.replace(/^\\//, '')
event.respondWith(
new Response(JSON.stringify({ noteId }), {
headers: { 'content-type': 'application/json' },
})
)
})
Now a request for notes://local/123 can be handled in a controlled, testable place. Use a stable host such as
local when you want the identifier in the pathname; with notes://123, 123 is the URL host, not the path.
const response = await fetch('notes://local/123')
const note = await response.json()
console.log(note.noteId)
4) Inspect the registered handler from app code#
oro:protocol-handlers lets you ask the runtime which service-worker registration is currently bound to a scheme:
import { getServiceWorker } from 'oro:protocol-handlers'
const worker = await getServiceWorker({ scheme: 'notes' })
console.log(worker)
That is useful when debugging route ownership and protocol bootstrapping.
5) Use protocol handlers for clarity, not novelty#
Good uses:
- internal document references,
- app-scoped asset URLs,
- virtual endpoints that should never hit the network.
Bad uses:
- replacing every normal relative path,
- hiding business logic behind clever URL tricks,
- inventing new schemes when a normal route would do.
6) Pair protocols with explicit window creation#
If a secondary window needs the same protocol support, create it with the matching handler list:
import application from 'oro:application'
await application.createWindow({
index: 1,
path: 'details.html',
title: 'Details',
protocolHandlers: ['notes'],
})
That writes the window-local webview_protocol-handlers_notes config entry. The secondary window still needs a
navigator.serviceWorker.register('/sw.js', { scope: '/', scheme: 'notes' }) call in its startup path if it must own
the route itself.
Next#
Source repository · Edit this page · View Markdown