Skip to content

Static app packages

Most Matrix webapps are static-asset packages: a built dist/browser/ directory containing HTML, JS, CSS, images. The gateway serves them through MatrixHttpAssetEndpointActor.

Package shape

A static app package's matrix.json:

json
{
  "name": "@open-matrix/chat",
  "webapp": {
    "appName": "chat",
    "displayName": "Chat",
    "icon": "💬",
    "navOrder": 10,
    "shells": ["platform"],
    "description": "...",
    "routePrefix": "/apps/chat/"
  },
  "components": [
    {
      "type": "ChatActor",
      "mount": "chat",
      "autoStart": true,
      "...": "..."
    }
  ]
}

The package's runtime, when started, reads webapp.distDir (or the build-derived equivalent) and configures a MatrixHttpAssetEndpointActor mounted at chat.http pointing at that directory.

Asset endpoint config

typescript
interface IMatrixHttpAssetEndpointConfig {
  packageName: string;     // for diagnostics
  appName: string;         // 'chat'
  distDir: string;         // absolute path resolved at mount time
  entryFile: string;       // typically 'index.html'
}

The runtime resolves distDir from:

package-install-path + matrix.json.webapp.distDir (if set)

For a package installed via npm install @open-matrix/chat, that's typically <host-home>/packages/global/node_modules/@open-matrix/chat/dist/browser.

Asset endpoint mount path

typescript
function buildMatrixHttpAssetMount(appName: string, runtimeId?: string): string {
  if (runtimeId) {
    return `system.runtimes.${runtimeId}.http`;
  }
  return `${appName}.http`;
}

If a runtimeId is provided, the mount is namespaced under system.runtimes.<id>.http to avoid collisions when multiple runtimes serve the same appName. Otherwise it's the simple <appName>.http.

Webapp record advertisement

Alongside the asset mount, the runtime advertises a webapp record in its runtime metadata. This is what _loadRoutes reads:

typescript
{
  appName: 'chat',
  runtimeId: 'RUNTIME-host-chat',
  packageName: '@open-matrix/chat',
  webapp: {
    assetMount: 'chat.http',
    appName: 'chat',
    displayName: 'Chat',
    routePrefix: '/apps/chat/',
    shells: ['platform'],
    icon: '💬',
    navOrder: 10
  }
}

The record reaches the gateway through:

  • The host-runtime record file (<host-home>/runtimes/<id>/runtime.json), written by host-service when the runtime starts.
  • The runtime registry actor (system.runtimes.runtimes.registered).

Request flow

Browser GET /apps/chat/assets/index.js
  → Gateway HTTP listener
  → onWebapp handler
  → SystemGatewayHttpActor.onGatewayHttpRequest('chat', pathname)
  → _loadRoutes (joins records + registry)
  → _resolveRoute → { assetMount: 'chat.http', runtimeId: 'RUNTIME-host-chat' }
  → RequestReply.execute('chat.http', 'http.asset.request', { pathname, method: 'GET' })
  → MatrixHttpAssetEndpointActor.onHttpAssetRequest
  → resolveStaticAssetPath('assets/index.js', distDir) → filePath
  → fs.readFile(filePath) → bytes
  → return { ok: true, statusCode: 200, headers: { 'content-type': 'text/javascript' }, bodyEncoding: 'base64', bodyBase64: ... }
  → Gateway writes HTTP response to browser

End-to-end: one HTTP request, one Matrix RR, one filesystem read.

SPA fallback

For client-side-routed SPAs, a path like /apps/chat/conversations/abc is not a real file. The asset endpoint detects this:

typescript
const resolved = resolveStaticAssetPath(pathname, config);
if (!resolved) {
  return { ok: false, statusCode: 404, ... };
}

If the SPA package has its dist set up so index.html is the fallback (the common case), the resolver returns index.html's path. The browser then loads index.html and the SPA's client-side router handles the path.

What can go wrong

FailureCauseFix
404 for valid assetdistDir not resolved correctly at mount; runtime points at wrong directoryCheck runtime.json.metadata.webapp.assetMount and the resolved distDir
502 with "exceeded relay limit"File >10 MBReduce asset size; chunked delivery is target state
Slow first requestRR call to runtime registry; cold-start costAcceptable; subsequent requests reuse parsed routes
Missing MIME type / application/octet-streamFile extension not in MIME_TYPES tableUse one of the supported extensions or override in package config

See also

Source: projects/matrix-3/packages/system-gateway-http/src/index.ts:80-219.