Repository navigation
Releases: interop-alliance/pws-react
Release list
v0.32.0
Changed
- Breaking: a grant's Space is read off its delegation chain's root
(was-client'sspaceUrlOf), and itsinvocationTargetis classified beneath
that Space URL.ParsedGrantscarriesspaceUrlin place ofbaseUrland
spaceId; the two "grants span two servers / two spaces" checks collapse into
one on the Space URL.parseInvocationTarget(target)is replaced by
parseGrantTarget(zcap), which returns{ spaceUrl, collectionId }. The
Space URL the root names is taken as given, in any form (a single fixed Space
may live at any URL). A grant whose chain starts at no Space root capability
is refused, as this module's own grant error with was-client's
ValidationErrorascause. AudienceReader.replaceCapabilityrefuses a zcap whose chain roots in another
Space, and changes nothing on the reader until every check has passed.- Breaking:
WasRemoteStoreandAudienceReaderbuild their client with
WasClient.forSpace({ spaceUrl }): a grants-only app holds a Space URL and
nothing above it.WasRemoteStore.spaceUrlreplacesspaceId, and
AudienceReader.spaceUrlreplacesserverUrl. - Breaking:
resolveSpaceOwner,createSpaceTrust, andcreateWasSyncPort
takespaceUrlin place ofbaseUrl/spaceId. - Breaking: the persisted session record (
AppSessionRecord) carriesspaceUrl
in place ofbaseUrlandspaceId. A record in the old shape fails the
structural check and the reload lands inlocal; the next login re-persists
it. - Breaking:
provisionDevGrantstakesbaseServerUrl(the server's base, e.g.
http://localhost:3002/) in place ofserverUrl(the Spaces Repository URL),
builds the Space URL withwas.space(id).url, and delegates each grant on a
target built beneath it. - The sync controller fills was-sync's port with
spaceUrl. was-sync's default
replication identifier changed with it, so every replica re-pulls its
Collection from the beginning once. @interop/was-client^0.100.1,@interop/wallet-core^0.109.0,
@interop/was-sync^0.11.0,@interop/did-method-webvh^5.18.0.
v0.31.0
Changed
restoreAppSessionis removed from the root entry andpeekAppSessionis
exported in its place: the one reader of the persisted session, which never
wipes anything and leaves the expiry to the caller (isExpired).RestoredAppSessionis renamedAppSession(thepersistAppSessioninput:
the record plus the seed).snapshotWipeTargetstakessessionStoreDbNameandanonStoreDbName, and
SeedStorecarries thedbNameit is bound to, so a wipe deletes an injected
session store by its own name instead of assuming<dbName>-session.WipeTargetscarriessweepDbNamein place ofidbNamePrefixes. The sweep
no longer matches a broadcast-channel store:LocalStore.removeDatabase
removes single-instance, asinitopens, so RxDB opens no channel beside a
replica.seedStoreDbName(src/identity/seedStore.ts) andisReplicaDbNamebeside
dbNameForControllername the seed-store and replica database shapes once,
for the auth store that mints them and the sweep that matches them.
Fixed
-
The entity store's write verbs (
insert,update,upsert) commit to the
Map before the persist is awaited, and the persists of one id (remove
included) run in call order, so a read-modify-write issued before the previous
write has persisted merges onto what that one wrote instead of onto the
pre-write value and dropping it, and two unawaited upserts of a new id no
longer mint two rows. A persist that rejects puts the last persisted doc back,
and a hydrate that lands mid-write keeps showing the write.
defineDocumentApp'supdateandexportFileinherit this. -
The clear-data sweep matches only the database names this library derives from
itsdbName(<dbName>-session,<dbName>-anon,<dbName>-<8 hex>, and the
Dexie shells RxDB opens beside a replica), so clearing one app no longer wipes
a sibling app on the same origin whosedbNameextends it. -
LocalStore.initcloses the database it opened whenaddCollectionsrefuses
(a stored schema hash that no longer matches), instead of leaving it open with
its collections counted toward RxDB's process-wide cap. -
The adoption merge fills a missing
updatedAtand a missingwriterIdeach
on its own. A payload carryingupdatedAtbut nowriterIdkeeps its
original instant instead of being re-stamped to the adoption time and
outranking newer connected edits. -
An epoch-roster entry that names this app but fails its key-wrap check
(IntegrityErrorfrom@interop/was-client0.99.1) is routed as a tamper
signal: the local store records it as the collection's spent refusal, and the
shared-collection reader surfaces it as itself rather than as "not a
recipient". -
A boot over a persisted session whose grants have expired lands
reconnect
over the connected replica, with the seed intact and no replication started,
instead of wiping the session and landing an anonymouslocalreplica with no
reconnect banner. The replica opens on the cached descriptors alone, and
reconnect()renews the grants, persists the record, and starts sync. When
that expired restore fails withKeyUnwrapErrororIntegrityError(a cached
roster that no longer wraps a key to this app, or whose entry for it fails its
key-wrap check), the record is cleared, so the session lands a cleanlocal
rather than re-running the failed bring-up on every reload; any other failure
keeps the record for a reload or the reconnect to repair. -
A singleton duplicate that another client tombstoned while a hydrate was
reconciling it is no longer re-removed or warned about, anddeleteEntityno
longer rewrites a tombstone a pull landed under it. A winner tombstoned while
its losers were being removed is warned about. -
loginresolvesnullinstead of connecting when the session changed while
the wallet popup was open (a provider unmount with no re-mount, a
clearLocalData, aconnectWithGrants). A sync-controller stop that throws
no longer aborts the teardown, anddestroyresets the session state even
when its teardown throws. -
logoutclears the persisted session record before the anonymous replica
re-opens, so a re-open that fails no longer leaves the wallet seed on disk for
a reload to hot-restore. A failed re-open, inlogoutand in a connected
activation's fallback, now landslocalwith no replica and the failure on
errorinstead of keeping the previous status with no replica attached. -
AdoptDialogswallows the rejection of thelogin({ adopt })it starts. The
store already records a failed login onuseLogin().error, so a wallet
without App Connect support or a failed activation no longer also escapes the
dialog as an unhandled rejection. -
LocalStore.setDescriptorSourcere-arms the refresh guard, the in-flight
refresh, and the recorded refusals together, and a refresh that outlives its
source records nothing. -
A refresh whose fetched roster excludes this app is recorded and surfaces as
KeyUnwrapError; a refresh that fails on the wire is logged and left
un-spent. -
Per-collection cipher changes (the refresh,
applyRemoteDescriptor,
applyCollectionMeta) are serialized, and a descriptor that is an earlier
head of the log than the held one is not swapped back in. -
A public (plaintext) collection row whose body is an EDV envelope (an object
jwemember) no longer breaks the collection. The plaintext codec raises it
asIntegrityError, the same failure a mis-bound payload id is, so hydration
skips the row with a warning and hydrates the rest, and the conflict handler
settles a 412 on it by last-write-wins stamp instead of failing the
replication cycle on every retry. Any grant holder can write such a body, and
before this every hydrate of that collection rejected. -
The sync bootstrap guards its per-collection hook. A cipher rebuild that
throwsKeyUnwrapError(a roster rotated off this app since the cached
descriptor) now costs that one collection, which is warned about and left on
the cipher it opened with, while every other collection replicates. Before,
the throw rejectedstartWasSyncas a whole and no collection replicated
while the session stayedconnected. -
The adopted anonymous replica is removed before its seed is cleared. The
database goes through the wipe executor (so the Dexie shells RxDB's removal
leaves standing go with it), and the seed is cleared only when nothing failed.
Before, the seed was cleared first, so a failed removal (another tab holding
the database) orphaned the replica under a name no later run could derive. -
The connect transition's replica swap opens only for an
adopt: 'merge'login
fromlocal, the one case anonymous data is carried over. A write queued
during a login fromreconnectnow rejects instead of waiting on the swap and
landing in the newly connected account's replica as a create. -
reconnect()runs its post-popup epilogue (controller stop, record persist,
sync restart,connectedtransition) on the lifecycle chain and re-checks the
session first. Alogout,clearLocalData, ordestroycompleted while the
re-grant popup was open is no longer undone when the popup answers: the
renewed grants are discarded with a warning, nothing is persisted, and no sync
or expiry watch starts over the torn-down session. -
AudienceReader.pull()no longer reports a replay (or a listing read) cut by
a denial of a request that left under a since-replaced zcap as
snapshot: true. Such a cut pauses nothing but leaves the read partial; the
pull now returnssnapshot: falsewith the pages it did read, and the
checkpoint stays at the last fully read page. A consumer applying the partial
set as a snapshot would have dropped every post the cut kept it from reading. -
hydrateSingletontombstones a duplicate row with a conditional write on the
revision it read, anddeleteEntityremoves through RxDB's incremental write
queue, so a pull that updated or tombstoned the row after it was read (the
singleton decrypts can wait on a descriptor refresh) no longer fails the call
with a storage conflict. A duplicate whose row moved meanwhile is skipped with
a warning and reconciled by the next hydrate rather than removed unweighed,
and a winner whose row moved tombstones no loser at all.
v0.30.0
Changed
- Follow
@interop/was-client0.97.0, whoseWasClienttakes the server's
Spaces Repository URL (<base>/spaces/) asserverUrl.ParsedGrantsnow
carries the server's base URL asbaseUrl(read off the grant targets,
sub-path mount included) in place ofserverUrl; the remote store builds its
client overspacesUrl({ baseUrl }). Service discovery starts from that URL,
which the WAS server itself answers, so a bare origin whose root is served by
another application (a wallet's pages, say) no longer fails login with
"carries no rel=service link".createSpaceTrustandresolveSpaceOwnertake
baseUrl, andprovisionDevGrantstakes the Spaces Repository URL as
serverUrl. - The persisted session record's
serverUrlfield is renamedbaseUrland
holds the server's base URL. A record under the old shape fails the structural
check and is dropped, so the next load falls through to a returning login. CheckedGrants.parsedandLoginOutcome.parsedare optional, absent for a
grant-less login (an app requesting no collections, or a shared-only app whose
user declined every share) in place of the former empty-string topology.WasRemoteStore.serverUrlis removed; readwas.serverUrlorwas.baseUrl
off the store's client.- Requires
@interop/was-sync0.10.0, whose controller port names the
replication-identifier inputbaseUrl.
Fixed
- A login that returns no grants lands
connectedagain instead of failing its
activation: the session persists no record, starts no replication, and builds
no remote store, since there is no server to point them at. A reload of such a
session lands inlocal, as before.
v0.29.0
Changed
- A write issued during the connect transition (
login/connectWithGrants)
no longer rejects with "LocalStore is not initialized". The transition is a
replica swap on theStorageContext(beginSwap/endSwap): the
process-wide pointer stays with the session while the anonymous replica is
detached, and the entity-store write verbs resolve their replica through the
newawaitStore, which waits for the swap to attach and hydrate the connected
replica (or the re-opened anonymous one, on a failed activation) and then runs
the write there.hydratestill reads throughrequireStore. Only a merge
opens a swap; underadopt: 'leave'a mid-transition write still rejects, so
an edit of a set-aside document cannot resurrect it in the connected replica. login's connect epilogue now rides the serialized lifecycle chain (its
wallet popup await stays off it), andconnectWithGrantsis a no-op while a
login is in flight, so neither can swap the replica out from under the other
or under adestroy.- A failed connect surfaces its error from the caller (
login,
connectWithGrants,boot) only, instead of once in the activation and once
more inlogin. - The connected activation runs its network half (the Space owner's log, the
collection logs, the descriptor reads) before the anonymous replica is
detached, so the swap holds only local work: collect, open, merge, hydrate. A
failed read rejects with the anonymous replica still attached and nothing to
undo. provisionDevGrantstakes anexpiresoption and defaults it to
DEV_GRANT_TTL_MS(seven days, freewallet's write-grant lifetime; exported
from./dev). It passed none before, so ezcap minted five-minute grants and
the reconnect banner was raised right after a dev-connect.
v0.28.0
Added
rxdbis an optional peer, loaded lazily inLocalStore.init()/
removeDatabase()andSyncController.start(). A replica-less app installs
without it; an app that replicates adds it explicitly. NewReplicaStorage
andReplicaChangetypes replace therxdbtypes on the public surface, so
the declarations name norxdbtype.test:distfails on a staticrxdbor
@interop/was-sync/rxdbimport indist(WR-44).SyncControllerdeclares the session'swriterIdto the server on every push
(was-sync'swriterIdreplication option, sent as theWriter-Idheader).
The newWasSyncConfig.declareWriterId(defaulttrue) opts out, withholding
the label from the wire while the payload's LWW stamp is unchanged (WR-53).
Changed
-
BREAKING:
isAuthErroris no longer re-exported from the root entry.
Import it from@interop/was-sync/rxdb(WR-44). -
BREAKING:
LocalStore.rxCollectionis@internaland stripped from the
published declarations; its return type is RxDB's. Tests that reached the RxDB
collection through it must go throughLocalStore's own verbs (WR-44). -
resolveSpaceOwnerresolves the owner through wallet-core's
discoverAccountLoginstead of its own fetch, parse, and resolve body; the
location check runs upstream. -
provisionDevGrantspromotes the Space controller through wallet-core's
ensurePromotedSpaceControllerunder the configured Space name, so the
promotion reads first and heals an interrupted run. -
BREAKING: every
edvcollection is read through a verifying descriptor
source under was-client'srequireGovernedpolicy.WasRemoteStoregains
descriptorSource, wallet-core'slogGovernedDescriptorSourceover each
collection'smeta/log(invoked with the collection's own grant), checked
against the Space owner's did:webvh account log. The owner is resolved from
the Space's world-readableid/did.jsonl(resolveSpaceOwner): the log is
fetched, resolved, and the resolved DID's log location is checked against the
URL it was served from. The login-time read, the sync bootstrap's install,
LocalStore's unknown-epoch refresh, andSharedCollectionReaderall acquire
through it; a descriptor is never taken from the Metadata object's
encryptionprojection any more.remoteDescriptorSourceis removed from the
root entry. A collection with no governing log stays fail-closed. The Space
owner's verified view and the chain-head pins are held in memory for the
connected activation on oneSpaceTrustthe login-time read and the sync
bootstrap share (fromGrantstakes it astrust), so the owner's log is
fetched and verified once per activation. A failed Space-owner resolution is
retried on the next read rather than remembered, and a shared collection whose
descriptor read fails surfaces that failure as itself instead of as
SharedCollectionUnavailableError. -
was-client's
UnverifiedDescriptorError(a source that does not verify the
governing log) surfaces as itself at every call site:
SharedCollectionReader.openrethrows it ahead of
SharedCollectionUnavailableError, andLocalStoresurfaces it from the
decrypt that met it and every later one, instead of the envelope's
unknown-epoch error. A governing log the source refuses to adopt (a forged or
discontinuous chain, vh-resource-log'sisResourceLogRefusal) surfaces the
same way at both sites. Any other refresh failure inLocalStoreis logged at
warnand re-asked on the next unknown-epoch decrypt. -
resolveSpaceOwnertakes an optionalpinStore, andcreateSpaceTrust
passes its own, so the owner's account log is checked for chain-head
continuity across resolutions within one activation like every collection log
is. -
createSpaceTrust,SpaceTrust,resolveSpaceOwner, and
acquireGovernedDescriptorare exported from the root entry, so a consumer
building its ownWasRemoteStoreorLocalStorecan share one trust across
stores and build a verifying descriptor source. -
provisionDevGrantsbuilds what a wallet's account genesis builds: a Space
promoted to a did:webvh owner whose log is published atid/did.jsonl, and
every private collection's epoch zero installed as the genesis of its
meta/logthrough the log-governed descriptor store.provisionerDidis now
the did:webvh. The server URL must be one did:webvh accepts (https://, or
http://on the literal hostlocalhost). The Metadata-write probe leaves
encryptionout of the body it writes back, since the server derives it from
the log head. -
Remove
WasRemoteStore.readCollectionEncryption(the Metadata object's
encryptionprojection is no longer read for a descriptor;descriptorSource
is the read) andWasRemoteStore.markCollectionEncryptedwith the sync
bootstrap's{ encryption: { scheme: 'edv' } }declaration write. A
collection is declared encrypted by its governing log's genesis, and one with
no log stays fail-closed under the policy above, so the bare declaration had
nothing left to do. -
Add
@interop/vh-resource-logas a dependency. -
Bump
@interop/was-syncto 0.9.0 and@interop/was-clientto 0.94.0. The ack
write-back now stamps a pushed row'supdatedAt,updatedAtCounter,
originId,createdBy, andmetafrom the write response itself, so a row
this replica created no longer depends on the feed echo for its server stamp
(needs a server answering201/200with a body, was-teaching-server
0.42.0 or later). The replica schema also gains an optional row-level
writerIdat the same schema version, so RxDB refuses to reopen an existing
replica: as with 0.27.0, every remembered browser clears its local data and
logs in again once, and local writes not yet pushed are lost with the old
replica. Nothing in this library reads the push ack, so no code changed.
v0.27.0
Changed
- Depend on
@interop/was-client0.94.0 forparseAbsoluteSpaceTarget,
allowsAction, andisDenialError.
Added
AudienceReader, the member-side reader of one plaintext audience collection
in another owner's Space, opened on a GET and HEAD zcap and a signer directly
(AudienceReader.open({ zcap, signer, checkpoint?, pageSize? })). The client
is built from the zcap's owninvocationTarget(was-client's
parseAbsoluteSpaceTarget), so the audience may live on another host.
pull()follows thechangesfeed through its GET form from a per-reader
checkpoint that moves only with the changes a pull returns, keeps
kind: 'resource'entries only, replays from the start on a refused
checkpoint, and falls back to the listing plus one GET per resource on a 405
or 501 (bothsnapshot: true). A denial (401, 403, or the masked 404 with its
capability-revoked/capability-expiredcauses, on the feed or settled
against the container after a null body read) pauses the reader with its
checkpoint kept;replaceCapability({ zcap })resumes it, and a denial of a
request in flight under the replaced zcap pauses nothing. Exported with
AUDIENCE_CHANGES_PAGE_SIZE,AudienceChange, andAudiencePull.
Changed
SharedCollectionReader.list()reads the widenedchangesfeed through
was-client'sCollection.documents()walk instead of paging the feed itself.
Onlykind: 'resource'entries with JSON content are kept; the Collection's
own records, unknown kinds, tombstones (deleted, was_deleted), and binary
ortext/jsonlresources drop out, and the walk resumes from each page's
checkpoint.- Bump
@interop/was-syncto 0.8.2. The replica schema carries the server's
write stamp (updatedAtCounter,originId, nestedmeta) in place of the
integerversion/metaVersion, at the same schema version, so RxDB refuses
to reopen an existing replica. No migration or in-code forget is shipped:
every remembered browser clears its local data (clearLocalData()removes the
replica with its replication meta) and logs in again once, and local writes
not yet pushed are lost with the old replica. Also bump@interop/was-client
to 0.93.2,@interop/wallet-coreto 0.104.1, and@interop/wallet-requestto
0.5.1. - Docs: the party that asks a wallet for grants is the "requester" (an app,
service, or agent), replacing "relying party" / "RP" in JSDoc, README, and
ARCHITECTURE.md. Here the requester is the app running this library. - Tests: the in-process server suites boot through
was-teaching-server/testing
(startTestServer,openTempBackend), against the server's 0.40.0 release.
v0.26.0
Changed
- Bump wallet-request to 0.4.1 (appUrl rejects query).
v0.25.0
Breaking
- Every decrypt is addressed by the row's/resource's own id, matching was-client
0.68's requiredDocCipher.decrypt({ id, envelope })shape.DocCipher
implementors must takeid, andLocalStore#decryptEnvelopetakes an options
object ({ key, id, envelope }) rather than positional arguments. Both are
root-entry API.
Changed
@interop/was-clientbumped to^0.69.0,@interop/was-syncto^0.4.0,
@interop/did-method-webvhto^5.10.0,@interop/ezcapto^7.4.9.- The public plaintext codec checks the binding that is real on a public
collection: the row id must equal the payload's ownid. A mismatch (or a
body carrying no stringid) raisesIntegrityError. It refuses a decrypt
carrying no resource id at all through was-client's ownrequireResourceId,
the guard both of that package's built-in ciphers run first.
Fixed
- A private collection's
IntegrityErrorfails that collection's replication
cycle the same way any other fatal conflict-resolution error does; no new
status or error-name contract was added for it. LocalStorehydration skips, with a warning, a public collection's row whose
payloadidis missing or differs from its row id, so one malformed document
no longer fails hydration of the rest. The LWW conflict handler is lenient the
same way: on a public collection a mis-bound side is compared on the
last-write-wins stamp it still carries instead of failing the cycle, which the
server's unchanged body would otherwise fail on every retry.SharedCollectionReader.get()surfacesIntegrityErrorinstead of skipping
the resource with the "not a recipient" warning.list()skips a mis-bound
envelope (logged aterror) so one of them cannot make a wallet-owned
collection unlistable.
v0.24.1
Changed
- Update to latest wallet-core@0.78.1 and was-client@0.65.0.
v0.24.0
Breaking
- A collection's description and its metadata are one object under WAS v0.5. The
encryptiondescriptor, the publicplaintext.indexesdeclaration, and the
encryptedcustomenvelope all live on the Collection Metadata object at
meta, under one validator. The old partial-body descriptionPUTis gone;
every write now reads that object first and carries it forward whole, so a
declaration write no longer clearscustom(where the blinded-index schema
lives). Both the encryption declaration and the publicindexesdeclaration
go through was-client'sCollection.configuremerge rather than a rawPUT. - The sync bootstrap reads a granted collection's Metadata object once per
collection, public collections with indexes included, and feeds the cipher
rebuild, the encryption declaration, and the public indexes declaration from
that read. A private collection with a blinding key still costs extra reads:
the blinded-index declaration and its schema install read the object again on
their own. provisionDevGrants's per-collection grant target is the collection's
canonical container URL, and its write probe targetsmeta.- Minimum versions:
@interop/was-client^0.63.0,@interop/wallet-core
^0.76.0,@interop/was-sync^0.2.9, andwas-teaching-server^0.33.0
for the in-process tests. @interop/webkms-clientis replaced by@interop/capability-agent^0.2.0
forCapabilityAgent, following@interop/wallet-core's drop of its
./identitysubpath (now@interop/was-client/identity).- A server still on WAS v0.4 no longer works against this release.
was-teaching-server0.33.0 admits a Collection-scoped grant at
PUT /space/{s}/{c}/meta, so the encryption descriptor, the publicindexes
declaration, and the blinded-index schema all land under the per-collection
grants this library holds. The warn-and-skip path stays for a read-only or
withdrawn grant.WasRemoteStore.markCollectionEncrypteddrops itsencryptionoption; the
skip check now readscurrent.description.encryptiondirectly, since every
caller already had one or the other. A caller that already knows a
collection's descriptor without a read (the known-descriptor path in the sync
bootstrap) skips the call itself instead of passing an absentcurrent.- The blinded-index declaration settles every missing attribute in one
compare-and-swap write through was-client'sCollection.declareIndexes,
instead of one read-and-write per attribute. Minimum@interop/was-clientis
^0.63.0.
Changed
@interop/wallet-request0.2.0. The counterpart test no longer exercises the
legacy (pre-appUrl) app-key re-issue path, which that release removed.