sliftutils 1.7.54 → 1.7.56

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.
@@ -37,11 +37,11 @@ export declare class SourceWrapper {
37
37
  private pings;
38
38
  private pingTimer;
39
39
  private loggedConnected;
40
- /** Starts measuring this source's latency (for variable-shard target preference). Only hosted remotes are pinged; our own local server counts as 0, everything else as Infinity. */
40
+ /** Starts measuring this source's latency, which decides which hosts reads and variable-shard writes prefer. Hosted remotes ping over their API connection; URL-only sources (read-only mode, backblaze without credentials) probe over plain HTTPS instead - the client NEEDS their latency too, or it cannot rank the hosts it reads from. Our own local server counts as 0; only sources with neither form stay Infinity. */
41
41
  startPinging(): void;
42
42
  /** Seeds the latency estimate before the first ping lands (e.g. from the initial routing fetch), so variable-shard picking has something immediately. Real pings take over from the first measurement on. */
43
43
  seedLatency(ms: number): void;
44
- /** Median of the recent pings. Sources that can't be pinged sort last (Infinity), except our own in-process server, which is the best possible target (0). */
44
+ /** Median of the recent pings (API or URL-form, whichever this source measures). Sources with no measurements yet sort last (Infinity), except our own in-process server, which is the best possible target (0). */
45
45
  getLatency(): number;
46
46
  /** Writes always go through the API, so a permission error throws to the caller on every write (and access granted in the meantime is picked up automatically). */
47
47
  write<T>(run: (archives: IArchives) => Promise<T>): Promise<T>;
@@ -204,16 +204,30 @@ export class SourceWrapper {
204
204
  private pingTimer: ReturnType<typeof setInterval> | undefined;
205
205
  private loggedConnected = false;
206
206
 
207
- /** Starts measuring this source's latency (for variable-shard target preference). Only hosted remotes are pinged; our own local server counts as 0, everything else as Infinity. */
207
+ /** Starts measuring this source's latency, which decides which hosts reads and variable-shard writes prefer. Hosted remotes ping over their API connection; URL-only sources (read-only mode, backblaze without credentials) probe over plain HTTPS instead - the client NEEDS their latency too, or it cannot rank the hosts it reads from. Our own local server counts as 0; only sources with neither form stay Infinity. */
208
208
  public startPinging(): void {
209
- const remote = this.remote;
210
- if (!remote || this.pingTimer || this.disposed) return;
211
- // Latency only decides which shard a variable-shard write materializes into, and a source whose window has passed never receives writes - so measuring it is pointless traffic
209
+ if (this.pingTimer || this.disposed) return;
210
+ // A source whose window has passed never receives reads or writes - measuring it is pointless traffic
212
211
  if (this.config.validWindow[1] <= Date.now()) return;
212
+ const remote = this.remote;
213
+ const url = this.url;
214
+ let probe: () => Promise<void>;
215
+ if (remote) {
216
+ probe = async () => {
217
+ await remote.ping();
218
+ };
219
+ } else if (url) {
220
+ // A 1-byte ranged read of the routing file transfers essentially nothing, and unlike an explicit OPTIONS request it needs no CORS preflight (backblaze's CORS rules only allow download operations) - so it works from the browser, which is exactly where URL-only mode runs
221
+ probe = async () => {
222
+ await url.getInfo(ROUTING_FILE);
223
+ };
224
+ } else {
225
+ return;
226
+ }
213
227
  let measure = async () => {
214
228
  let start = Date.now();
215
229
  try {
216
- await remote.ping();
230
+ await probe();
217
231
  } catch {
218
232
  // A failed ping is also our earliest down-detection
219
233
  this.noteFailure();
@@ -241,12 +255,10 @@ export class SourceWrapper {
241
255
  this.pings.push(ms);
242
256
  }
243
257
 
244
- /** Median of the recent pings. Sources that can't be pinged sort last (Infinity), except our own in-process server, which is the best possible target (0). */
258
+ /** Median of the recent pings (API or URL-form, whichever this source measures). Sources with no measurements yet sort last (Infinity), except our own in-process server, which is the best possible target (0). */
245
259
  public getLatency(): number {
246
- if (!this.remote) {
247
- if (this.config.type === "remote" && this.api) return 0;
248
- return Infinity;
249
- }
260
+ // Our own in-process server: hosted (type remote), api present, but no remote connection - it IS us
261
+ if (!this.remote && this.config.type === "remote" && this.api) return 0;
250
262
  if (!this.pings.length) return Infinity;
251
263
  let sorted = [...this.pings];
252
264
  sort(sorted, x => x);
@@ -59,10 +59,11 @@ export type AccessState = {
59
59
 
60
60
  const sessions = new Map<string, string>();
61
61
 
62
+ // We must never serve anything that can be evaluated as code (html, js - and svg, which can embed <script> and runs it when visited as a document). Otherwise a user could host a file and, by visiting it, run it on our domain - giving them access to all of our keys and cookies. PDF stays: browser PDF viewers run any embedded PDF scripting in a sandbox with no access to the serving origin's cookies or DOM.
62
63
  const CONTENT_TYPES: { [ext: string]: string } = {
63
- html: "text/html; charset=utf-8", js: "text/javascript; charset=utf-8", css: "text/css; charset=utf-8", json: "application/json; charset=utf-8",
64
+ css: "text/css; charset=utf-8", json: "application/json; charset=utf-8",
64
65
  txt: "text/plain; charset=utf-8", png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif",
65
- svg: "image/svg+xml", webp: "image/webp", mp4: "video/mp4", webm: "video/webm",
66
+ webp: "image/webp", mp4: "video/mp4", webm: "video/webm",
66
67
  mp3: "audio/mpeg", wav: "audio/wav", pdf: "application/pdf",
67
68
  };
68
69
 
@@ -510,6 +510,7 @@ function buildBucket(account: string, bucketName: string, routing: RemoteConfig,
510
510
  validWindow: spec.validWindow,
511
511
  route: spec.route,
512
512
  noFullSync: spec.noFullSync,
513
+ intermediate: spec.sourceConfig?.intermediate,
513
514
  identity: sourceIdentity(spec.sourceConfig),
514
515
  }));
515
516
  store = new BlobStore(folder, sources, {
@@ -609,6 +610,7 @@ async function checkRoutingChanged(account: string, bucketName: string, config?:
609
610
  validWindow: spec.validWindow,
610
611
  route: spec.route,
611
612
  noFullSync: spec.noFullSync,
613
+ intermediate: sourceConfig.intermediate,
612
614
  create: () => createApiArchives(sourceConfig),
613
615
  };
614
616
  }));