Contents

MōBrowser 2.17.0

In this update, we added the Session API for cookies, browsing data, and the user agent, authenticated update servers, modern message dialogs on Windows, and left and right Command keys in macOS global shortcuts. Linux gets window menus, tray items, and windows without a title bar. Packaged apps now ignore Node.js environment variables and verify the integrity of their sources at startup. MōBrowser is now based on Chromium 154.

What’s new 

Session API 

The new session object manages the browsing session shared by every browser in the application. Use it to read and write cookies, clear caches and browsing data, and change the user agent:

import { session } from '@mobrowser/api';

// Read and write cookies.
const cookies = await session.cookies.get({ url: 'https://example.com' })
await session.cookies.set({
  url: 'https://example.com',
  name: 'theme',
  value: 'dark'
})

// Get notified when a cookie changes.
session.cookies.on('changed', ({ cookie, cause, removed }) => {
  console.log(`${cookie.name} ${removed ? 'removed' : 'set'}: ${cause}`)
})

// Sign the user out of a site.
await session.clearData({
  dataTypes: ['cookies', 'storage'],
  origins: ['https://example.com']
})

// Identify the application to web servers.
session.setUserAgent('MyApp/1.0')

The session also provides getCacheSize(), clearCache(), clearAuthCache(), clearHostResolverCache(), and clearCodeCaches(), and reports where browsing data is stored with storagePath and isPersistent(). A new user agent applies to pages that are already open as well as to pages loaded afterwards.

clearData() removes data by site, not by exact origin: clearing https://mail.example.com also clears example.com and every host under it.

Authenticated update servers 

An update server no longer has to be public. Applications can now send HTTP headers, such as an Authorization token, with the update feed request and with package downloads. This covers a CDN that requires a token, a Cloudflare Access service token, and an Azure SAS.

The built-in Check for Updates… flow takes headers from mobrowser.conf.json. A production build replaces ${VAR} with the value of the environment variable and fails if the variable is not set:

{
  "app": {
    "updateServerUrl": "https://example.com/updates/",
    "updateServerHeaders": { "Authorization": "Bearer ${UPDATE_TOKEN}" }
  }
}

A custom update flow passes its own headers, for example a per-user token or credentials for a separate feed:

import { app } from '@mobrowser/api';

const update = await app.checkForUpdate('https://example.com/updates/', {
  headers: { Authorization: `Bearer ${token}` }
})

Headers are sent only to the origin of the update server and are dropped on a redirect to another origin. The update feed can also point packages at pre-signed URLs, such as S3 or GCS links, and a query string on the update server URL is kept. URL queries and header values are removed from the update log and error messages.

Note: Headers from mobrowser.conf.json ship inside the application bundle in plain text and are shared by every installation. For per-user or revocable credentials, pass headers to app.checkForUpdate() instead.

Node.js environment variables are ignored in production 

Breaking change. Packaged applications now ignore the environment variables Node.js reads, so the environment an application is launched with can no longer control its main process. Previously, NODE_OPTIONS could run arbitrary code with --require or open a debugger with --inspect inside a signed application. SIGUSR1 no longer starts the inspector either.

TLS variables such as NODE_EXTRA_CA_CERTS and NODE_TLS_REJECT_UNAUTHORIZED are ignored as well, so Node.js https and fetch trust only the bundled CA store. Chromium networking is unaffected.

These variables are also removed from process.env and from child processes the application spawns. If your application relies on one of them, for example to pass NODE_EXTRA_CA_CERTS to a spawned npm behind a corporate proxy, set it explicitly on the child process. NODE_ENV is kept, and npm run dev still honors all of these variables.

Application integrity checks 

MōBrowser no longer encrypts the application sources in app.bin. The decryption key had to ship with the application, so encryption never kept the sources confidential. app.bin is now a plain archive, and the runtime verifies it before loading any sources:

  • macOS. When the application is signed with a Developer ID Application certificate, app.bin is covered by the code signature of the bundle. An application whose app.bin was modified after signing refuses to start.
  • Windows. When the application is signed, packaging creates a resources/app.bin.cat catalog and signs it with the same signCommand as the application executable. At startup, the runtime checks that app.bin matches the catalog and that the catalog and the executable are signed with the same certificate.
  • Linux and unsigned builds. app.bin ends with a SHA-256 checksum that detects corruption and truncation. It does not protect against deliberate modification.

The build no longer generates the mobrowser.app.id file.

Modern message dialogs on Windows 

Message dialogs on Windows have a new look. They follow the application’s light or dark theme, use modern buttons, text and password fields, and checkboxes, and support keyboard navigation and high-DPI displays.

The API, button roles, and returned values are unchanged. File open and save dialogs still use the native Windows implementation.

Window menus on Linux 

Each BrowserWindow on Linux can now have its own menu bar, in the same way as on Windows. Attach a menu with setMenu(), and pass null to remove it:

import { BrowserWindow, Menu } from '@mobrowser/api';

const win = new BrowserWindow()
win.setMenu(new Menu({
  items: [
    new Menu({
      label: 'File',
      items: [
        'closeWindow'
      ]
    })
  ]
}))

The menu bar is displayed inside the window frame. Different windows can display different menus at the same time. The checkForUpdates role is available only on macOS and Windows.

Tray on Linux 

Tray items are now available on Linux, in addition to macOS and Windows. The same Tray API is used on all platforms:

import { app, Tray, Menu, MenuItem } from '@mobrowser/api';

const tray = new Tray({
  tooltip: 'My App',
  imagePath: app.getPath('appResources') + '/image.png',
  menu: new Menu({
    items: [
      new MenuItem({
        id: 'quit',
        label: 'Quit',
        action: () => app.quit()
      })
    ]
  })
})

Hiding the window title bar on Linux 

A window on Linux can now be displayed without a title bar, which lets you draw your own title bar in web content:

import { BrowserWindow } from '@mobrowser/api';

const win = new BrowserWindow({
  windowTitlebarVisible: false
})

The window keeps its shadow and resize handles, and the rounded corners that GNOME draws for the title bar no longer look broken at the top of the window.

Left and right Command keys in global shortcuts 

Global shortcuts on macOS can now distinguish the left and right Command keys. Use LeftCommand (LeftCmd) or RightCommand (RightCmd) on its own or in combination with other keys:

import { globalShortcut } from '@mobrowser/api';

globalShortcut.register('RightCommand', () => toggleDictation())
globalShortcut.register('LeftCommand+Shift+K', () => performAction())

Cmd and CmdOrCtrl still match either Command key. Pressed keys also reach the focused application, and using these shortcuts while the application is in the background requires the Accessibility permission.

Stack updates 

  • Updated Chromium to version 154.0.8037.58.
  • Updated Node.js to version 24.21.0.
  • Updated the Velopack fork used for automatic updates with the latest upstream Velopack changes.

Fixes and improvements 

  • Fixed draggable regions on Windows and Linux. Dragging a draggable region now moves the window.
  • Fixed a gap between the window menu bar and web content on Windows when the window title bar is hidden.
  • Fixed app.showOpenDialog() filters on macOS greying out document packages. A package whose extension matches a filter, such as one declared through fileAssociations with "isPackage": true, or a package type registered by another application such as .rtfd or .xcodeproj, can now be selected and is returned as a single path.
  • Fixed a crash on launch on Linux desktops where Chromium chose to load GTK 4.
  • Fixed the layout of window buttons on Linux. Hiding a window button no longer leaves a gap between the remaining buttons.
  • Fixed a crash when calling download() on an application update after dismiss(). The returned promise now resolves with an error instead.
  • Fixed app.restart() on Windows when no update was pending. An application with no open windows, or one that handles allWindowsClosed itself, now restarts instead of staying alive.