@appport/server
The server-side protocol runtime and its web bindings.
import { createServer } from "@appport/server";
import { serve } from "@appport/server/node";
const server = createServer({
application,
authenticate: ({ sessionId }) => sessions.resolve(sessionId)
});
const running = await serve({ server, port: 4400, cors: { origins: "*" } });
A complete minimal provider needs nothing else for clients to connect: serve advertises, in the manifest and so in
/.well-known/appport, the transports it actually binds (http at /appport, and websocket at /appport/ws unless you pass
websocket: false), as same-origin paths. It never advertises an endpoint it does not serve. Pass advertise: false to advertise
nothing, or transports to createApplication to advertise something else (for example a public URL behind a proxy). If an
application advertises no transport, AppPort.discover fails with INVALID_PROTOCOL and says so.
serve exposes, against one capability server:
| Route | Purpose |
|---|---|
POST /appport | requests, and streams as NDJSON |
GET /appport/events | events as Server-Sent Events |
GET /appport/manifest | the manifest |
GET /.well-known/appport | discovery |
WS /appport/ws | the WebSocket binding |
The WebSocket endpoint is a dependency-free RFC 6455 implementation: framing, fragmentation, ping/pong and close, with one AppPort envelope per message.
AppPortConnection is the duplex half used by WebSocket, IPC and in-process
transports. It owns subscription and stream bookkeeping, so unsubscribe releases
resources and cancellation reaches the handler.
For edge runtimes, createFetchHandler(binding) returns a standard
(Request) => Promise<Response>.
authenticate resolves an identity from transport-supplied credentials. AppPort
does not authenticate; it represents the resulting identity, and authorizes every
capability regardless of which transport delivered it.
Browser boundary modes
Direct browser mode
browser origin
-> @appport/transport-http
-> AppPort HTTP server with CORS/origin policy
-> capability
Configure the HTTP binding with cors and allowedOrigins when the browser talks
to the AppPort server directly.
Trusted boundary mode
browser
-> same-origin AppPort server
-> server-side AppPort client / transport
-> remote capability
The trusted boundary authenticates the caller, authorizes the capability, applies origin policy, and uses server-side credentials for any downstream AppPort call. The browser never receives those trusted credentials.
Server mode
node/server
-> @appport/transport-http
-> remote AppPort capability
In every mode, CORS stays in the HTTP boundary. It is not part of the AppPort protocol.
Public discovery (1.1.0)
createServer({ application, publication }) opts into a public description served at /appport/public and advertised from
/.well-known/appport. Absent, disabled, malformed or inconsistent policy publishes nothing. The manifest remains public by protocol, so
"not published" is not "secret". Discovery is not authorization.
The policy has this shape (PublicationPolicy in @appport/protocol); nothing is published unless public is exactly true, and only
the capabilities you name are listed:
publication: {
public: true,
applicationDescription: false, // optional: include the manifest's application description
capabilities: [{ name: "tasks.add", version: 1, purpose: true }] // purpose: include that capability's description
}
A consumer's AppPort.discoverApplication result reports notPublishedCount: every manifest capability you did not name, including the
built-in appport.manifest and appport.ping, so a provider that publishes one of three capabilities reports 4.
Authenticating with permissionAuthorizer
permissionAuthorizer() decides from the session's permissions, but only for an identified caller: an identity with no principal
is treated as unauthenticated and gets UNAUTHORIZED ("Authentication is required"), not FORBIDDEN, even if it carries permissions.
Return both from authenticate: { principal, session: { ..., principal, permissions } }. A caller that is identified but lacks a
required permission gets FORBIDDEN.