Skip to content

Releases: interop-alliance/pws-react

v0.32.0

Choose a tag to compare

@dmitrizagidulin dmitrizagidulin released this 30 Oct 02:19

Changed

  • Breaking: a grant's Space is read off its delegation chain's root
    (was-client's spaceUrlOf), and its invocationTarget is classified beneath
    that Space URL. ParsedGrants carries spaceUrl in place of baseUrl and
    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
    ValidationError as cause.
  • AudienceReader.replaceCapability refuses a zcap whose chain roots in another
    Space, and changes nothing on the reader until every check has passed.
  • Breaking: WasRemoteStore and AudienceReader build their client with
    WasClient.forSpace({ spaceUrl }): a grants-only app holds a Space URL and
    nothing above it. WasRemoteStore.spaceUrl replaces spaceId, and
    AudienceReader.spaceUrl replaces serverUrl.
  • Breaking: resolveSpaceOwner, createSpaceTrust, and createWasSyncPort
    take spaceUrl in place of baseUrl / spaceId.
  • Breaking: the persisted session record (AppSessionRecord) carries spaceUrl
    in place of baseUrl and spaceId. A record in the old shape fails the
    structural check and the reload lands in local; the next login re-persists
    it.
  • Breaking: provisionDevGrants takes baseServerUrl (the server's base, e.g.
    http://localhost:3002/) in place of serverUrl (the Spaces Repository URL),
    builds the Space URL with was.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

Choose a tag to compare

@dmitrizagidulin dmitrizagidulin released this 29 Oct 16:28

Changed

  • restoreAppSession is removed from the root entry and peekAppSession is
    exported in its place: the one reader of the persisted session, which never
    wipes anything and leaves the expiry to the caller (isExpired).
  • RestoredAppSession is renamed AppSession (the persistAppSession input:
    the record plus the seed).
  • snapshotWipeTargets takes sessionStoreDbName and anonStoreDbName, and
    SeedStore carries the dbName it is bound to, so a wipe deletes an injected
    session store by its own name instead of assuming <dbName>-session.
  • WipeTargets carries sweepDbName in place of idbNamePrefixes. The sweep
    no longer matches a broadcast-channel store: LocalStore.removeDatabase
    removes single-instance, as init opens, so RxDB opens no channel beside a
    replica.
  • seedStoreDbName (src/identity/seedStore.ts) and isReplicaDbName beside
    dbNameForController name 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's update and exportFile inherit this.

  • The clear-data sweep matches only the database names this library derives from
    its dbName (<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 whose dbName extends it.

  • LocalStore.init closes the database it opened when addCollections refuses
    (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 updatedAt and a missing writerId each
    on its own. A payload carrying updatedAt but no writerId keeps 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
    (IntegrityError from @interop/was-client 0.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 anonymous local replica 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 with KeyUnwrapError or IntegrityError (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 clean local
    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, and deleteEntity no
    longer rewrites a tombstone a pull landed under it. A winner tombstoned while
    its losers were being removed is warned about.

  • login resolves null instead of connecting when the session changed while
    the wallet popup was open (a provider unmount with no re-mount, a
    clearLocalData, a connectWithGrants). A sync-controller stop that throws
    no longer aborts the teardown, and destroy resets the session state even
    when its teardown throws.

  • logout clears 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, in logout and in a connected
    activation's fallback, now lands local with no replica and the failure on
    error instead of keeping the previous status with no replica attached.

  • AdoptDialog swallows the rejection of the login({ adopt }) it starts. The
    store already records a failed login on useLogin().error, so a wallet
    without App Connect support or a failed activation no longer also escapes the
    dialog as an unhandled rejection.

  • LocalStore.setDescriptorSource re-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
    jwe member) no longer breaks the collection. The plaintext codec raises it
    as IntegrityError, 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
    throws KeyUnwrapError (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 rejected startWasSync as a whole and no collection replicated
    while the session stayed connected.

  • 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
    from local, the one case anonymous data is carried over. A write queued
    during a login from reconnect now 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, connected transition) on the lifecycle chain and re-checks the
    session first. A logout, clearLocalData, or destroy completed 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 returns snapshot: false with 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.

  • hydrateSingleton tombstones a duplicate row with a conditional write on the
    revision it read, and deleteEntity removes 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

Choose a tag to compare

@dmitrizagidulin dmitrizagidulin released this 12 Oct 19:08

Changed

  • Follow @interop/was-client 0.97.0, whose WasClient takes the server's
    Spaces Repository URL (<base>/spaces/) as serverUrl. ParsedGrants now
    carries the server's base URL as baseUrl (read off the grant targets,
    sub-path mount included) in place of serverUrl; the remote store builds its
    client over spacesUrl({ 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". createSpaceTrust and resolveSpaceOwner take
    baseUrl, and provisionDevGrants takes the Spaces Repository URL as
    serverUrl.
  • The persisted session record's serverUrl field is renamed baseUrl and
    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.parsed and LoginOutcome.parsed are 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.serverUrl is removed; read was.serverUrl or was.baseUrl
    off the store's client.
  • Requires @interop/was-sync 0.10.0, whose controller port names the
    replication-identifier input baseUrl.

Fixed

  • A login that returns no grants lands connected again 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 in local, as before.

v0.29.0

Choose a tag to compare

@dmitrizagidulin dmitrizagidulin released this 11 Oct 23:44

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 the StorageContext (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
    new awaitStore, 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. hydrate still reads through requireStore. Only a merge
    opens a swap; under adopt: '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), and connectWithGrants is a no-op while a
    login is in flight, so neither can swap the replica out from under the other
    or under a destroy.
  • A failed connect surfaces its error from the caller (login,
    connectWithGrants, boot) only, instead of once in the activation and once
    more in login.
  • 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.
  • provisionDevGrants takes an expires option 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

Choose a tag to compare

@dmitrizagidulin dmitrizagidulin released this 11 Oct 19:31

Added

  • rxdb is an optional peer, loaded lazily in LocalStore.init() /
    removeDatabase() and SyncController.start(). A replica-less app installs
    without it; an app that replicates adds it explicitly. New ReplicaStorage
    and ReplicaChange types replace the rxdb types on the public surface, so
    the declarations name no rxdb type. test:dist fails on a static rxdb or
    @interop/was-sync/rxdb import in dist (WR-44).
  • SyncController declares the session's writerId to the server on every push
    (was-sync's writerId replication option, sent as the Writer-Id header).
    The new WasSyncConfig.declareWriterId (default true) opts out, withholding
    the label from the wire while the payload's LWW stamp is unchanged (WR-53).

Changed

  • BREAKING: isAuthError is no longer re-exported from the root entry.
    Import it from @interop/was-sync/rxdb (WR-44).

  • BREAKING: LocalStore.rxCollection is @internal and stripped from the
    published declarations; its return type is RxDB's. Tests that reached the RxDB
    collection through it must go through LocalStore's own verbs (WR-44).

  • resolveSpaceOwner resolves the owner through wallet-core's
    discoverAccountLog instead of its own fetch, parse, and resolve body; the
    location check runs upstream.

  • provisionDevGrants promotes the Space controller through wallet-core's
    ensurePromotedSpaceController under the configured Space name, so the
    promotion reads first and heals an interrupted run.

  • BREAKING: every edv collection is read through a verifying descriptor
    source under was-client's requireGoverned policy. WasRemoteStore gains
    descriptorSource, wallet-core's logGovernedDescriptorSource over each
    collection's meta/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-readable id/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, and SharedCollectionReader all acquire
    through it; a descriptor is never taken from the Metadata object's
    encryption projection any more. remoteDescriptorSource is 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 one SpaceTrust the login-time read and the sync
    bootstrap share (fromGrants takes it as trust), 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.open rethrows it ahead of
    SharedCollectionUnavailableError, and LocalStore surfaces 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's isResourceLogRefusal) surfaces the
    same way at both sites. Any other refresh failure in LocalStore is logged at
    warn and re-asked on the next unknown-epoch decrypt.

  • resolveSpaceOwner takes an optional pinStore, and createSpaceTrust
    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
    acquireGovernedDescriptor are exported from the root entry, so a consumer
    building its own WasRemoteStore or LocalStore can share one trust across
    stores and build a verifying descriptor source.

  • provisionDevGrants builds what a wallet's account genesis builds: a Space
    promoted to a did:webvh owner whose log is published at id/did.jsonl, and
    every private collection's epoch zero installed as the genesis of its
    meta/log through the log-governed descriptor store. provisionerDid is now
    the did:webvh. The server URL must be one did:webvh accepts (https://, or
    http:// on the literal host localhost). The Metadata-write probe leaves
    encryption out of the body it writes back, since the server derives it from
    the log head.

  • Remove WasRemoteStore.readCollectionEncryption (the Metadata object's
    encryption projection is no longer read for a descriptor; descriptorSource
    is the read) and WasRemoteStore.markCollectionEncrypted with 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-log as a dependency.

  • Bump @interop/was-sync to 0.9.0 and @interop/was-client to 0.94.0. The ack
    write-back now stamps a pushed row's updatedAt, updatedAtCounter,
    originId, createdBy, and meta from the write response itself, so a row
    this replica created no longer depends on the feed echo for its server stamp
    (needs a server answering 201 / 200 with a body, was-teaching-server
    0.42.0 or later). The replica schema also gains an optional row-level
    writerId at 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

Choose a tag to compare

@dmitrizagidulin dmitrizagidulin released this 10 Oct 22:49

Changed

  • Depend on @interop/was-client 0.94.0 for parseAbsoluteSpaceTarget,
    allowsAction, and isDenialError.

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 own invocationTarget (was-client's
    parseAbsoluteSpaceTarget), so the audience may live on another host.
    pull() follows the changes feed 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 (both snapshot: true). A denial (401, 403, or the masked 404 with its
    capability-revoked / capability-expired causes, 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, and AudiencePull.

Changed

  • SharedCollectionReader.list() reads the widened changes feed through
    was-client's Collection.documents() walk instead of paging the feed itself.
    Only kind: 'resource' entries with JSON content are kept; the Collection's
    own records, unknown kinds, tombstones (deleted, was _deleted), and binary
    or text/jsonl resources drop out, and the walk resumes from each page's
    checkpoint.
  • Bump @interop/was-sync to 0.8.2. The replica schema carries the server's
    write stamp (updatedAtCounter, originId, nested meta) in place of the
    integer version / 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-core to 0.104.1, and @interop/wallet-request to
    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

Choose a tag to compare

@dmitrizagidulin dmitrizagidulin released this 28 Sep 02:49

Changed

  • Bump wallet-request to 0.4.1 (appUrl rejects query).

v0.25.0

Choose a tag to compare

@dmitrizagidulin dmitrizagidulin released this 18 Sep 01:47

Breaking

  • Every decrypt is addressed by the row's/resource's own id, matching was-client
    0.68's required DocCipher.decrypt({ id, envelope }) shape. DocCipher
    implementors must take id, and LocalStore#decryptEnvelope takes an options
    object ({ key, id, envelope }) rather than positional arguments. Both are
    root-entry API.

Changed

  • @interop/was-client bumped to ^0.69.0, @interop/was-sync to ^0.4.0,
    @interop/did-method-webvh to ^5.10.0, @interop/ezcap to ^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 own id. A mismatch (or a
    body carrying no string id) raises IntegrityError. It refuses a decrypt
    carrying no resource id at all through was-client's own requireResourceId,
    the guard both of that package's built-in ciphers run first.

Fixed

  • A private collection's IntegrityError fails 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.
  • LocalStore hydration skips, with a warning, a public collection's row whose
    payload id is 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() surfaces IntegrityError instead of skipping
    the resource with the "not a recipient" warning. list() skips a mis-bound
    envelope (logged at error) so one of them cannot make a wallet-owned
    collection unlistable.

v0.24.1

Choose a tag to compare

@dmitrizagidulin dmitrizagidulin released this 15 Sep 17:47

Changed

  • Update to latest wallet-core@0.78.1 and was-client@0.65.0.

v0.24.0

Choose a tag to compare

@dmitrizagidulin dmitrizagidulin released this 14 Sep 20:38

Breaking

  • A collection's description and its metadata are one object under WAS v0.5. The
    encryption descriptor, the public plaintext.indexes declaration, and the
    encrypted custom envelope all live on the Collection Metadata object at
    meta, under one validator. The old partial-body description PUT is gone;
    every write now reads that object first and carries it forward whole, so a
    declaration write no longer clears custom (where the blinded-index schema
    lives). Both the encryption declaration and the public indexes declaration
    go through was-client's Collection.configure merge rather than a raw PUT.
  • 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 targets meta.
  • Minimum versions: @interop/was-client ^0.63.0, @interop/wallet-core
    ^0.76.0, @interop/was-sync ^0.2.9, and was-teaching-server ^0.33.0
    for the in-process tests.
  • @interop/webkms-client is replaced by @interop/capability-agent ^0.2.0
    for CapabilityAgent, following @interop/wallet-core's drop of its
    ./identity subpath (now @interop/was-client/identity).
  • A server still on WAS v0.4 no longer works against this release.
  • was-teaching-server 0.33.0 admits a Collection-scoped grant at
    PUT /space/{s}/{c}/meta, so the encryption descriptor, the public indexes
    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.markCollectionEncrypted drops its encryption option; the
    skip check now reads current.description.encryption directly, 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 absent current.
  • The blinded-index declaration settles every missing attribute in one
    compare-and-swap write through was-client's Collection.declareIndexes,
    instead of one read-and-write per attribute. Minimum @interop/was-client is
    ^0.63.0.

Changed

  • @interop/wallet-request 0.2.0. The counterpart test no longer exercises the
    legacy (pre-appUrl) app-key re-issue path, which that release removed.