release(v4.4.0): public accessor for device identity public key
Expose the local device's 32-byte Ed25519 identity public key on Shade so apps can hand it to their own backend at enrollment time for signature verification, key pinning or per-device safety-number computation. Closes the gap that forced consumers to ship placeholder random bytes their backend could store but never verify against. - @shade/sdk Shade.identityPublicKey: Promise<Uint8Array> — getter mirrors the existing fingerprint accessor. Throws pre-init, reflects the current key after rotate(), retired key preserved in retired-identities storage per existing grace-period contract. Private key remains unreachable. - Test in shade-sdk/tests/sdk.test.ts: round-trip match against the underlying storage's signingPublicKey, plus value updates after rotate(). - Lockstep version bump 4.3.0 → 4.4.0 across all 25 packages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
29
CHANGELOG.md
29
CHANGELOG.md
@@ -5,6 +5,35 @@ All notable changes to Shade are documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [4.4.0] — 2026-05-05 — Public accessor for the device's identity public key
|
||||||
|
|
||||||
|
Browser-based Shade consumers building enrollment flows had no way to
|
||||||
|
hand the device's actual Ed25519 identity public key to their own
|
||||||
|
backend — the key was reachable only via the private
|
||||||
|
`storage.getIdentityKeyPair()` call inside `Shade`. Apps shipped with
|
||||||
|
placeholder bytes (`crypto.getRandomValues(new Uint8Array(32))`) that
|
||||||
|
the backend stored but couldn't verify against, deferring real
|
||||||
|
cryptographic device binding until the SDK exposed the key.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
#### `@shade/sdk`
|
||||||
|
- `Shade.identityPublicKey: Promise<Uint8Array>` — getter returning the
|
||||||
|
local device's 32-byte Ed25519 identity public key. Mirrors the
|
||||||
|
`fingerprint` accessor shape. Throws if accessed before
|
||||||
|
`initialize()`. Reflects the current key after `rotate()`; the
|
||||||
|
previous key remains in retired-identities storage for the
|
||||||
|
configured grace period. Use `fingerprint` (12-group safety number)
|
||||||
|
for human side-channel comparison; use `identityPublicKey` when
|
||||||
|
handing the raw key to a backend for signature verification or
|
||||||
|
pinning.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
- `packages/shade-sdk/tests/sdk.test.ts` — `identityPublicKey exposes
|
||||||
|
the device Ed25519 key and tracks rotation` covers the round-trip
|
||||||
|
match against the underlying storage and that the value updates
|
||||||
|
after `rotate()`.
|
||||||
|
|
||||||
## [4.3.0] — 2026-05-05 — Browser persistence via `@shade/storage-indexeddb`
|
## [4.3.0] — 2026-05-05 — Browser persistence via `@shade/storage-indexeddb`
|
||||||
|
|
||||||
Browser-based Shade consumers had no path to session persistence: the only
|
Browser-based Shade consumers had no path to session persistence: the only
|
||||||
|
|||||||
173
docs/shade-identity-public-key-accessor.md
Normal file
173
docs/shade-identity-public-key-accessor.md
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
# Feature Request — Public accessor for the device's identity public key
|
||||||
|
|
||||||
|
**To**: Shade SDK team
|
||||||
|
**From**: Dispatch (browser-based Shade consumer)
|
||||||
|
**Target**: Shade SDK 4.4.x (or whichever release vehicle fits)
|
||||||
|
**Priority**: medium — unblocks real per-device fingerprint binding at
|
||||||
|
enrollment time; consumers ship with placeholder keys until then
|
||||||
|
|
||||||
|
Thanks for shipping `@shade/storage-indexeddb` so quickly — that unblocked
|
||||||
|
Dispatch's Slice 2.5 (persistent browser identity). During integration we
|
||||||
|
hit one more gap that's worth raising as a separate FR.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Expose the local device's Ed25519 identity public key as a public accessor
|
||||||
|
on `Shade`, so applications can hand it to their own backend at enrollment
|
||||||
|
time for per-device verification, audit, or peer-fingerprint computation.
|
||||||
|
|
||||||
|
Today the SDK exposes `myAddress` and `fingerprint`, but the underlying
|
||||||
|
identity public key — the cryptographic root that everything else binds
|
||||||
|
to — is reachable only via the private `this.storage.getIdentityKeyPair()`
|
||||||
|
call inside `Shade`. Consumers building enrollment flows have no way to
|
||||||
|
hand the real key over.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
A common pattern in Shade-using apps is:
|
||||||
|
|
||||||
|
1. Browser device generates Shade identity (via `createShade`)
|
||||||
|
2. User enters an enrollment token
|
||||||
|
3. Browser POSTs to its backend: `{ token, address, identityPublicKey, ... }`
|
||||||
|
4. Backend records the device, computes/stores a safety number from the
|
||||||
|
identity key, opens a Shade session
|
||||||
|
|
||||||
|
Step 3 today has nowhere to get a real `identityPublicKey` from. Dispatch
|
||||||
|
currently sends `crypto.getRandomValues(new Uint8Array(32))` formatted as
|
||||||
|
hex, with this comment in the source:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
/**
|
||||||
|
* The browser submits a hex public-key field at enroll time so the schema
|
||||||
|
* stays stable. Wiring this to the SDK-generated identity key requires a
|
||||||
|
* Shade SDK addition (no public accessor for the raw identity key today).
|
||||||
|
*/
|
||||||
|
export function generatePlaceholderIdentityPublicKey(): string { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
The backend stores this placeholder but cannot verify anything against it,
|
||||||
|
because it's not actually the device's key. Real cryptographic binding is
|
||||||
|
deferred until the SDK exposes the underlying key.
|
||||||
|
|
||||||
|
## Why `fingerprint` isn't sufficient
|
||||||
|
|
||||||
|
`Shade.fingerprint` returns a 12-groups-of-5-digits safety-number string
|
||||||
|
designed for human side-channel comparison. That's the right output for a
|
||||||
|
"compare these digits with your friend" UX, but it's a derived format, not
|
||||||
|
the raw key. Backends that want to:
|
||||||
|
|
||||||
|
- Store the key for later signature verification
|
||||||
|
- Compute their own safety-number representation (Dispatch uses
|
||||||
|
`deterministicSafetyNumber(localAddr, peerAddr)` based on the raw bytes)
|
||||||
|
- Re-derive the fingerprint after an identity rotation
|
||||||
|
|
||||||
|
…all need access to the raw 32-byte Ed25519 public key.
|
||||||
|
|
||||||
|
## Proposed API
|
||||||
|
|
||||||
|
Add a single async accessor on `Shade`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class Shade {
|
||||||
|
/**
|
||||||
|
* The local device's Ed25519 identity public key (32 bytes).
|
||||||
|
*
|
||||||
|
* Stable for the lifetime of the identity. After `rotateIdentity()`
|
||||||
|
* this returns the new key; the old key is preserved in retired-
|
||||||
|
* identities storage for the configured grace period.
|
||||||
|
*/
|
||||||
|
get identityPublicKey(): Promise<Uint8Array>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Internally:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
get identityPublicKey(): Promise<Uint8Array> {
|
||||||
|
if (!this.initialized) throw new Error('Not initialized');
|
||||||
|
return this.storage.getIdentityKeyPair().then((kp) => {
|
||||||
|
if (!kp) throw new Error('Identity not yet generated');
|
||||||
|
return kp.publicKey;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If returning a getter that produces a Promise feels off, the equivalent
|
||||||
|
method form is fine:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
async getIdentityPublicKey(): Promise<Uint8Array> { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Either shape works for consumers — pick whichever matches existing SDK
|
||||||
|
conventions.
|
||||||
|
|
||||||
|
## Alternative considered: object accessor
|
||||||
|
|
||||||
|
Returning an object would leave room to expose other identity-related
|
||||||
|
fields later without a breaking change:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
get identity(): Promise<{
|
||||||
|
address: string;
|
||||||
|
publicKey: Uint8Array;
|
||||||
|
fingerprint: string;
|
||||||
|
// future: registrationId, createdAt, etc.
|
||||||
|
}>;
|
||||||
|
```
|
||||||
|
|
||||||
|
This would mildly duplicate `myAddress` + `fingerprint`, but consolidates
|
||||||
|
identity-related accessors. Not a blocker for shipping the simpler
|
||||||
|
single-purpose accessor — just flagging the option in case the SDK is
|
||||||
|
considering broader API ergonomics.
|
||||||
|
|
||||||
|
## What this unblocks
|
||||||
|
|
||||||
|
For Dispatch specifically:
|
||||||
|
|
||||||
|
- Real device identity binding at enrollment (replaces the placeholder
|
||||||
|
`crypto.randomUUID()`-derived hex bytes the backend currently stores)
|
||||||
|
- Server-side `computePeerSafetyNumber()` can use the real key instead of
|
||||||
|
the deterministic-from-address stand-in (`shade-identity-provider.ts:151`)
|
||||||
|
- Future signature-based device authentication (sign a challenge with the
|
||||||
|
device's identity key during enrollment) becomes possible without
|
||||||
|
another SDK round
|
||||||
|
|
||||||
|
For other Shade consumers:
|
||||||
|
|
||||||
|
- Any app that hands a device key to its own backend for enrollment —
|
||||||
|
multi-device pairing flows, contact verification UIs, push-notification
|
||||||
|
targeting — gets an actual key to work with
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
1. New accessor exposed on `Shade` (getter or async method, SDK's
|
||||||
|
preference)
|
||||||
|
2. Returns the 32-byte Ed25519 identity public key
|
||||||
|
3. Returns the **current** key after `rotateIdentity()`
|
||||||
|
4. Throws (or resolves to a clear error) when called before
|
||||||
|
`initialize()` completes
|
||||||
|
5. Documented alongside `myAddress` and `fingerprint` in the SDK reference
|
||||||
|
6. One unit test in `shade-sdk` confirming the returned bytes match what
|
||||||
|
`storage.getIdentityKeyPair()` holds
|
||||||
|
7. Mentioned in the changelog under the next release
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
- **Private key access** — consumers should never need it; signature
|
||||||
|
operations go through `shade.send()` etc. Don't expose the secret half.
|
||||||
|
- **Cross-peer key lookup** — getting *another* peer's identity key is a
|
||||||
|
separate concern (related to peer-verification storage), not what this
|
||||||
|
FR is about. This is strictly the local device's own key.
|
||||||
|
- **Format conversions** — base64/hex/PEM helpers don't belong in the SDK.
|
||||||
|
Consumers can encode the raw bytes however their wire format requires.
|
||||||
|
|
||||||
|
## Why this can't be done in consumer-land
|
||||||
|
|
||||||
|
The identity keypair is generated by `MemoryStorage` / `SQLiteStorage` /
|
||||||
|
`IndexedDBStorage` and consumed by `ShadeSessionManager`. Consumers can't
|
||||||
|
reach into either layer without breaching the SDK's encapsulation. A
|
||||||
|
public accessor is the only path that doesn't require monkey-patching
|
||||||
|
private fields.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/cli",
|
"name": "@shade/cli",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/cli.ts",
|
"main": "src/cli.ts",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/core",
|
"name": "@shade/core",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/crypto-web",
|
"name": "@shade/crypto-web",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/dashboard",
|
"name": "@shade/dashboard",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/files",
|
"name": "@shade/files",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/inbox-server",
|
"name": "@shade/inbox-server",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/inbox",
|
"name": "@shade/inbox",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/key-transparency",
|
"name": "@shade/key-transparency",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/keychain",
|
"name": "@shade/keychain",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/observability",
|
"name": "@shade/observability",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/observer",
|
"name": "@shade/observer",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/proto",
|
"name": "@shade/proto",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/recovery",
|
"name": "@shade/recovery",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/sdk",
|
"name": "@shade/sdk",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -299,6 +299,26 @@ export class Shade {
|
|||||||
return this.address;
|
return this.address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The local device's Ed25519 identity public key (32 bytes).
|
||||||
|
*
|
||||||
|
* Stable for the lifetime of the identity. After {@link rotate} this
|
||||||
|
* reflects the new key; the previous key is preserved in retired-
|
||||||
|
* identities storage for the configured grace period.
|
||||||
|
*
|
||||||
|
* Hand this to your application's backend at enrollment time so it
|
||||||
|
* can verify signatures from this device, compute its own safety-
|
||||||
|
* number representation, or pin the key for later attestation. Use
|
||||||
|
* {@link fingerprint} instead for human side-channel comparison.
|
||||||
|
*/
|
||||||
|
get identityPublicKey(): Promise<Uint8Array> {
|
||||||
|
if (!this.initialized) throw new Error('Not initialized');
|
||||||
|
return this.storage.getIdentityKeyPair().then((kp) => {
|
||||||
|
if (!kp) throw new Error('Identity not yet generated');
|
||||||
|
return kp.signingPublicKey;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `@shade/files` namespace — high-level entry point for E2EE filesystem
|
* `@shade/files` namespace — high-level entry point for E2EE filesystem
|
||||||
* RPC. Lazily creates the underlying channel + streams bridges on first
|
* RPC. Lazily creates the underlying channel + streams bridges on first
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
MemoryPrekeyStore,
|
MemoryPrekeyStore,
|
||||||
PrekeyServerEvents,
|
PrekeyServerEvents,
|
||||||
} from '@shade/server';
|
} from '@shade/server';
|
||||||
import { SubtleCryptoProvider } from '@shade/crypto-web';
|
import { SubtleCryptoProvider, MemoryStorage } from '@shade/crypto-web';
|
||||||
|
|
||||||
const crypto = new SubtleCryptoProvider();
|
const crypto = new SubtleCryptoProvider();
|
||||||
|
|
||||||
@@ -182,6 +182,26 @@ describe('createShade — happy path', () => {
|
|||||||
const newFp = await alice.fingerprint;
|
const newFp = await alice.fingerprint;
|
||||||
expect(newFp).not.toBe(oldFp);
|
expect(newFp).not.toBe(oldFp);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('identityPublicKey exposes the device Ed25519 key and tracks rotation', async () => {
|
||||||
|
const storage = new MemoryStorage();
|
||||||
|
alice = await createShade({ prekeyServer: server.url, address: 'alice', storage });
|
||||||
|
|
||||||
|
const pk = await alice.identityPublicKey;
|
||||||
|
expect(pk).toBeInstanceOf(Uint8Array);
|
||||||
|
expect(pk.length).toBe(32);
|
||||||
|
|
||||||
|
// Matches what the underlying storage holds
|
||||||
|
const stored = await storage.getIdentityKeyPair();
|
||||||
|
expect(stored).not.toBeNull();
|
||||||
|
expect(pk).toEqual(stored!.signingPublicKey);
|
||||||
|
|
||||||
|
// Reflects the new key after rotate (acceptance criteria #3)
|
||||||
|
await alice.rotate();
|
||||||
|
const pkAfter = await alice.identityPublicKey;
|
||||||
|
expect(pkAfter.length).toBe(32);
|
||||||
|
expect(pkAfter).not.toEqual(pk);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('createShade — validation', () => {
|
describe('createShade — validation', () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/server",
|
"name": "@shade/server",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/storage-encrypted",
|
"name": "@shade/storage-encrypted",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/storage-indexeddb",
|
"name": "@shade/storage-indexeddb",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/storage-postgres",
|
"name": "@shade/storage-postgres",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/storage-sqlite",
|
"name": "@shade/storage-sqlite",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/streams",
|
"name": "@shade/streams",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/transfer",
|
"name": "@shade/transfer",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/transport-bridge",
|
"name": "@shade/transport-bridge",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/transport-webrtc",
|
"name": "@shade/transport-webrtc",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/transport",
|
"name": "@shade/transport",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shade/widgets",
|
"name": "@shade/widgets",
|
||||||
"version": "4.3.0",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
Reference in New Issue
Block a user