@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.
package/dist/connect.d.ts CHANGED
@@ -1706,6 +1706,14 @@ declare class FileStorage {
1706
1706
  data: Uint8Array;
1707
1707
  stat: FileStat;
1708
1708
  }>;
1709
+ /**
1710
+ * Streaming inverse of {@link writeFileToShards}: yields each chunk's
1711
+ * decrypted plaintext in order WITHOUT concatenating. Peak memory stays at
1712
+ * ~one chunk, so multi-GB files (which overflow a single `Uint8Array`) can be
1713
+ * piped straight to a file/socket. Same shard sourcing (cache → peers →
1714
+ * origin) and per-chunk auth as {@link readFileFromShards}.
1715
+ */
1716
+ readChunksFromShards(manifest: FileManifest): AsyncGenerator<Uint8Array>;
1709
1717
  /** As {@link writeFileToShards}, plus POSTs the manifest to `input.spaceId`. */
1710
1718
  writeFile(input: WriteFileInput): Promise<FileStat>;
1711
1719
  /** As {@link readFileFromShards}, but fetches the manifest from a space first. */
@@ -4146,6 +4154,11 @@ declare class Space {
4146
4154
  data: Uint8Array;
4147
4155
  stat: FileStat;
4148
4156
  }>;
4157
+ /** Streaming read: async-iterate a stored file's decrypted chunks in order,
4158
+ * never holding the whole file in memory. Use for multi-GB files (a single
4159
+ * `Uint8Array` can't hold them) — e.g. a transcoder piping the raw to a temp
4160
+ * file. Same peer/origin shard sourcing as {@link getFile}. */
4161
+ getFileStream(manifest: FileManifest): AsyncGenerator<Uint8Array>;
4149
4162
  /** Declare a file's shards (ref++ + meter) for a manifest already stored —
4150
4163
  * used to back-fill accounting for files written before commit existed, or to
4151
4164
  * register a manifest received via sharing. `putFile` already commits new
@@ -2976,7 +2976,7 @@ function base64UrlDecode(s) {
2976
2976
  * expect (and isn't serving a stale cached bundle). Keep this in sync with
2977
2977
  * `package.json` `version`.
2978
2978
  */
2979
- const VERSION = "0.10.8-alpha.0";
2979
+ const VERSION = "0.10.10-alpha.0";
2980
2980
 
2981
2981
  /**
2982
2982
  * `HttpClient` — the one place the unified {@link Client} talks to the
@@ -3499,7 +3499,13 @@ async function createPasskeyWithPrf(opts) {
3499
3499
  user: { id: rand(16), name: opts.username, displayName: opts.username },
3500
3500
  challenge: rand(32),
3501
3501
  pubKeyCredParams: [{ type: "public-key", alg: -7 }, { type: "public-key", alg: -257 }],
3502
- authenticatorSelection: { residentKey: "preferred", userVerification: "preferred" },
3502
+ // Force the PLATFORM authenticator (Touch ID / Windows Hello / iCloud
3503
+ // Keychain). Our factor needs the PRF extension to wrap the seed, and
3504
+ // third-party browser password-manager extensions register as cross-platform
3505
+ // authenticators that often DON'T implement PRF — letting one handle the
3506
+ // request is the main cause of "didn't return a PRF result" failures. Pinning
3507
+ // to platform routes it to the OS authenticator, which supports PRF.
3508
+ authenticatorSelection: { authenticatorAttachment: "platform", residentKey: "preferred", userVerification: "preferred" },
3503
3509
  extensions: { prf: {} },
3504
3510
  },
3505
3511
  }));
@@ -5826,31 +5832,37 @@ class FileStorage {
5826
5832
  */
5827
5833
  async writeFileToShards(input) {
5828
5834
  await this.codec.ready(this.rsWasmModule);
5829
- const data = await toUint8Array(input.data);
5830
5835
  const fileId = input.fileId ?? generateId();
5831
5836
  const createdAt = Date.now();
5832
5837
  const chunks = [];
5833
- const totalChunks = Math.max(1, Math.ceil(data.length / this.chunkSize));
5838
+ // Read the source per-chunk so the peak memory footprint stays at
5839
+ // ~chunkSize even for multi-GB files. A Blob/File is sliced lazily (only
5840
+ // the current chunk's bytes are materialized) — reading the whole thing
5841
+ // up front would blow past the browser's ~2GB ArrayBuffer cap and throw
5842
+ // "the file could not be read". A caller that already has the bytes in a
5843
+ // Uint8Array just gets zero-copy subarray views.
5844
+ const asBytes = input.data instanceof Uint8Array ? input.data : null;
5845
+ const size = asBytes ? asBytes.length : input.data.size;
5846
+ const readChunk = async (start, end) => asBytes ? asBytes.subarray(start, end)
5847
+ : new Uint8Array(await input.data.slice(start, end).arrayBuffer());
5848
+ const totalChunks = Math.max(1, Math.ceil(size / this.chunkSize));
5834
5849
  input.onProgress?.(0, totalChunks);
5835
- // Slicing + encrypting + encoding + uploading is done per-chunk so
5836
- // the peak memory footprint stays at ~chunkSize even for huge files.
5837
- // Within a chunk, the shard uploads are parallelized up to
5838
- // `this.concurrency`.
5839
- for (let chunkIndex = 0; chunkIndex * this.chunkSize < Math.max(1, data.length); chunkIndex++) {
5850
+ // Within a chunk, the shard uploads are parallelized up to `this.concurrency`.
5851
+ for (let chunkIndex = 0; chunkIndex * this.chunkSize < Math.max(1, size); chunkIndex++) {
5840
5852
  const start = chunkIndex * this.chunkSize;
5841
- const end = Math.min(start + this.chunkSize, data.length);
5842
- const plaintext = data.subarray(start, end);
5853
+ const end = Math.min(start + this.chunkSize, size);
5854
+ const plaintext = await readChunk(start, end);
5843
5855
  chunks.push(await this.writeChunk(plaintext, chunkIndex));
5844
5856
  input.onProgress?.(chunks.length, totalChunks);
5845
5857
  // Stop after one iteration if the file is empty — we still emit a
5846
5858
  // single zero-length chunk so the manifest has a stable shape.
5847
- if (data.length === 0)
5859
+ if (size === 0)
5848
5860
  break;
5849
5861
  }
5850
5862
  const manifest = {
5851
5863
  id: fileId,
5852
5864
  name: input.metadata.name,
5853
- size: data.length,
5865
+ size,
5854
5866
  type: input.metadata.type,
5855
5867
  path: input.metadata.path,
5856
5868
  lastModified: input.metadata.lastModified ?? createdAt,
@@ -5886,6 +5898,19 @@ class FileStorage {
5886
5898
  }
5887
5899
  return { data: out, stat: manifestToStat(manifest) };
5888
5900
  }
5901
+ /**
5902
+ * Streaming inverse of {@link writeFileToShards}: yields each chunk's
5903
+ * decrypted plaintext in order WITHOUT concatenating. Peak memory stays at
5904
+ * ~one chunk, so multi-GB files (which overflow a single `Uint8Array`) can be
5905
+ * piped straight to a file/socket. Same shard sourcing (cache → peers →
5906
+ * origin) and per-chunk auth as {@link readFileFromShards}.
5907
+ */
5908
+ async *readChunksFromShards(manifest) {
5909
+ await this.codec.ready(this.rsWasmModule);
5910
+ for (const chunkManifest of [...manifest.chunks].sort((a, b) => a.chunkIndex - b.chunkIndex)) {
5911
+ yield await this.readChunk(chunkManifest);
5912
+ }
5913
+ }
5889
5914
  // -------------------------------------------------------------------------
5890
5915
  // Space-backed convenience wrappers — encrypt + upload + write manifest.
5891
5916
  // Require a `SharedSpaceClient` to have been provided at construction.
@@ -6019,14 +6044,6 @@ class FileStorage {
6019
6044
  await Promise.all(workers);
6020
6045
  }
6021
6046
  }
6022
- /** Normalize `data` to a flat `Uint8Array`. */
6023
- async function toUint8Array(data) {
6024
- if (data instanceof Uint8Array)
6025
- return data;
6026
- // Blob covers File too (File extends Blob in every runtime we target).
6027
- const ab = await data.arrayBuffer();
6028
- return new Uint8Array(ab);
6029
- }
6030
6047
 
6031
6048
  /**
6032
6049
  * HTTP client for the gated file-manifest store on a SharedSpace
@@ -7342,6 +7359,13 @@ class Space {
7342
7359
  async getFile(manifest) {
7343
7360
  return this.fileStorage().readFileFromShards(manifest);
7344
7361
  }
7362
+ /** Streaming read: async-iterate a stored file's decrypted chunks in order,
7363
+ * never holding the whole file in memory. Use for multi-GB files (a single
7364
+ * `Uint8Array` can't hold them) — e.g. a transcoder piping the raw to a temp
7365
+ * file. Same peer/origin shard sourcing as {@link getFile}. */
7366
+ getFileStream(manifest) {
7367
+ return this.fileStorage().readChunksFromShards(manifest);
7368
+ }
7345
7369
  /** Declare a file's shards (ref++ + meter) for a manifest already stored —
7346
7370
  * used to back-fill accounting for files written before commit existed, or to
7347
7371
  * register a manifest received via sharing. `putFile` already commits new