@muhkoo/connect 0.10.8-alpha.0 → 0.10.10-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.
@@ -4120,31 +4120,37 @@ class FileStorage {
4120
4120
  */
4121
4121
  async writeFileToShards(input) {
4122
4122
  await this.codec.ready(this.rsWasmModule);
4123
- const data = await toUint8Array(input.data);
4124
4123
  const fileId = input.fileId ?? generateId();
4125
4124
  const createdAt = Date.now();
4126
4125
  const chunks = [];
4127
- const totalChunks = Math.max(1, Math.ceil(data.length / this.chunkSize));
4126
+ // Read the source per-chunk so the peak memory footprint stays at
4127
+ // ~chunkSize even for multi-GB files. A Blob/File is sliced lazily (only
4128
+ // the current chunk's bytes are materialized) — reading the whole thing
4129
+ // up front would blow past the browser's ~2GB ArrayBuffer cap and throw
4130
+ // "the file could not be read". A caller that already has the bytes in a
4131
+ // Uint8Array just gets zero-copy subarray views.
4132
+ const asBytes = input.data instanceof Uint8Array ? input.data : null;
4133
+ const size = asBytes ? asBytes.length : input.data.size;
4134
+ const readChunk = async (start, end) => asBytes ? asBytes.subarray(start, end)
4135
+ : new Uint8Array(await input.data.slice(start, end).arrayBuffer());
4136
+ const totalChunks = Math.max(1, Math.ceil(size / this.chunkSize));
4128
4137
  input.onProgress?.(0, totalChunks);
4129
- // Slicing + encrypting + encoding + uploading is done per-chunk so
4130
- // the peak memory footprint stays at ~chunkSize even for huge files.
4131
- // Within a chunk, the shard uploads are parallelized up to
4132
- // `this.concurrency`.
4133
- for (let chunkIndex = 0; chunkIndex * this.chunkSize < Math.max(1, data.length); chunkIndex++) {
4138
+ // Within a chunk, the shard uploads are parallelized up to `this.concurrency`.
4139
+ for (let chunkIndex = 0; chunkIndex * this.chunkSize < Math.max(1, size); chunkIndex++) {
4134
4140
  const start = chunkIndex * this.chunkSize;
4135
- const end = Math.min(start + this.chunkSize, data.length);
4136
- const plaintext = data.subarray(start, end);
4141
+ const end = Math.min(start + this.chunkSize, size);
4142
+ const plaintext = await readChunk(start, end);
4137
4143
  chunks.push(await this.writeChunk(plaintext, chunkIndex));
4138
4144
  input.onProgress?.(chunks.length, totalChunks);
4139
4145
  // Stop after one iteration if the file is empty — we still emit a
4140
4146
  // single zero-length chunk so the manifest has a stable shape.
4141
- if (data.length === 0)
4147
+ if (size === 0)
4142
4148
  break;
4143
4149
  }
4144
4150
  const manifest = {
4145
4151
  id: fileId,
4146
4152
  name: input.metadata.name,
4147
- size: data.length,
4153
+ size,
4148
4154
  type: input.metadata.type,
4149
4155
  path: input.metadata.path,
4150
4156
  lastModified: input.metadata.lastModified ?? createdAt,
@@ -4180,6 +4186,19 @@ class FileStorage {
4180
4186
  }
4181
4187
  return { data: out, stat: manifestToStat(manifest) };
4182
4188
  }
4189
+ /**
4190
+ * Streaming inverse of {@link writeFileToShards}: yields each chunk's
4191
+ * decrypted plaintext in order WITHOUT concatenating. Peak memory stays at
4192
+ * ~one chunk, so multi-GB files (which overflow a single `Uint8Array`) can be
4193
+ * piped straight to a file/socket. Same shard sourcing (cache → peers →
4194
+ * origin) and per-chunk auth as {@link readFileFromShards}.
4195
+ */
4196
+ async *readChunksFromShards(manifest) {
4197
+ await this.codec.ready(this.rsWasmModule);
4198
+ for (const chunkManifest of [...manifest.chunks].sort((a, b) => a.chunkIndex - b.chunkIndex)) {
4199
+ yield await this.readChunk(chunkManifest);
4200
+ }
4201
+ }
4183
4202
  // -------------------------------------------------------------------------
4184
4203
  // Space-backed convenience wrappers — encrypt + upload + write manifest.
4185
4204
  // Require a `SharedSpaceClient` to have been provided at construction.
@@ -4313,14 +4332,6 @@ class FileStorage {
4313
4332
  await Promise.all(workers);
4314
4333
  }
4315
4334
  }
4316
- /** Normalize `data` to a flat `Uint8Array`. */
4317
- async function toUint8Array(data) {
4318
- if (data instanceof Uint8Array)
4319
- return data;
4320
- // Blob covers File too (File extends Blob in every runtime we target).
4321
- const ab = await data.arrayBuffer();
4322
- return new Uint8Array(ab);
4323
- }
4324
4335
 
4325
4336
  /**
4326
4337
  * HTTP client for the gated file-manifest store on a SharedSpace
@@ -5057,7 +5068,7 @@ function base64UrlDecode(s) {
5057
5068
  * expect (and isn't serving a stale cached bundle). Keep this in sync with
5058
5069
  * `package.json` `version`.
5059
5070
  */
5060
- const VERSION = "0.10.8-alpha.0";
5071
+ const VERSION = "0.10.10-alpha.0";
5061
5072
 
5062
5073
  /**
5063
5074
  * `HttpClient` — the one place the unified {@link Client} talks to the
@@ -5580,7 +5591,13 @@ async function createPasskeyWithPrf(opts) {
5580
5591
  user: { id: rand(16), name: opts.username, displayName: opts.username },
5581
5592
  challenge: rand(32),
5582
5593
  pubKeyCredParams: [{ type: "public-key", alg: -7 }, { type: "public-key", alg: -257 }],
5583
- authenticatorSelection: { residentKey: "preferred", userVerification: "preferred" },
5594
+ // Force the PLATFORM authenticator (Touch ID / Windows Hello / iCloud
5595
+ // Keychain). Our factor needs the PRF extension to wrap the seed, and
5596
+ // third-party browser password-manager extensions register as cross-platform
5597
+ // authenticators that often DON'T implement PRF — letting one handle the
5598
+ // request is the main cause of "didn't return a PRF result" failures. Pinning
5599
+ // to platform routes it to the OS authenticator, which supports PRF.
5600
+ authenticatorSelection: { authenticatorAttachment: "platform", residentKey: "preferred", userVerification: "preferred" },
5584
5601
  extensions: { prf: {} },
5585
5602
  },
5586
5603
  }));
@@ -7611,6 +7628,13 @@ class Space {
7611
7628
  async getFile(manifest) {
7612
7629
  return this.fileStorage().readFileFromShards(manifest);
7613
7630
  }
7631
+ /** Streaming read: async-iterate a stored file's decrypted chunks in order,
7632
+ * never holding the whole file in memory. Use for multi-GB files (a single
7633
+ * `Uint8Array` can't hold them) — e.g. a transcoder piping the raw to a temp
7634
+ * file. Same peer/origin shard sourcing as {@link getFile}. */
7635
+ getFileStream(manifest) {
7636
+ return this.fileStorage().readChunksFromShards(manifest);
7637
+ }
7614
7638
  /** Declare a file's shards (ref++ + meter) for a manifest already stored —
7615
7639
  * used to back-fill accounting for files written before commit existed, or to
7616
7640
  * register a manifest received via sharing. `putFile` already commits new