Releases: OmnyGrid/omnyhub
Release list
v1.5.1
Patch release. Dependency-only change; no API changes.
Changed
multi_domain_secure_server: ^1.0.17(from^1.0.16) — improves error handling and logging in_acceptandextractSNIHostname, the path every TLS handshake takes before aSecurityContextis chosen, so an SNI read that fails now reports why instead of failing silently.meta: ^1.19.0(from^1.16.0).
The suite passes unmodified against these versions.
Full Changelog: v1.5.0...v1.5.1
v1.5.0
Added
-
LetsEncryptTls.renewBefore. How much validity a certificate must have
left to be kept; below it,maybeRenewrenews. Defaults to 5 days — the
previous, hard-codedshelf_letsencryptbehaviour — so nothing changes unless
you set it.5 days is a thin margin: a certificate is renewed at most one
OmnyHub.tlsRenewalInterval(12h by default) after dropping below the
threshold, and a failed renewal then has very little room to retry before the
certificate actually expires. Let's Encrypt's own advice is to renew with
roughly a third of the lifetime left (30 of 90 days). Applications that were
previously enforcing their own margin — refusing to serve a near-expiry
certificate, and reissuing — can now express it here instead:LetsEncryptTls.onDemand( allowDomain: ..., cacheDir: '/etc/letsencrypt/live', renewBefore: const Duration(days: 15), );
A certificate that is still valid keeps being served while it renews in the
background; only an expired one is withheld (isHandledDomainCertificate
already rejects an expired, corrupt or unloadable certificate before any
SecurityContextis built, so an expired certificate is never served).
v1.4.0
On-demand TLS release — the seams an application needs to decide its own way
whether a host may be certified, rather than reimplementing the SNI cache and
issuance around LetsEncryptTls. Driven by adopting omnyhub in MenuIci's
sites_server, whose front door had grown a parallel copy of both.
Additive and backward-compatible: DomainPolicy is widened, so existing
synchronous policies still satisfy it, and autoIssue defaults to the 1.3.0
behaviour. Verified by running the full suite unmodified against this release.
Added
- Asynchronous domain policy.
DomainPolicyis now
FutureOr<bool> Function(String host)(wasbool Function(String host)), so
on-demand issuance can be gated by a real lookup — a database query, a tenant
API call — instead of only what is knowable synchronously. Previously an
application whose "may this host be certified?" answer lived behind I/O had to
keep a hand-rolled cache beside the hub and pre-warm it, which is exactly the
duplicationallowDomainexists to remove. Existing sync policies are
unaffected:boolis a subtype ofFutureOr<bool>. LetsEncryptTls.autoIssue. Whenfalse, only certificates already cached
incacheDirare served and the CA is never contacted — neither on a
handshake miss nor from the renewal loop. For a deployment whose certificates
are provisioned out-of-band (certbot, a secrets mount, a sibling process),
which previously could not be expressed through this provider at all.
Changed
LetsEncryptTls.isAllowedreturnsFuture<bool>. It awaits the policy.
The synchronous SNI path (TlsProvider.contextFor, which cannot await) no
longer calls it:contextFornow schedulesobtain()in the background, and
obtain()is the single place the policy is evaluated. The only breaking
change, and only for code callingisAlloweddirectly.- A rejected host is no longer re-checked on every handshake. With a sync
policy the pre-check incontextForwas free; with an async one it could be
an HTTP call per TLS handshake. Rejections are now remembered in a bounded
cache (capped, so an SNI flood of random hostnames cannot grow it without
limit). obtain()stays de-duplicated under an async policy. It registers its
in-flight future before the first suspension, so concurrent handshakes for
the same host still share one provisioning attempt now that the allow-check
can itself suspend.
v1.3.0
Control-plane release — the seams an application needs to host its own node protocol on NodeGateway/NodeRuntime, rather than reimplementing the registry, heartbeat watchdog and RPC correlation around them.
Additive and backward-compatible: every new wire field is omitted when empty, every new hook defaults to null, and every new option defaults to the 1.2.0 behaviour. Verified by running the omnydrive (260 tests) and omnyshell (808 tests) suites unmodified against this release.
Added
Heartbeat.payload+NodeGateway.onHeartbeat+NodeConfig.heartbeatPayload— telemetry rides the beat instead of needing a second periodic message. A payload builder that throws or stalls never costs the node its liveness.NodeNotify+NodeRuntime.notify/NodeGateway.notify/onNotify— one-way push. Previously a node could only push by callingrequest()and discarding a response it did not want.- Connection lifecycle hooks —
NodeGateway.onConnect/onDisconnect/onTimeout, andNodeEvent.node. The registry's events carry a bare descriptor and never fire at all for a socket that connects without registering. NodeGateway.retainNodes— keep a disconnected or timed-out node, marked offline, for a hub that is the system of record for a known fleet. Backed byNodeRegistry.markOfflineandNodeEventKind.disconnected.RegisteredNode.state— per-node application state. The registry constructsRegisteredNodeitself, so subclassing it to add fields never worked.NodeConfig.connect(injectable transport),pingInterval,isTerminal(a revoked key is not fixed by retrying),descriptorBuilder(re-advertise on reconnect when the node has changed).AppException,WsCloseCodes.forException,TypedConnection.onDecodeError,LoggerBase.
Fixed
- Frames sent during registration were silently dropped.
NodeGatewayranonRegisterwithout awaiting it, so anything arriving while an async handler was in flight raced an unset registration and was discarded (Heartbeat,NodeUpdate) or refused asNot registered(NodeRequest). Frames are now queued and replayed in order, and discarded if registration is rejected. - A stray binary frame could take down the gateway.
MessageCodec.decodeUTF-8-decoded aBinaryMessageoutside its guard, so arbitrary bytes escaped as a rawFormatException— pastNodeGateway'sHubException-only catch — and surfaced as an uncaught async error. - An unavailable node was reported to the peer as an auth failure. Every close-code status outside 401/403/404 fell through to
unauthorized; 502/503/504 now map tobadGateway.
Changed
MessageCodec's docs claimed third parties could register new message types. They cannot — NodeControlMessage is sealed. Application protocols ride on NodeRequest/NodeResponse and NodeNotify.
278 tests (up from 246). Full notes in CHANGELOG.md.
v1.2.0
Control-plane release — the node protocol grows the pieces an application needs to run a real HUB/Node infrastructure on it (enrolment, node-initiated calls, domain-specific discovery), instead of only the "worker node" shape where the hub calls the node and the node merely heartbeats.
Fully backward-compatible (additive): every new wire field is omitted when empty and every new hook defaults to null, so existing peers and callers are unaffected.
Added
- Bidirectional RPC.
request/responsenow flow in both directions. A node calls the hub withNodeRuntime.request(action, payload:), answered by the hub's newNodeGateway.onRequest— which receives the callingRegisteredNode, so the hub knows who is asking and can authorize on itsprincipal. Registration is a precondition: a request from a connection that has not registered is rejected ("Not registered") without reaching the handler. The hub→node direction (NodeGateway.request) is unchanged. Previously the only node-initiated message wasquery, so an application needed a second channel back to the hub. - Enrolment.
NodeRegisterandNodeRegisteredcarry aMap<String, dynamic> payload, andNodeGateway.onRegistervets registrations — returning the ack payload, or throwing aHubExceptionto reject (the hub replieserror, closes, and never registers the node). Node-side:NodeConfig.registerPayload/NodeConfig.onRegisteredandNodeRuntime.registration. This is the seam for CA-style enrolment: submit a CSR, get a signed certificate back. - Node-side in-band handshake.
NodeConfig.onHandshakeruns on the raw connection before registering — the counterpart of the hub's existingConnectionAuthenticator, which had no node-side half — so a node can now answer a challenge/response or key-agreement exchange. Unconsumed frames are replayed to the control protocol. - Application-defined discovery.
NodeDescriptor.attributes(Map<String, dynamic>, may nest — unlike the flat string-onlylabelsandmetadata),NodeQuery.filter, and theNodeMatcherport, so an application owns query semantics the hub cannot know about (version ranges, nested service catalogues).NodeRegistry.discovertakes awherepredicate. NodeUpdate(t: "update") — a node revises its advertised descriptor without re-registering.Json.optObjectfor reading free-form JSON object fields.
Changed
NodeGateway.codecandNodeRuntime.codecare now typedConnectionCodec<NodeControlMessage>rather than the concreteMessageCodec, so an application can supply its own wire format (e.g. binary) without forking either endpoint.MessageCodec.standard()remains the default; source-compatible for existing callers.
Fixed
- In-flight RPCs no longer hang when a connection drops.
NodeGatewayandNodeRuntimenow fail their pending calls withNodeUnavailableExceptionon disconnect, goodbye and heartbeat timeout, instead of leaving callers to wait out their own timeout.NodeRuntime's pending discovery queries were leaked on disconnect and are now cleared too.
Full Changelog: v1.1.0...v1.2.0
v1.1.0
Synergy release — shared primitives for protocol-oriented apps + seamless TLS renewal. Backward-compatible.
ConnectionCodec<T>+TypedConnection<T>— a first-class "codec over a raw duplex connection" primitive. NodeMessageCodecis now aConnectionCodec<NodeControlMessage>;NodeRuntimeconsumes aTypedConnection.- Gap-free TLS rebind —
HttpTransport.rebind()binds a freshsharedlistener on the same port with the renewed cert and drains the old gracefully; live connections survive certificate renewal. ReloadableFileTlsnow detects changes by byte content (catches same-size rotations; keeps the old cert on a partial write).
Published to pub.dev/packages/omnyhub 1.1.0. See the CHANGELOG.
v1.0.0
First stable release — published to pub.dev/packages/omnyhub.
OmnyHub is a reusable, protocol-agnostic HUB framework for building distributed HUB/Node infrastructures over HTTP/HTTPS/WS/WSS behind one architecture and API.
Main features
- Multi-service hosting on one instance/port, with dynamic register/unregister.
- Protocol-agnostic transport — HTTP/HTTPS/WS/WSS on one listener; protocol code isolated from business logic.
- Advanced routing — host/domain/subdomain (exact,
*.wildcard, or regexp), path prefix, path parameters (RouterService/ShelfService), protocol, headers, method, auth-state; composable + custom routers. - Reverse proxy & gateway — HTTP + WebSocket-upgrade forwarding,
X-Forwarded-*, hybrid local/remote. - Automatic TLS — static, hot-reload file, and Let's Encrypt (ACME) with dynamic on-demand multi-domain issuance via SNI.
- Layered authentication — global
AuthCoordinator(authenticate/bypass/delegate/block) + per-service handlers + in-bandConnectionAuthenticator; Bearer/Basic/composite + role/predicate authorizers. - Node infrastructure — control protocol + codec, registry, discovery, heartbeat, gateway, and a node runtime with RPC and reconnection.
- Demo CLI, examples, and design docs.
- Tested with unit/integration/e2e over real servers and sockets (no mocking).
See the CHANGELOG.
v0.3.0
Migration-readiness features so omnydrive and omnyshell can adopt OmnyHub for HUB/Node/transport. Fully backward-compatible; 213 tests.
Path-parameter routing
PathPattern(<endpoint>, wildcard tail<path|.*>) +RouterService— intra-service routing with captured params and method dispatch (405/404).ShelfService— host an existingshelf.Handler/shelf_router.Routerverbatim.HostPatternRule(RegExp, {part})— regexp routing over host / domain / subdomain, combinable with aPathRulevia&.
Layered authentication framework
AuthCoordinator→ sealedAuthDecision:Authenticated/Anonymous(bypass) /Delegate(to per-service) /Blocked(pre-check).- Per-service
authenticator/authorizeronregisterService/route; newTooManyRequestsException(429).DefaultAuthCoordinatorkeeps the no-config path identical to before. ConnectionAuthenticator+ aHandshakeConnectionbuffered wrapper for in-band WebSocket handshakes (single-subscription-safe hand-off to the service).
Conveniences
ReloadableFileTls— hot-reload certificate/key files on change (cert-manager/certbot friendly).mapErrorsmiddleware +successEnvelope/errorEnvelopehelpers.NodeRegistryextras:activeSessions,connectionId,byConnectionId,updateActiveSessions.
New examples: path_params_example.dart, layered_auth_example.dart.
See the CHANGELOG for the full list.
v0.1.0
Initial release of OmnyHub — a reusable, protocol-agnostic HUB framework in pure Dart over HTTP/HTTPS/WS/WSS.
Highlights
- Multi-service hosting on a single port with dynamic register/unregister.
- Advanced routing — host/domain/subdomain/path/protocol/header/method/auth-state rules with
and/or/not+ predicate, and a pluggableRouter. - Reverse proxy & gateway — streaming HTTP + WebSocket-upgrade forwarding,
X-Forwarded-*, hop-by-hop stripping; host/path gateways + hybrid. - Automatic TLS —
StaticTlsandLetsEncryptTls(ACME) behind aTlsProviderport, with challenge auto-mount, provision-before-bind and renewal hot-reload. - Node infrastructure — control protocol + extensible codec, registry, discovery, heartbeat monitoring, gateway, and a node runtime with RPC, peer discovery and reconnection/backoff.
- Fail-closed auth — Bearer/Basic/composite authenticators and role/predicate authorizers.
- Demo CLI, runnable examples, and unit/integration/e2e tests over real servers and sockets (no mocking library).
See the CHANGELOG for the full list.