RemoteContentPack<T> class
A typed, signed remote content slot — live-ops content (a level
definition, a shop catalog page, an event schedule) pushed from a
server without an app-store review cycle, built on the same 3 pieces
RemoteConfigService, VersionedJsonStore and save_integrity.dart
already provide separately (IDEA-37): asset-fallback-then-fetch,
schema-version migration, and HMAC signing.
load always resolves from the bundled asset first — never network- gated — so current has something usable the instant load returns. If fetchRemote is given, a fetch is kicked off in the background and current is swapped in-place once it resolves; callers that care about that exact moment (mostly tests) can await refreshed.
A fetched envelope is rejected — silently, keeping whatever current
already was — instead of applied when: the signature doesn't verify
(or contentSecret wasn't supplied at all, in which case an envelope
carrying a signature can never be trusted since there's no key
configured to check it against), its schemaVersion is newer than
this pack's own (a downgraded app being handed a shape it can't read),
or fromJson throws on it (a right-shaped-but-wrong-typed field).
Never crashes and never applies unverified content.
Constructors
-
RemoteContentPack({required String assetPath, required int schemaVersion, required T fromJson(Map<
String, Object?> json), Map<String, Object?> migrate(int fromVersion, Map<String, Object?> json)?, String? contentSecret, Future<Map< fetchRemote()?, AssetBundle? bundle})String, Object?> > - Legacy/source-compatible constructor — cache disabled, byte-for-byte the same public signature and behavior as before ENH-94. Use RemoteContentPack.withCache to opt into persistence.
-
RemoteContentPack.withCache({required String assetPath, required int schemaVersion, required T fromJson(Map<
String, Object?> json), required StorageService? storage, required String? cacheKey, Map<String, Object?> migrate(int fromVersion, Map<String, Object?> json)?, String? contentSecret, Future<Map< fetchRemote()?, Duration? maxCacheAge, AssetBundle? bundle})String, Object?> > - ENH-94: same signed/typed content pack with a durable verified cache. Kept as a NAMED constructor (instead of changing RemoteContentPack's existing signature by adding optional params) so every pre-existing consumer remains 100% API-gate compatible — additive only, no major version bump/BREAKING changelog required.
-
RemoteContentPack.withHistory({required String assetPath, required int schemaVersion, required T fromJson(Map<
String, Object?> json), required StorageService storage, required String cacheKey, required String contentSecret, Map<String, Object?> migrate(int fromVersion, Map<String, Object?> json)?, Future<Map< fetchRemote()?, Duration? maxCacheAge, int? historyCapacity = 5, int contentVersionResolver(Map<String, Object?> >String, Object?> json)?, AssetBundle? bundle}) - ENH-96: adds a bounded, persisted history of previously-applied VERIFIED revisions on top of withCache's single-slot cache, plus downgrade rejection and explicit rollbackToChecksum/ rollbackToVersion — a consumer that doesn't need rollback/audit history keeps using withCache unchanged; this is a strict superset, additive new constructor (same "new named constructor, never change an existing one" convention withCache's own doc comment explains).
Properties
- assetPath → String
-
final
- cacheKey → String?
-
Storage key the cache is written/read under. Required whenever
storage is provided — see the constructor's
ArgumentError. Not a named StorageKeys constant (unlike most storage keys in this repo) because a caller may construct several packs (different content types/asset paths) that each need their own cache slot; the caller picks a unique key the same waySaveSlotManager/CheckpointCoordinatorlet callers supply their own scoped key.final - contentSecret → String?
-
Shared secret used to verify a fetched envelope's
_checksum(seesave_integrity.dart).nullmeans "don't trust any remote content for this pack" — a fetched envelope is rejected outright regardless of what it contains, since there'd be no key to check its signature against.final -
contentVersionResolver
→ int Function(Map<
String, Object?> json)? -
ENH-96 (
withHistoryonly): resolves a fetched envelope's content revision number — defaults to_defaultContentVersionResolver(contentVersion, thenversion, then0). Used to reject a downgrade (an incoming revision numbered lower than currentContentVersion) without touching_current/the cache/ history at all.final - current → T?
-
The latest applied content: the asset fallback until (if ever) a
verified fetch replaces it.
nullonly if the asset itself was missing/invalid AND no fetch has applied anything yet.no setter - currentChecksum → String?
-
The checksum of whatever current holds right now —
nullbefore any verified fetch has ever been applied, or for a constructor that doesn't track history.no setter - currentContentVersion → int
-
The content revision number of whatever current holds right now —
0before any verified fetch has ever been applied (withHistoryonly; always0for the other 2 constructors).no setter -
fetchRemote
→ Future<
Map< Function()?String, Object?> > -
final
-
fromJson
→ T Function(Map<
String, Object?> json) -
final
- hashCode → int
-
The hash code for this object.
no setterinherited
-
history
→ List<
ContentPackHistoryEntry> -
Every verified revision currently retained, oldest first, capped at
historyCapacity — metadata only (see ContentPackHistoryEntry's
doc), never the content body. Empty for RemoteContentPack/
RemoteContentPack.withCache.
no setter
- historyCapacity → int?
-
ENH-96 (
withHistoryonly): max verified revisions kept in history, oldest evicted first once exceeded.nullfor RemoteContentPack/ RemoteContentPack.withCache — those constructors keep no history at all.final - maxCacheAge → Duration?
-
Optional TTL: a cache entry older than this (by nowMsClamped) is
treated as invalid and load falls back to the bundled asset instead.
null(default) means the cache never expires by age — only rejected for being tampered/malformed/a future schema version.final -
migrate
→ Map<
String, Object?> Function(int fromVersion, Map<String, Object?> json)? -
Upgrades a JSON map saved under an older
fromVersionto a shape fromJson can read. Defaults to identity (no-op) — a pack that never changes shape doesn't need to supply one.final -
refreshed
→ Future<
void> -
Resolves once the background fetch kicked off by the most recent
load call has settled (applied or rejected).
nullif no fetchRemote was ever supplied. Production callers don't need this — current updates in place — it exists for tests that need to wait deterministically past the background refresh.no setter - runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
- schemaVersion → int
-
final
- storage → StorageService?
-
ENH-94: durable cache seam.
null(default) disables the cache entirely — load/_refreshFromRemotebehave byte-for-byte as before this feature existed.final
Methods
-
diagnosticsSummary(
) → Map< String, Object?> -
ENH-96 (
withHistoryonly): metadata-only snapshot safe to paste into a support ticket / log line /DiagnosticsExportBundlesection — NEVER the content body (a level layout, a shop catalog, whateverTrepresents), matching this package's existing default-deny diagnostics convention (DiagnosticsExportBundle.build'sconfigAllowedKeys). -
load(
) → Future< T?> -
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
rollbackToChecksum(
String checksum) → Future< SdkResult< T> > -
ENH-96 (
withHistoryonly): re-activates the verified revision whose checksum ischecksum— re-verifies it was indeed in history (never trusts an caller-supplied checksum blindly), persists it as the current cache entry, appends a fresh history entry recording WHEN the rollback itself happened (so the audit trail shows the rollback as its own event, not a silent rewrite of the original entry's timestamp), and only then swaps_current. Returns SdkFailure (no mutation at all) if no history entry matches. -
rollbackToVersion(
int contentVersion) → Future< SdkResult< T> > -
ENH-96 (
withHistoryonly): re-activatescontentVersion— if more than one history entry shares that version number (same version re-applied more than once), the most-recently-applied one wins, same "latest wins" convention currentContentVersion itself uses. Returns SdkFailure (no mutation) if no history entry matches. -
toString(
) → String -
A string representation of this object.
inherited
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited