Address Codex audit P1.6 — connection presets + dead `autoReauthOn` removal

Two new orca presets in `arts/active-app/presets/`:
- `applyConnectionsReauthOnIdentityChange` — listens to
  `SESSION_EVENT_IDENTITY_CHANGED` and calls
  `App.connections.reauthenticateAll()`. Closes the canonical motivating
  scenario for orca: "chat connected with the previous user's
  credentials" can no longer happen with this preset wired.
- `applyConnectionsCloseOnRevoke` — listens to `SESSION_EVENT_REVOKED`
  and calls `App.connections.closeAll('session-revoked')`, leaving no
  socket alive carrying revoked credentials.

`applyStandardOrca` now picks both up automatically when `App.connections`
is declared, and the index barrel re-exports the new shapes.

Removes the dead `autoReauthOn` config — declared on
`EngineConnectionsOptions` but never read by any runtime code:
- field removed from `connection/types.ts`
- `CONNECTION_AUTO_REAUTH_*` constants removed from `connection/consts.ts`
- `ConnectionAutoReauthOn` / `ConnectionAutoReauthTarget` types removed
- unused test import removed from `connection.test.ts`
- connection README rewritten: orca preset is now the canonical bridge,
  per-connection `session: { ... }` documented as the manual / standalone
  alternative
- demo route artifact-docs.ts and aapp page updated to use
  `applyStandardOrca(App)` instead of `autoReauthOn: 'standard'`

The per-connection `session-wiring.ts` mechanism stays as-is — it's
useful for connections that live outside an App composition or that
need a custom `ConnectionSessionSource`. README now spells out the
two paths: orca preset for App-composed apps, per-connection `session`
for manual control.

Suite: 1482 / 1482 (+4 from this commit: 3 preset behaviour tests
+ 1 `applyStandardOrca` connection wiring test).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
master
dev 5 months ago
parent c0ed619372
commit 2473ade4ad

@ -0,0 +1,42 @@
import { ORCA_ON_ERROR_CONTINUE, ORCA_STAGE_MAIN, orcaError, orcaSuccess } from '$orca';
import { SESSION_EVENT_REVOKED } from '$session';
import type { ActiveConnections } from '$connection/types';
import type { ActiveAppCore } from '../types.ts';
const ACTION_ID = 'connections.close-on-revoke';
const TOKEN_CLOSED = 'connections:closed-on-revoke';
const CLOSE_REASON = 'session-revoked';
/**
* Shape this preset requires from `App`. Only `closeAll()` is needed.
*/
export interface ConnectionsCloseOnRevokeApp extends ActiveAppCore {
readonly connections: Pick<ActiveConnections, 'closeAll'>;
}
/**
* Registers an orca action that closes every active connection when
* the session is revoked. Used together with
* `applyCacheClearOnRevoke` to ensure that a logout / forced sign-out
* leaves no live socket carrying the revoked identity's credentials.
*
* Returns a detach function. Calling it unregisters the action.
*/
export function applyConnectionsCloseOnRevoke(
App: ConnectionsCloseOnRevokeApp
): () => void {
return App.Orca.onEvent(SESSION_EVENT_REVOKED, {
id: ACTION_ID,
stage: ORCA_STAGE_MAIN,
provides: [TOKEN_CLOSED],
onError: ORCA_ON_ERROR_CONTINUE,
action: async () => {
try {
App.connections.closeAll(CLOSE_REASON);
return orcaSuccess({ emits: [TOKEN_CLOSED] });
} catch (error) {
return orcaError(error);
}
}
});
}

@ -0,0 +1,47 @@
import { ORCA_ON_ERROR_CONTINUE, ORCA_STAGE_MAIN, orcaError, orcaSuccess } from '$orca';
import { SESSION_EVENT_IDENTITY_CHANGED } from '$session';
import type { ActiveConnections } from '$connection/types';
import type { ActiveAppCore } from '../types.ts';
const ACTION_ID = 'connections.reauth-on-identity-change';
const TOKEN_REAUTHENTICATED = 'connections:reauthenticated-on-identity';
/**
* Shape this preset requires from `App`. Only the
* `reauthenticateAll()` method is actually invoked, so apps can
* inject any compatible adapter — no need to expose the full
* `ActiveConnections` surface.
*/
export interface ConnectionsReauthOnIdentityChangeApp extends ActiveAppCore {
readonly connections: Pick<ActiveConnections, 'reauthenticateAll'>;
}
/**
* Registers an orca action that asks every active connection to
* reauthenticate when the session's actor identity changes. Pairs with
* `applyCacheClearOnIdentityChange` and
* `applyPermInvalidateOnIdentityChange` to flush stale state from the
* previous user before any new request flies — the canonical motivator
* scenario for orca: "chat connected with the previous user's
* credentials" can no longer happen with this preset registered.
*
* Returns a detach function. Calling it unregisters the action.
*/
export function applyConnectionsReauthOnIdentityChange(
App: ConnectionsReauthOnIdentityChangeApp
): () => void {
return App.Orca.onEvent(SESSION_EVENT_IDENTITY_CHANGED, {
id: ACTION_ID,
stage: ORCA_STAGE_MAIN,
provides: [TOKEN_REAUTHENTICATED],
onError: ORCA_ON_ERROR_CONTINUE,
action: async () => {
try {
await App.connections.reauthenticateAll();
return orcaSuccess({ emits: [TOKEN_REAUTHENTICATED] });
} catch (error) {
return orcaError(error);
}
}
});
}

@ -24,6 +24,14 @@ export {
applyCacheClearOnRevoke,
type CacheClearOnRevokeApp
} from './cache-clear-on-revoke.ts';
export {
applyConnectionsCloseOnRevoke,
type ConnectionsCloseOnRevokeApp
} from './connections-close-on-revoke.ts';
export {
applyConnectionsReauthOnIdentityChange,
type ConnectionsReauthOnIdentityChangeApp
} from './connections-reauth-on-identity-change.ts';
export {
applyPermInvalidateOnIdentityChange,
type PermInvalidateOnIdentityChangeApp

@ -1,8 +1,11 @@
import type { ActiveCache } from '$cache/types';
import type { ActiveConnections } from '$connection/types';
import type { ActivePerms } from '$perm/types';
import type { ActiveAppCore } from '../types.ts';
import { applyCacheClearOnIdentityChange } from './cache-clear-on-identity-change.ts';
import { applyCacheClearOnRevoke } from './cache-clear-on-revoke.ts';
import { applyConnectionsCloseOnRevoke } from './connections-close-on-revoke.ts';
import { applyConnectionsReauthOnIdentityChange } from './connections-reauth-on-identity-change.ts';
import { applyPermInvalidateOnIdentityChange } from './perm-invalidate-on-identity-change.ts';
/**
@ -15,6 +18,7 @@ import { applyPermInvalidateOnIdentityChange } from './perm-invalidate-on-identi
export interface StandardOrcaApp extends ActiveAppCore {
readonly cache?: Pick<ActiveCache, 'clear'>;
readonly perm?: Pick<ActivePerms, 'invalidate'>;
readonly connections?: Pick<ActiveConnections, 'reauthenticateAll' | 'closeAll'>;
}
/**
@ -37,6 +41,13 @@ export function applyStandardOrca(App: StandardOrcaApp): () => void {
const permApp = App as StandardOrcaApp & { perm: NonNullable<StandardOrcaApp['perm']> };
detachers.push(applyPermInvalidateOnIdentityChange(permApp));
}
if (App.connections !== undefined) {
const connApp = App as StandardOrcaApp & {
connections: NonNullable<StandardOrcaApp['connections']>;
};
detachers.push(applyConnectionsReauthOnIdentityChange(connApp));
detachers.push(applyConnectionsCloseOnRevoke(connApp));
}
return () => {
for (let i = detachers.length - 1; i >= 0; i--) detachers[i]();

@ -12,6 +12,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
applyCacheClearOnIdentityChange,
applyCacheClearOnRevoke,
applyConnectionsCloseOnRevoke,
applyConnectionsReauthOnIdentityChange,
applyPermInvalidateOnIdentityChange,
applyStandardOrca
} from '../presets/index.ts';
@ -24,6 +26,7 @@ import type { EngineLogger } from '$logger';
import type { EngineBus } from '$bus';
import type { ActiveTimers } from '$timer';
import type { ActiveCache } from '$cache/types';
import type { ActiveConnections } from '$connection/types';
import type { ActivePerms } from '$perm/types';
interface CoreState {
@ -164,6 +167,74 @@ describe('applyCacheClearOnRevoke', () => {
});
});
describe('applyConnectionsReauthOnIdentityChange', () => {
let core: CoreState;
beforeEach(() => {
core = buildCore();
});
afterEach(() => {
disposeCore(core);
});
it('reauthenticates connections when SESSION_EVENT_IDENTITY_CHANGED is published', async () => {
const reauthenticateAll = vi.fn(() => Promise.resolve([]));
const connections = { reauthenticateAll } as unknown as ActiveConnections;
applyConnectionsReauthOnIdentityChange({ Orca: core.orca, connections });
core.bus.publish(SESSION_EVENT_IDENTITY_CHANGED, samplePayload);
await flush();
expect(reauthenticateAll).toHaveBeenCalledTimes(1);
});
it('records action failure in run trace when reauthenticateAll() rejects', async () => {
const error = new Error('reauth failed');
const connections = {
reauthenticateAll: vi.fn(() => Promise.reject(error))
} as unknown as ActiveConnections;
applyConnectionsReauthOnIdentityChange({ Orca: core.orca, connections });
core.bus.publish(SESSION_EVENT_IDENTITY_CHANGED, samplePayload);
await flush();
const runs = core.orca.recentRuns();
expect(runs).toHaveLength(1);
expect(runs[0].actions[0].status).toBe('error');
expect(runs[0].actions[0].error).toBe(error);
});
});
describe('applyConnectionsCloseOnRevoke', () => {
let core: CoreState;
beforeEach(() => {
core = buildCore();
});
afterEach(() => {
disposeCore(core);
});
it('closes connections when SESSION_EVENT_REVOKED is published', async () => {
const closeAll = vi.fn();
const connections = { closeAll } as unknown as ActiveConnections;
applyConnectionsCloseOnRevoke({ Orca: core.orca, connections });
const revokePayload = {
...samplePayload,
event: 'session.lifecycle.revoked'
};
core.bus.publish(SESSION_EVENT_REVOKED, revokePayload);
await flush();
expect(closeAll).toHaveBeenCalledTimes(1);
expect(closeAll).toHaveBeenCalledWith('session-revoked');
});
});
describe('applyStandardOrca', () => {
let core: CoreState;
@ -189,6 +260,25 @@ describe('applyStandardOrca', () => {
expect(permInvalidate).toHaveBeenCalledTimes(1);
});
it('registers connection reauth + close when connections is present', async () => {
const reauthenticateAll = vi.fn(() => Promise.resolve([]));
const closeAll = vi.fn();
const connections = { reauthenticateAll, closeAll } as unknown as ActiveConnections;
applyStandardOrca({ Orca: core.orca, connections });
core.bus.publish(SESSION_EVENT_IDENTITY_CHANGED, samplePayload);
await flush();
expect(reauthenticateAll).toHaveBeenCalledTimes(1);
core.bus.publish(SESSION_EVENT_REVOKED, {
...samplePayload,
event: 'session.lifecycle.revoked'
});
await flush();
expect(closeAll).toHaveBeenCalledTimes(1);
});
it('skips cache reactions when cache is absent', async () => {
const permInvalidate = vi.fn();
const perm = { invalidate: permInvalidate } as unknown as ActivePerms;

@ -80,32 +80,47 @@ App inyecta:
- `App.Logger`, como `Logger` común de `$libs/logr`.
- `App.Timers`, para reconexión, heartbeat y timeouts de ack.
- `App.Bus`, para escuchar eventos públicos de aplicación cuando
`autoReauthOn` lo active.
The registry decides whether to subscribe to a session source. Wire one
explicitly when needed:
### Reacción a cambios de identidad
El patrón canónico para reaccionar a cambios de sesión vive en los
**presets de `arts/active-app`**, no dentro del registry de
conexiones:
```ts
import { applyStandardOrca } from '$active-app/presets';
const App = createActiveApp({
services: {
connections: defineActiveConnections({
autoReauthOn: 'standard'
}),
session: defineActiveSession({ ... })
connections: defineActiveConnections({}),
session: defineActiveSession({ ... }),
cache: defineActiveCache({ ... }),
perm: defineActivePerms({ ... })
}
});
applyStandardOrca(App);
// → registra applyConnectionsReauthOnIdentityChange y
// applyConnectionsCloseOnRevoke entre otros, todos vía orca.
```
`standard` listens to `SESSION_EVENT_IDENTITY_CHANGED`. Each connection
still decides whether to react via its own `session` option.
`applyConnectionsReauthOnIdentityChange` escucha
`SESSION_EVENT_IDENTITY_CHANGED` y llama
`App.connections.reauthenticateAll()`. `applyConnectionsCloseOnRevoke`
escucha `SESSION_EVENT_REVOKED` y llama `App.connections.closeAll()`.
Apps que prefieran granularidad pueden llamar a los presets
individuales en lugar del agregador.
Cada conexión decide si usa la sesión:
### Sesión per-connection (modo manual)
Para casos donde una conexión concreta tiene un `ConnectionSessionSource`
propio (ajeno al `App.session` global), o para usos standalone sin
orca, cada conexión sigue aceptando `session` en sus opciones:
```ts
const Main = Connections.createConnection('main', {
transport: createWebSocketTransport({ url: '/realtime' }),
auth: () => ({ token: App.Sess?.current?.credential }),
auth: () => ({ token: App.session?.current?.credential }),
session: {
enabled: true,
reauthOnRefresh: true,
@ -114,15 +129,17 @@ const Main = Connections.createConnection('main', {
});
```
Sin `autoReauthOn`, `conn` no reacciona automáticamente a cambios de identidad
aunque exista `App.Bus`. Sin `session.enabled`, una conexión concreta tampoco
se reautentica ni se desconecta por eventos de identidad.
Si la conexión declara `session.enabled`, su lógica interna
(`session-wiring.ts`) escucha el `onChange` del source y reacciona
con `reauthenticate()` / `disconnect()`. La reautenticación necesita
`auth`: define qué credencial nueva se envía cuando el source dice
"identidad cambió". Sin `auth` no se puede emitir un frame de reauth
y debe resolverse con reconnect/disconnect manual.
La reautenticación necesita también `auth`. `autoReauthOn` y `session.enabled`
solo dicen cuándo reaccionar; `auth` dice qué credencial nueva se envía. Si la
conexión no define `auth`, un cambio de usuario no puede producir un frame de
reauth y debe resolverse con reconnect/disconnect manual o con una política de
sesión que cierre la conexión.
Las dos vías (preset orca a nivel App y `session` per-connection)
coexisten. La regla práctica: usa el preset cuando uses `arts/orca` y
`arts/session`; usa `session` per-connection para casos standalone o
cuando la conexión vive fuera del ciclo App.
Cuando `Timers` no se inyecta, `createEngineConnections()` crea un scheduler
privado con el mismo logger. Los diagnósticos del scheduler salen bajo la
@ -333,26 +350,29 @@ El resultado de auth es tagged:
await Main.reauthenticate(); // { ok: true } | { ok: false, reason, error? }
```
With `autoReauthOn` on the registry, `session.enabled` on the connection
and `auth` defined, the session source bound to the App's bus can:
- reauthenticate when `session` publishes `SESSION_EVENT_IDENTITY_CHANGED`
on adoption, refresh, or external change;
- disconnect when that event represents expiration / revocation and
`disconnectOnExpire !== false`.
The full flow is:
The full flow with the orca preset (`applyStandardOrca` or
`applyConnectionsReauthOnIdentityChange` /
`applyConnectionsCloseOnRevoke`) is:
```txt
session -> SESSION_EVENT_IDENTITY_CHANGED
connection -> reauth/disconnect when autoReauthOn + connection.session +
connection.auth allow it
session -> SESSION_EVENT_IDENTITY_CHANGED on App.Bus
orca -> connections-reauth-on-identity action runs
-> App.connections.reauthenticateAll()
-> each connection calls auth() with the new credential
session -> SESSION_EVENT_REVOKED on App.Bus
orca -> connections-close-on-revoke action runs
-> App.connections.closeAll('session-revoked')
```
`connection` does not subscribe to `session.*` directly: the wiring is the
session source the registry creates from `App.Bus`. If you want different
behaviour, register a custom orca action or call
`Connections.reauthenticateAll()` / `closeAll()` explicitly.
The connection art does not subscribe to `session.*` events directly:
the orca preset is the canonical bridge. For standalone setups (a
connection that lives outside an App composition or that needs a
custom session source), each connection still accepts
`session: { enabled, reauthOnRefresh, disconnectOnExpire, ... }` and
its internal `session-wiring` listens to the source's `onChange`.
Both routes coexist: pick the orca preset when using `arts/orca` +
`arts/session`; pick the per-connection `session` option for manual
control.
`conn` no crea sesiones ni decide permisos. En servidor, los joins/sends de un
canal deben validarse con `auth/sess/perm`.

@ -142,10 +142,6 @@ export const CONNECTION_METHOD_REAUTHENTICATE = 'reauthenticate';
export const CONNECTION_METHOD_JOIN = 'join';
export const CONNECTION_METHOD_LEAVE = 'leave';
export const CONNECTION_AUTO_REAUTH_NONE = 'none';
export const CONNECTION_AUTO_REAUTH_STANDARD = 'standard';
export const CONNECTION_AUTO_REAUTH_USER_IDENTITY_CHANGE = 'userIdentityChange';
export const CONNECTION_FRAME_TYPE_AUTH = 'connection.auth';
export const CONNECTION_FRAME_TYPE_JOIN = 'connection.join';
export const CONNECTION_FRAME_TYPE_LEAVE = 'connection.leave';

@ -11,7 +11,6 @@ import {
CONNECTION_STATE_FAILED,
CONNECTION_STATE_OPEN,
CONNECTION_STATE_RECONNECTING,
CONNECTION_AUTO_REAUTH_STANDARD,
CONNECTION_SESSION_EVENT_EXPIRED,
CONNECTION_SESSION_EVENT_REVOKED,
createEngineConnections,

@ -27,9 +27,6 @@ import type {
CONNECTION_CONNECT_REASON_DISPOSED,
CONNECTION_CONNECT_REASON_RECONNECT_EXHAUSTED,
CONNECTION_CONNECT_REASON_TRANSPORT_ERROR,
CONNECTION_AUTO_REAUTH_NONE,
CONNECTION_AUTO_REAUTH_STANDARD,
CONNECTION_AUTO_REAUTH_USER_IDENTITY_CHANGE,
CONNECTION_SEND_REASON_BUFFER_FULL,
CONNECTION_SEND_REASON_CLOSED,
CONNECTION_SEND_REASON_INVALID_FRAME,
@ -365,19 +362,11 @@ export interface EngineConnectionsOptions {
*/
readonly timers: TimerScheduler;
readonly defaults?: Partial<ConnectionOptions>;
readonly autoReauthOn?: ConnectionAutoReauthOn;
readonly session?: ConnectionSessionSource;
}
export type ActiveConnectionsOptions = EngineConnectionsOptions;
export type ConnectionAutoReauthTarget = typeof CONNECTION_AUTO_REAUTH_USER_IDENTITY_CHANGE;
export type ConnectionAutoReauthOn =
| typeof CONNECTION_AUTO_REAUTH_NONE
| typeof CONNECTION_AUTO_REAUTH_STANDARD
| readonly ConnectionAutoReauthTarget[];
export interface ConnectionSessionChange {
readonly event: string;
readonly current?: unknown | null;

@ -1284,15 +1284,10 @@ const artifactApis = {
purpose: 'Shared runtime dependencies.',
notes: 'App injects Logger and Timers automatically.'
},
{
name: 'autoReauthOn',
purpose: 'Reauth policy on session-source events.',
notes: 'Defaults to none. "standard" listens to identity-change events from the supplied ConnectionSessionSource.'
},
{
name: 'session',
purpose: 'ConnectionSessionSource (abstract { onChange } interface).',
notes: 'Application code builds it from App.session and passes it through; the connection art does not couple to App.Bus directly.'
purpose: 'ConnectionSessionSource (abstract { onChange } interface) for advanced/manual wiring.',
notes: 'Optional. The canonical pattern wires reauth/close as orca actions via applyConnectionsReauthOnIdentityChange / applyConnectionsCloseOnRevoke (or applyStandardOrca) — this option is for standalone / per-connection setups outside the App composition.'
}
]
},
@ -1891,7 +1886,7 @@ const App = createActiveApp({
cache: defineActiveCache({}),
perm: defineActivePerm({ endpoint: '/api/perm' }),
session: defineActiveSession<User, ChatCredential>({ onRefresh, onRevoke }),
connections: defineActiveConnections({ autoReauthOn: 'standard' })
connections: defineActiveConnections({})
}
});
@ -1902,12 +1897,6 @@ const Chat = App.connections.createConnection('chat', {
auth: () => {
const credential = App.session?.current?.credential;
return credential ? { accessToken: credential.accessToken } : null;
},
session: {
enabled: true,
reauthOnChange: true,
reauthOnRefresh: true,
disconnectOnExpire: true
}
});
@ -1915,13 +1904,14 @@ const Chat = App.connections.createConnection('chat', {
App.session.adoptServer(nextSessionFromServer);
// 1. App.session updates state, then publishes SESSION_EVENT_IDENTITY_CHANGED on App.Bus.
// 2. Orca runs the registered actions:
// - cache-clear-on-identity action calls App.cache.clear()
// - perm-invalidate-on-identity action calls App.perm.invalidate()
// 3. Chat sees the change via its ConnectionSessionSource (built from App.session)
// and calls auth(), sending a fresh credential frame because the registry's
// autoReauthOn and the connection's session/auth options all opted in.
// 4. If the session expires or is revoked, Chat disconnects instead.`
// 2. Orca runs the registered actions in a single trace:
// - cache-clear-on-identity -> App.cache.clear()
// - perm-invalidate-on-identity -> App.perm.invalidate()
// - connections-reauth-on-identity -> App.connections.reauthenticateAll()
// which calls each connection's auth() with the fresh credential.
// 3. If the session is revoked, the same orca pipeline runs
// cache-clear-on-revoke and connections-close-on-revoke, leaving no
// socket alive carrying the revoked credentials.`
}
}
],
@ -3800,13 +3790,13 @@ Timers.cancelScope('profile');`
overview: [
'Connections is a registry of named realtime connections. A connection is not the engine: EngineConnections owns all connection state; each Connection owns transport, channels, heartbeat, reconnect and request/reply.',
'The transport contract is pluggable. Browser WebSocket is one transport; tests and demos can use mock transports without changing the connection runtime.',
'When composed through App via defineActiveConnections, the registry receives Timers and Logger from the core. Identity tracking is decoupled: the connection consumes a ConnectionSessionSource (an abstract { onChange } interface) — typically built from App.session — and the autoReauthOn option declares which source events trigger reauth.'
'When composed through App via defineActiveConnections, the registry receives Timers and Logger from the core. Identity tracking is decoupled: the canonical wiring uses orca presets (applyConnectionsReauthOnIdentityChange / applyConnectionsCloseOnRevoke) to bridge App.session events to the registry. Per-connection ConnectionSessionSource remains available for standalone or manual setups.'
],
dynamics: [
'Create one registry, then create named connections inside it. The registry tracks all names and aggregate state; each connection owns its transport lifecycle, channel collection, send buffer, heartbeat timers and reconnect strategy.',
'Transports emit open/message/close/failure signals. The connection translates those into framework states, schedules heartbeat and reconnect through Timers, and routes logs through the shared Logger/diagnostic constants.',
'Channels are scoped streams over a connection. They can join, leave, send and request. After reconnect, auto-join channels rejoin so feature code does not rebuild subscriptions manually.',
'Identity reauth is opt-in at three levels: the registry must enable autoReauthOn, each connection must enable its own session option, and the connection must provide auth. Without those, identity events are observable but inert.'
'Identity reauth is wired through the orca presets in $active-app/presets — applyStandardOrca(App) registers the canonical reactions (cache clear, perm invalidate, connections reauth on identity change, connections close on revoke). Each connection still needs its own auth() callback to produce a credential frame. Per-connection session options remain available for standalone or manual setups outside the App composition.'
],
commonMistakes: [
{
@ -3828,7 +3818,7 @@ Timers.cancelScope('profile');`
{
name: 'forgetting app-event reauth behavior',
purpose: 'Connections can keep old identity after login/logout/refresh.',
notes: 'Enable registry autoReauthOn, each connection session option and connection auth, or reconnect/disconnect manually.'
notes: 'Wire the orca preset (applyStandardOrca or applyConnectionsReauthOnIdentityChange / applyConnectionsCloseOnRevoke) so identity changes flow through reauthenticateAll/closeAll. For standalone connections without orca, use the per-connection session option.'
}
],
quickStart: {
@ -3836,10 +3826,12 @@ Timers.cancelScope('profile');`
code: `const App = createActiveApp({
services: {
session: defineActiveSession({ ... }),
connections: defineActiveConnections({ autoReauthOn: 'standard' })
connections: defineActiveConnections({})
}
});
applyStandardOrca(App); // wires reauth-on-identity / close-on-revoke
const Updates = App.connections.createConnection('updates', {
transport: createWebSocketTransport({ url: '/ws' }),
auth: () => {
@ -3847,12 +3839,7 @@ const Updates = App.connections.createConnection('updates', {
return credential ? { accessToken: credential.accessToken } : null;
},
heartbeat: { enabled: true },
reconnect: { enabled: true },
session: {
enabled: true,
reauthOnChange: true,
disconnectOnExpire: true
}
reconnect: { enabled: true }
});
await Updates.connect();
@ -3945,8 +3932,8 @@ await Chat.connect();`
{
title: 'Session-aware reauth',
body: [
'Connections consume an abstract ConnectionSessionSource (a { onChange } interface). The application builds the source from App.session and passes it through defineActiveConnections({ session: ... }) — the connection art does not import session or bus internals.',
'When autoReauthOn is enabled at the registry level, identity-change events from the source trigger a reauth attempt on each connection that has session.enabled and an auth provider. Without auth there is no credential payload to send, so the connection can only be observed or manually reconnected.'
'The canonical pattern wires reauth through orca: applyStandardOrca(App) (or the individual applyConnectionsReauthOnIdentityChange / applyConnectionsCloseOnRevoke) calls reauthenticateAll() / closeAll() on App.connections when session events fire. Each connection still needs its own auth() callback to produce the credential frame.',
'For standalone or per-connection setups outside the App composition, the registry / connection still accepts ConnectionSessionSource ({ onChange }) via the session option. The internal session-wiring listens to onChange and reacts directly. This is the manual / advanced path.'
]
},
{

@ -29,9 +29,7 @@ App.lang.setLocale('es-MX'); // notifies Format and Frontend via the locale sour
const App = createActiveApp({
services: {
sium: defineEngineSium({}), // validation engine
connections: defineActiveConnections({ // realtime registry
autoReauthOn: 'standard'
}),
connections: defineActiveConnections({}), // realtime registry
auth: defineActiveAuth({ initial: data.auth }),
perm: defineActivePerm({ endpoint: '/api/perm' }),
session: defineActiveSession({

Loading…
Cancel
Save

Powered by TurnKey Linux.