Skip to main content
Version: 5.x

MTE Relay Web Client Implementation

Current SDK release: 5.0.0.

MTE Relay Client for Web​

The web client exposes mteFetch, which mirrors the browser's native fetch. Swap fetch for mteFetch, point the request at your MTE Relay Server, and the client encrypts the request and decrypts the response for you.

This page covers the v5 client, which speaks frame version 2 and works only with v5 relays. A v4 client cannot connect to a v5 relay; see Upgrading from v4.

Installation​

Install the client from npm:

npm install mte-relay-browser-public-client@5.0.0

mte-relay-browser-public-client 5.0.0 bundles the mte-relay-browser engine 5.0.0, the MTE WASM library, and a license, and it initializes itself when imported, so no setup call is required.

Quick Start​

import { mteFetch } from "mte-relay-browser-public-client";

const response = await mteFetch("https://relay.example.com/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username: "John", password: "P@ssw0rd!" }),
});

const data = await response.json();

The origin of the request URL is the relay. The path, query, headers, and body travel encrypted, and the relay forwards them to the upstream its DOMAIN_MAP names for that host. The method is encrypted too, but for the nine standard methods the frame also carries a one byte method code in the clear, which the relay checks against the encrypted copy. Open the browser's network tab and you will see POST / requests with Content-Type: application/octet-stream in place of your original requests.

On the first request to a relay origin the client authenticates, reads the relay's discovery document, and pairs. It keeps one session per relay origin and encode type.

Configuration​

Call configureMteRelay(options) once at startup, before the first mteFetch, to change the defaults. Calling it again replaces the previous configuration.

import { configureMteRelay, mteFetch } from "mte-relay-browser-public-client";

await configureMteRelay({
unencryptedHeaders: ["traceparent"],
credentials: "include",
});
OptionDefaultDescription
defaultEncodeType"MKE""MKE" or "MTE".
minPairs4Warm baseline of the pair pool; the pool refills in the background toward it.
initialPairs1Pairs created when a session starts, from 1 to maxPairs.
maxPairs8Hard ceiling on the pair pool. The relay's advertised maxPairsPerClient also caps it.
unencryptedHeaders[]Header names sent as ordinary HTTP headers instead of inside the encrypted frame. ["*"] sends every header plain except content-type and x-mte-relay-route, which are always encrypted.
credentials"include"The fetch credentials mode for every request to the relay: "include", "same-origin", or "omit".
warningstrueThe client's once per session console warnings.
keepAliveIntervalMsderivedKeepalive interval, from 60000 to 600000. Without it the client uses one third of the relay's session timeout, within the same range, which is 600000 for a relay that advertises the default or no session timeout.
httpTimeoutMs30000Timeout for each request to the relay.
sequenceWindow, timeWindowrelay's valuesThe client uses the values the relay advertises, and warns once when a configured sequenceWindow differs.
streamDiagnosticsofftrue logs a bounded preview of each frame to the console; an object with onEvent delivers them to your callback.
onPoolEventnoneCallback for pair pool events such as pair-appended, pair-replaced, and capacity-exhausted.

Per request options​

mteFetch takes the same two arguments as fetch, plus an optional third:

mteFetch(resource, init, { encodeType: "MTE", unencryptedHeaders: ["x-request-id"] });
OptionDescription
encodeType"MKE" or "MTE" for this request.
unencryptedHeadersReplaces the configured list for this request only.

The v4 options encodeUrl, encodeHeaders, pathPrefix, and useStreaming do not exist in v5. The path and every header are always encrypted, apart from names listed in unencryptedHeaders, and responses stream.

Plain headers, CORS, and cookies​

  • A header sent plain reaches your upstream only if the relay domain's forward_plain_headers lists it. The client warns once for each name the relay will not forward.
  • When the relay domain customizes cors_allowed_headers, it must list Content-Type, X-MTE-Relay-Route, and every plain header name.
  • The client sends credentials by default, so the relay domain must allow them, which it does unless cors_allow_credentials is false.
  • Cookies reach the upstream when the relay domain's forward_browser_cookies is on, which is the default.

See CORS and WAF for the relay side.

Pass through routes​

Requests whose path matches one of the relay domain's pass_through_routes are sent as plain fetch calls, with no encryption. The list arrives in the discovery document, so the first request to a relay origin still authenticates before any pass through call.

Errors​

ErrorThrown whenUseful fields
MteRelayErrorThe relay answered with a relay error code, 470 to 490, 500, 502, or 503.code, reason, action, relayMessage, retryAfterMs, requestId, repaired, pairReplaced
MteRelayTransportErrorThe answer was not a valid frame, such as a CDN error page, a WAF block, or an ingress 404. It is never treated as application data.status, detail, requestId
MteRelaySetupErrorThe relay and the client cannot work together, for example a frame version, library profile, or Kyber strength mismatch. Retrying does not help.message
MteRelayRequestTooLargeErrorThe body is larger than the relay's maxRequestBodyBytes. Nothing was sent.size, limit
MteRelayCapacityErrorThe pair pool is at maxPairs with no idle pair.maxPairs

MteRelayHttpError and MteRelayProtocolError are kept as aliases of MteRelayError and MteRelayTransportError. The client retries a request once by itself only for 486 pair_busy and 503 state_store_unavailable, where nothing ran. For every other error it replaces or repairs pairs as the error's action says, then throws, and leaves the retry decision to you.

import {
mteFetch,
MteRelayError,
MteRelayRequestTooLargeError,
} from "mte-relay-browser-public-client";

try {
await mteFetch("https://relay.example.com/api/upload", { method: "POST", body });
} catch (error) {
if (error instanceof MteRelayRequestTooLargeError) {
console.error(`Body is ${error.size} bytes; the relay accepts ${error.limit}.`);
} else if (error instanceof MteRelayError) {
console.error(`Relay error ${error.code} ${error.reason}, request ${error.requestId}`);
}
}

error.requestId holds the X-MTE-Relay-Request-Id the relay returned, which matches the correlationId in relay logs. See Errors and recovery for every code.

The package also exports RelayCode, Reason, relayActionFor, requiresFullRepair, classifyRelayStatus, and isRelayErrorStatus for code that inspects relay errors.

Axios compatible Implementation​

Axios is a popular network request library created before the standardization of fetch(), and many web applications still use it. To use MTE Relay with an existing code base that uses Axios, we recommend switching to Redaxios, which uses fetch() under the hood but provides the same API as Axios.

First, replace Axios with Redaxios and test that the application still works. Then provide mteFetch as its fetch function:

npm i redaxios
import redaxios from "redaxios";
import { mteFetch } from "mte-relay-browser-public-client";

const apiClient = redaxios.create({
baseURL: "https://relay.example.com",
fetch: mteFetch, // use mteFetch instead of native fetch
});

Using the engine directly​

The public client wraps the mte-relay-browser engine and exports its whole API, including initMteRelayClient, closeMteRelayClient, and the MteRelayClient class for code that needs its own client instance. initMteRelayClient and MteRelayClient take the license through their own options; the bundled license is supplied only through configureMteRelay for the shared mteFetch client. One page runs one MTE runtime. Importing the public client starts it with the bundled license, so with a different license initMteRelayClient, or init() or the first fetch() on an MteRelayClient, rejects with MTE bootstrap options conflict.