@muhkoo/connect 0.10.4-alpha.0 → 0.10.6-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/index.js +61 -16
- package/dist/browser/index.js.map +1 -1
- package/dist/connect.d.ts +23 -6
- package/dist/server/index.js +61 -16
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/browser/index.js
CHANGED
|
@@ -5057,7 +5057,7 @@ function base64UrlDecode(s) {
|
|
|
5057
5057
|
* expect (and isn't serving a stale cached bundle). Keep this in sync with
|
|
5058
5058
|
* `package.json` `version`.
|
|
5059
5059
|
*/
|
|
5060
|
-
const VERSION = "0.10.
|
|
5060
|
+
const VERSION = "0.10.6-alpha.0";
|
|
5061
5061
|
|
|
5062
5062
|
/**
|
|
5063
5063
|
* `HttpClient` — the one place the unified {@link Client} talks to the
|
|
@@ -7599,12 +7599,46 @@ class Space {
|
|
|
7599
7599
|
}
|
|
7600
7600
|
// -- room-scoped files (no websocket needed) -------------------------------
|
|
7601
7601
|
async putFile(file, metadata, opts = {}) {
|
|
7602
|
-
|
|
7602
|
+
const result = await this.fileStorage(opts.backgroundUpload)
|
|
7603
7603
|
.writeFileToShards({ data: file, metadata, onProgress: opts.onProgress });
|
|
7604
|
+
// Declare this manifest's shards to the accelerator: attributes the stored
|
|
7605
|
+
// bytes to the owning app (so storage is never unmetered) and reference-
|
|
7606
|
+
// counts the shards for GC. Public shard hashes only — the per-chunk keys
|
|
7607
|
+
// stay inside the manifest, which the caller keeps in a vault/Space.
|
|
7608
|
+
await this.commitShards(result.manifest, "commit");
|
|
7609
|
+
return result;
|
|
7604
7610
|
}
|
|
7605
7611
|
async getFile(manifest) {
|
|
7606
7612
|
return this.fileStorage().readFileFromShards(manifest);
|
|
7607
7613
|
}
|
|
7614
|
+
/** Release a file's shards (ref--) when the owner deletes or unshares it. At
|
|
7615
|
+
* zero references a shard enters a retention window, then is GC'd. Idempotent-
|
|
7616
|
+
* ish: safe to call once per manifest the owner drops. */
|
|
7617
|
+
async releaseFile(manifest) {
|
|
7618
|
+
await this.commitShards(manifest, "release");
|
|
7619
|
+
}
|
|
7620
|
+
/** Commit/release a manifest's shards with the accelerator (metering + refcount). */
|
|
7621
|
+
async commitShards(manifest, action) {
|
|
7622
|
+
const hashes = manifest.chunks.flatMap((c) => c.shardHashes);
|
|
7623
|
+
if (hashes.length === 0)
|
|
7624
|
+
return;
|
|
7625
|
+
const body = JSON.stringify({ hashes, bytes: manifest.size });
|
|
7626
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
7627
|
+
try {
|
|
7628
|
+
const res = await this.deps.fetch(`${this.deps.httpBaseUrl}/api/storage/${action}`, {
|
|
7629
|
+
method: "POST", headers: { "Content-Type": "application/json" }, body,
|
|
7630
|
+
});
|
|
7631
|
+
if (res.ok)
|
|
7632
|
+
return;
|
|
7633
|
+
}
|
|
7634
|
+
catch { /* retry */ }
|
|
7635
|
+
await new Promise((r) => setTimeout(r, 400 * (attempt + 1)));
|
|
7636
|
+
}
|
|
7637
|
+
// Best-effort: a missed commit leaves the shards to orphan-GC (still not
|
|
7638
|
+
// durable-unmetered long-term); a missed release just delays GC. Reconciled
|
|
7639
|
+
// by the platform sweep. Don't fail the caller's write on a metering hiccup.
|
|
7640
|
+
console.warn(`[muhkoo] storage ${action} did not confirm for ${hashes.length} shard(s)`);
|
|
7641
|
+
}
|
|
7608
7642
|
fileStorage(backgroundUpload = false) {
|
|
7609
7643
|
// Global, content-addressed shard store — same store `client.storage`
|
|
7610
7644
|
// uses, so a file shared into a channel resolves from anywhere by
|
|
@@ -8739,10 +8773,13 @@ class PeerExchange {
|
|
|
8739
8773
|
* to Space membership. The sender (`from`) is server-stamped and can't be
|
|
8740
8774
|
* spoofed; the payload is opaque to the server.
|
|
8741
8775
|
*
|
|
8742
|
-
*
|
|
8743
|
-
*
|
|
8744
|
-
*
|
|
8745
|
-
*
|
|
8776
|
+
* Peers are identified by a unique **peer id** (per device/connection), carried
|
|
8777
|
+
* in the envelope's `from`/`to` — NOT by the server-stamped member id. This is
|
|
8778
|
+
* essential: one user's own devices share a member id, so the server's `from`
|
|
8779
|
+
* can't tell them apart (and would make each treat the others' signals as its
|
|
8780
|
+
* own echo). We therefore route entirely on the envelope peer ids and broadcast
|
|
8781
|
+
* to the whole Space, filtering client-side. The server `from` still guarantees
|
|
8782
|
+
* the sender is an authenticated Space member.
|
|
8746
8783
|
*/
|
|
8747
8784
|
const P2P_SUBJECT = "__p2p__";
|
|
8748
8785
|
class SpaceSignaler {
|
|
@@ -8754,11 +8791,11 @@ class SpaceSignaler {
|
|
|
8754
8791
|
}
|
|
8755
8792
|
/** Send a signal to one peer (or `"*"`). */
|
|
8756
8793
|
send(to, kind, data) {
|
|
8757
|
-
const env = { to, kind, data };
|
|
8758
|
-
//
|
|
8759
|
-
//
|
|
8760
|
-
//
|
|
8761
|
-
this.space.sendEphemeral(P2P_SUBJECT, env
|
|
8794
|
+
const env = { from: this.myId, to, kind, data };
|
|
8795
|
+
// Always broadcast to the Space: the server can't unicast to a specific
|
|
8796
|
+
// DEVICE (a user's devices share a member id), so we route client-side on
|
|
8797
|
+
// the envelope peer id. Signaling frames are tiny.
|
|
8798
|
+
this.space.sendEphemeral(P2P_SUBJECT, env);
|
|
8762
8799
|
}
|
|
8763
8800
|
/** Announce/withdraw P2P availability to all Space members. */
|
|
8764
8801
|
broadcast(kind, data) {
|
|
@@ -8766,18 +8803,22 @@ class SpaceSignaler {
|
|
|
8766
8803
|
}
|
|
8767
8804
|
/**
|
|
8768
8805
|
* Subscribe to inbound signals addressed to us (or broadcast). Skips our own
|
|
8769
|
-
* echoes. Returns an unsubscribe function.
|
|
8806
|
+
* echoes. Returns an unsubscribe function. Routing is entirely by the envelope
|
|
8807
|
+
* peer id (`env.from`/`env.to`), so a user's own devices — which share the
|
|
8808
|
+
* server-stamped `e.from` — are correctly distinguished.
|
|
8770
8809
|
*/
|
|
8771
8810
|
onSignal(cb) {
|
|
8772
8811
|
return this.space.onEphemeral((e) => {
|
|
8773
|
-
if (e.subject !== P2P_SUBJECT
|
|
8812
|
+
if (e.subject !== P2P_SUBJECT)
|
|
8774
8813
|
return;
|
|
8775
8814
|
const env = e.data;
|
|
8776
|
-
if (!env || typeof env.kind !== "string")
|
|
8815
|
+
if (!env || typeof env.kind !== "string" || typeof env.from !== "string")
|
|
8777
8816
|
return;
|
|
8817
|
+
if (env.from === this.myId)
|
|
8818
|
+
return; // our own device's echo
|
|
8778
8819
|
if (env.to !== this.myId && env.to !== "*")
|
|
8779
8820
|
return;
|
|
8780
|
-
cb(
|
|
8821
|
+
cb(env.from, env.kind, env.data);
|
|
8781
8822
|
});
|
|
8782
8823
|
}
|
|
8783
8824
|
}
|
|
@@ -9554,6 +9595,10 @@ class SpaceNamespace {
|
|
|
9554
9595
|
spaceCache;
|
|
9555
9596
|
/** Spaces built this session, by id — used to route offline send replays. */
|
|
9556
9597
|
openSpaces = new Map();
|
|
9598
|
+
/** Unique id for THIS device/session — the P2P peer identity. A user's own
|
|
9599
|
+
* devices share a member id, so the mesh MUST distinguish them by this or a
|
|
9600
|
+
* device treats its siblings' signals as its own echo and never connects. */
|
|
9601
|
+
deviceId = `peer-${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)}`;
|
|
9557
9602
|
constructor(deps) {
|
|
9558
9603
|
this.deps = deps;
|
|
9559
9604
|
if (deps.offline?.enabled) {
|
|
@@ -9752,7 +9797,7 @@ class SpaceNamespace {
|
|
|
9752
9797
|
if (this.deps.p2p?.enabled && isP2pCapable()) {
|
|
9753
9798
|
space.attachPeerNetwork(new PeerNetwork({
|
|
9754
9799
|
space,
|
|
9755
|
-
myId:
|
|
9800
|
+
myId: this.deviceId, // per-device peer id, NOT the shared member id
|
|
9756
9801
|
workerFactory: this.deps.p2p.workerFactory,
|
|
9757
9802
|
iceServers: this.deps.p2p.iceServers,
|
|
9758
9803
|
maxPeers: this.deps.p2p.maxPeers,
|