# Transport model

### How the SDK communicates with the extension

### What is a transport?
A transport is the communication layer between the SDK and the Arlopass extension. The `ArlopassTransport` interface is intentionally minimal: `request()` for single request-response operations (like listing providers), and `stream()` for streaming operations (like chat). That’s the entire surface area.

```ts title="ArlopassTransport interface"
// The ArlopassTransport interface — two methods, that's it.

export interface ArlopassTransport {
  // Send a request, get a single response
  request<TRequestPayload, TResponsePayload>(
    request: TransportRequest<TRequestPayload>,
  ): Promise<TransportResponse<TResponsePayload>>;

// Send a request, get a stream of responses
  stream<TRequestPayload, TResponsePayload>(
    request: TransportRequest<TRequestPayload>,
  ): Promise<TransportStream<TResponsePayload>>;

// Optional — called on disconnect
  disconnect?(sessionId: string): Promise<void>;
}
```

### Injected transport
The extension’s content script injects a `ArlopassTransport` implementation at `window.arlopass` before your app code executes. The React SDK’s `ArlopassProvider` detects this automatically and exposes a `transportAvailable` flag. The Web SDK accepts it as a constructor argument.

```tsx title="Transport detection"
// The extension's content script injects window.arlopass.
// This happens before your app code runs.

// React SDK — detects it automatically
import { ArlopassProvider } from "@arlopass/react";

<ArlopassProvider appId="my-app">
  {/* ArlopassProvider checks window.arlopass on mount */}
  {/* If missing → transportAvailable: false */}
  <App />
</ArlopassProvider>;

// Web SDK — you reference it explicitly
import { ArlopassClient } from "@arlopass/web-sdk";
const client = new ArlopassClient({ transport: window.arlopass });
```

### Why injected-only?
You might wonder why the SDK doesn’t let you construct your own transport — say, a WebSocket to your own backend. The reason is security. The entire Arlopass trust model depends on the extension controlling the communication channel. If arbitrary transports were allowed, any code in the page could bypass the extension’s consent flow, credential management, and rate limiting. The injected transport is the extension’s guarantee that it mediates every interaction.

### The envelope protocol
Every message — request or response — is wrapped in a `CanonicalEnvelope`. The SDK constructs these automatically; you never build one by hand. The envelope carries metadata that enables security validation, request correlation, and protocol versioning.

```ts title="Envelope structure"
// Every request/response is wrapped in a CanonicalEnvelope.
// The SDK builds this automatically — you never construct one manually.

const envelope = {
  protocolVersion: "1.0.0", // Must match — version mismatch rejects early
  requestId: "uuid-1234", // Unique per request
  correlationId: "uuid-5678", // Links request → response pairs
  origin: "https://myapp.com", // From window.location.origin
  sessionId: "uuid-abcd", // Scoped to this connect() session
  capability: "chat.stream", // What operation is requested
  issuedAt: "2025-03-26T...", // When the envelope was created
  expiresAt: "2025-03-26T...", // TTL — expired envelopes are rejected
  nonce: "random-value", // Prevents replay attacks
  payload: {
    /* request data */
  },
};
```

### Envelope validation
The extension validates every incoming envelope before processing it. This is where Arlopass’s replay resistance, expiry enforcement, and correlation checking happen. A stale envelope is rejected. A replayed nonce is rejected. A response that doesn’t match the original request’s correlation ID is rejected.

```ts title="Validation pipeline"
// The extension validates every envelope before processing:

// 1. Protocol version must match
if (envelope.protocolVersion !== SUPPORTED_VERSION) reject();

// 2. Timestamp check — is issuedAt recent?
if (Date.now() - Date.parse(envelope.issuedAt) > TTL) reject();

// 3. Expiry check — has the envelope expired?
if (Date.now() > Date.parse(envelope.expiresAt)) reject();

// 4. Nonce check — has this nonce been seen before?
if (nonceStore.has(envelope.nonce)) reject(); // replay detected
nonceStore.add(envelope.nonce);

// 5. Correlation ID — response must reference the original request
if (response.correlationId !== request.requestId) reject();
```

### Testing with mock transport
In tests, you don’t have a browser extension. The React SDK ships a `createMockTransport` utility that implements `ArlopassTransport` with configurable providers and responses. It lets you test your components against the full SDK without needing the extension installed.

```tsx title="Mock transport for tests"
// For testing, use the mock transport from the React SDK test utilities.
// It implements ArlopassTransport without needing the extension.

import { createMockTransport } from "@arlopass/react/testing";

const transport = createMockTransport({
  providers: [
    { providerId: "mock-provider", models: [{ modelId: "mock-model" }] },
  ],
});

// Use it in tests exactly like the real transport
const client = new ArlopassClient({ transport });
await client.connect({ appId: "test-app" });
```
