@mega-yfue/eufy-sdk 0.2.0-beta.13 → 0.2.0-beta.15
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/index.js +429 -197
- package/dist/index.js.map +4 -4
- package/dist/transport/http/decodeImageV2.d.ts +8 -14
- package/dist/transport/http/jpeg-scan.d.ts +59 -0
- package/dist/transport/http/media-download.d.ts +3 -0
- package/dist/transport/http/mega-client.d.ts +7 -1
- package/dist/transport/media-failure.d.ts +48 -0
- package/dist/transport/p2p/command-router.d.ts +4 -2
- package/dist/transport/p2p/live-trace.d.ts +23 -5
- package/dist/transport/p2p/p2p-session.d.ts +9 -0
- package/dist/transport/stored-image-cache.d.ts +7 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -217,18 +217,33 @@ BLOCKED_ADDRESSES.addAddress("::1", "ipv6");
|
|
|
217
217
|
BLOCKED_ADDRESSES.addSubnet("fc00::", 7, "ipv6");
|
|
218
218
|
BLOCKED_ADDRESSES.addSubnet("fe80::", 10, "ipv6");
|
|
219
219
|
var MediaDownloadError = class extends Error {
|
|
220
|
+
mediaFailure;
|
|
221
|
+
status;
|
|
222
|
+
constructor(message, mediaFailure, status) {
|
|
223
|
+
super(message);
|
|
224
|
+
this.name = "MediaDownloadError";
|
|
225
|
+
this.mediaFailure = mediaFailure;
|
|
226
|
+
if (status !== void 0)
|
|
227
|
+
this.status = status;
|
|
228
|
+
}
|
|
220
229
|
};
|
|
230
|
+
function mediaFailureError(message, reason, cause) {
|
|
231
|
+
const error = new MediaDownloadError(message, reason);
|
|
232
|
+
if (cause !== void 0)
|
|
233
|
+
error.cause = cause;
|
|
234
|
+
return error;
|
|
235
|
+
}
|
|
221
236
|
var MediaDownloadAuthenticationError = class extends Error {
|
|
222
237
|
};
|
|
223
|
-
function allowedMediaUrl(value, hostPattern) {
|
|
238
|
+
function allowedMediaUrl(value, hostPattern, reason) {
|
|
224
239
|
let url;
|
|
225
240
|
try {
|
|
226
241
|
url = new URL(value);
|
|
227
242
|
} catch {
|
|
228
|
-
throw new MediaDownloadError("Media download rejected");
|
|
243
|
+
throw new MediaDownloadError("Media download rejected", reason);
|
|
229
244
|
}
|
|
230
245
|
if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.port !== "" || isIP(url.hostname) !== 0 || !hostPattern.test(url.hostname)) {
|
|
231
|
-
throw new MediaDownloadError("Media download rejected");
|
|
246
|
+
throw new MediaDownloadError("Media download rejected", reason);
|
|
232
247
|
}
|
|
233
248
|
return url;
|
|
234
249
|
}
|
|
@@ -243,7 +258,7 @@ async function assertPublicResolution(url, resolveHost2, signal) {
|
|
|
243
258
|
try {
|
|
244
259
|
addresses = await Promise.race([resolveHost2(url.hostname), aborted]);
|
|
245
260
|
} catch {
|
|
246
|
-
throw new MediaDownloadError("Media download rejected");
|
|
261
|
+
throw new MediaDownloadError("Media download rejected", "address-not-public");
|
|
247
262
|
} finally {
|
|
248
263
|
signal.removeEventListener("abort", onAbort);
|
|
249
264
|
}
|
|
@@ -251,14 +266,14 @@ async function assertPublicResolution(url, resolveHost2, signal) {
|
|
|
251
266
|
const type = family === 6 ? "ipv6" : family === 4 ? "ipv4" : void 0;
|
|
252
267
|
return type === void 0 || BLOCKED_ADDRESSES.check(address, type);
|
|
253
268
|
})) {
|
|
254
|
-
throw new MediaDownloadError("Media download rejected");
|
|
269
|
+
throw new MediaDownloadError("Media download rejected", "address-not-public");
|
|
255
270
|
}
|
|
256
271
|
}
|
|
257
272
|
var resolveHost = (hostname) => lookup(hostname, { all: true, verbatim: true });
|
|
258
273
|
async function readMediaBody(response) {
|
|
259
274
|
const contentLength = response.headers.get("content-length");
|
|
260
275
|
if (contentLength && /^\d+$/.test(contentLength) && BigInt(contentLength) > BigInt(MEDIA_DOWNLOAD_MAX_BYTES)) {
|
|
261
|
-
throw new MediaDownloadError("Media download rejected");
|
|
276
|
+
throw new MediaDownloadError("Media download rejected", "too-large");
|
|
262
277
|
}
|
|
263
278
|
if (!response.body)
|
|
264
279
|
return Buffer.alloc(0);
|
|
@@ -272,14 +287,14 @@ async function readMediaBody(response) {
|
|
|
272
287
|
bytes2 += value.byteLength;
|
|
273
288
|
if (bytes2 > MEDIA_DOWNLOAD_MAX_BYTES) {
|
|
274
289
|
void reader2.cancel().catch(() => void 0);
|
|
275
|
-
throw new MediaDownloadError("Media download rejected");
|
|
290
|
+
throw new MediaDownloadError("Media download rejected", "too-large");
|
|
276
291
|
}
|
|
277
292
|
chunks.push(value);
|
|
278
293
|
}
|
|
279
294
|
return Buffer.concat(chunks, bytes2);
|
|
280
295
|
}
|
|
281
296
|
async function downloadMediaResource(url, authenticatedHeaders, fetchImpl = fetch, resolver = resolveHost) {
|
|
282
|
-
const original = allowedMediaUrl(url, EUFY_MEDIA_HOST);
|
|
297
|
+
const original = allowedMediaUrl(url, EUFY_MEDIA_HOST, "url-not-allowed");
|
|
283
298
|
const controller = new AbortController();
|
|
284
299
|
const timeout = setTimeout(() => controller.abort(), MEDIA_DOWNLOAD_TIMEOUT_MS);
|
|
285
300
|
try {
|
|
@@ -292,31 +307,33 @@ async function downloadMediaResource(url, authenticatedHeaders, fetchImpl = fetc
|
|
|
292
307
|
if (originalResponse.status >= 300 && originalResponse.status < 400) {
|
|
293
308
|
const location = originalResponse.headers.get("location");
|
|
294
309
|
if (!location)
|
|
295
|
-
throw new MediaDownloadError("Media download rejected");
|
|
296
|
-
const target = allowedMediaUrl(location, EUFY_OBJECT_HOST);
|
|
310
|
+
throw new MediaDownloadError("Media download rejected", "redirect-not-allowed");
|
|
311
|
+
const target = allowedMediaUrl(location, EUFY_OBJECT_HOST, "redirect-not-allowed");
|
|
297
312
|
await assertPublicResolution(target, resolver, controller.signal);
|
|
298
313
|
const redirectedResponse = await fetchImpl(target, { redirect: "manual", signal: controller.signal });
|
|
299
314
|
if (redirectedResponse.status >= 300 && redirectedResponse.status < 400) {
|
|
300
|
-
throw new MediaDownloadError("Media download rejected");
|
|
315
|
+
throw new MediaDownloadError("Media download rejected", "redirect-not-allowed");
|
|
316
|
+
}
|
|
317
|
+
if (redirectedResponse.status !== 200) {
|
|
318
|
+
throw new MediaDownloadError("Media download failed", "http-status", redirectedResponse.status);
|
|
301
319
|
}
|
|
302
|
-
if (redirectedResponse.status !== 200)
|
|
303
|
-
throw new MediaDownloadError("Media download failed");
|
|
304
320
|
return await readMediaBody(redirectedResponse);
|
|
305
321
|
}
|
|
306
322
|
if (originalResponse.status === 401 || originalResponse.status === 403) {
|
|
307
323
|
throw new MediaDownloadAuthenticationError("Media authentication failed");
|
|
308
324
|
}
|
|
309
|
-
if (originalResponse.status !== 200)
|
|
310
|
-
throw new MediaDownloadError("Media download failed");
|
|
325
|
+
if (originalResponse.status !== 200) {
|
|
326
|
+
throw new MediaDownloadError("Media download failed", "http-status", originalResponse.status);
|
|
327
|
+
}
|
|
311
328
|
return await readMediaBody(originalResponse);
|
|
312
329
|
} catch (error) {
|
|
313
330
|
if (controller.signal.aborted)
|
|
314
|
-
throw new MediaDownloadError("Media download timed out");
|
|
331
|
+
throw new MediaDownloadError("Media download timed out", "timeout");
|
|
315
332
|
if (error instanceof MediaDownloadAuthenticationError)
|
|
316
333
|
throw error;
|
|
317
334
|
if (error instanceof MediaDownloadError)
|
|
318
335
|
throw error;
|
|
319
|
-
throw new MediaDownloadError("Media download failed");
|
|
336
|
+
throw new MediaDownloadError("Media download failed", "network");
|
|
320
337
|
} finally {
|
|
321
338
|
clearTimeout(timeout);
|
|
322
339
|
}
|
|
@@ -375,8 +392,228 @@ function randomUserAgent(seed, model) {
|
|
|
375
392
|
// dist/transport/http/decodeImageV1.js
|
|
376
393
|
import { createDecipheriv as createDecipheriv2, createHash as createHash2 } from "node:crypto";
|
|
377
394
|
|
|
395
|
+
// dist/transport/http/jpeg-scan.js
|
|
396
|
+
var DHT = 196;
|
|
397
|
+
var SOS = 218;
|
|
398
|
+
var DRI = 221;
|
|
399
|
+
var RST_FIRST = 208;
|
|
400
|
+
var RST_LAST = 215;
|
|
401
|
+
var LUMA_SAMPLING = { 0: [1, 1], 1: [2, 1], 2: [2, 2] };
|
|
402
|
+
var ScanEnd = class extends Error {
|
|
403
|
+
};
|
|
404
|
+
function readHuffmanTables(segment, into) {
|
|
405
|
+
const end = 2 + (segment[2] << 8 | segment[3]);
|
|
406
|
+
let at = 4;
|
|
407
|
+
while (at < end) {
|
|
408
|
+
const id = segment[at];
|
|
409
|
+
const counts = segment.subarray(at + 1, at + 17);
|
|
410
|
+
let total = 0;
|
|
411
|
+
for (const count2 of counts)
|
|
412
|
+
total += count2;
|
|
413
|
+
const values = segment.slice(at + 17, at + 17 + total);
|
|
414
|
+
const minCode = new Int32Array(17);
|
|
415
|
+
const maxCode = new Int32Array(17).fill(-1);
|
|
416
|
+
const valPtr = new Int32Array(17);
|
|
417
|
+
let code = 0;
|
|
418
|
+
let index = 0;
|
|
419
|
+
for (let length = 1; length <= 16; length++) {
|
|
420
|
+
const count2 = counts[length - 1];
|
|
421
|
+
if (count2 > 0) {
|
|
422
|
+
valPtr[length] = index;
|
|
423
|
+
minCode[length] = code;
|
|
424
|
+
code += count2;
|
|
425
|
+
index += count2;
|
|
426
|
+
maxCode[length] = code - 1;
|
|
427
|
+
}
|
|
428
|
+
code <<= 1;
|
|
429
|
+
}
|
|
430
|
+
into.set(id, { minCode, maxCode, valPtr, values });
|
|
431
|
+
at += 17 + total;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
var BitReader = class {
|
|
435
|
+
/** Byte offset of the next byte to read. */
|
|
436
|
+
position;
|
|
437
|
+
bits = 0;
|
|
438
|
+
count = 0;
|
|
439
|
+
/** Set when the reader stepped over a restart marker — the caller resets its DC predictors. */
|
|
440
|
+
restarted = false;
|
|
441
|
+
data;
|
|
442
|
+
constructor(data, start) {
|
|
443
|
+
this.data = data;
|
|
444
|
+
this.position = start;
|
|
445
|
+
}
|
|
446
|
+
/** The offset the last whole byte ended at — what "how much of the scan did this consume" reads. */
|
|
447
|
+
get consumed() {
|
|
448
|
+
return this.position;
|
|
449
|
+
}
|
|
450
|
+
readBit() {
|
|
451
|
+
if (this.count === 0) {
|
|
452
|
+
if (this.position >= this.data.length)
|
|
453
|
+
throw new ScanEnd("out of data");
|
|
454
|
+
let byte = this.data[this.position++];
|
|
455
|
+
if (byte === 255) {
|
|
456
|
+
const next = this.data[this.position];
|
|
457
|
+
if (next === 0) {
|
|
458
|
+
this.position++;
|
|
459
|
+
} else if (next !== void 0 && next >= RST_FIRST && next <= RST_LAST) {
|
|
460
|
+
this.position++;
|
|
461
|
+
this.restarted = true;
|
|
462
|
+
if (this.position >= this.data.length)
|
|
463
|
+
throw new ScanEnd("out of data");
|
|
464
|
+
byte = this.data[this.position++];
|
|
465
|
+
if (byte === 255)
|
|
466
|
+
throw new ScanEnd("marker after restart");
|
|
467
|
+
} else {
|
|
468
|
+
this.position--;
|
|
469
|
+
throw new ScanEnd("marker");
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
this.bits = byte;
|
|
473
|
+
this.count = 8;
|
|
474
|
+
}
|
|
475
|
+
this.count--;
|
|
476
|
+
return this.bits >> this.count & 1;
|
|
477
|
+
}
|
|
478
|
+
/** Read `length` bits, most significant first. */
|
|
479
|
+
readBits(length) {
|
|
480
|
+
let value = 0;
|
|
481
|
+
for (let i = 0; i < length; i++)
|
|
482
|
+
value = value << 1 | this.readBit();
|
|
483
|
+
return value;
|
|
484
|
+
}
|
|
485
|
+
/** Drop the current byte's remaining bits — what a restart interval boundary does. */
|
|
486
|
+
align() {
|
|
487
|
+
this.count = 0;
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
function decodeHuffman(reader2, table) {
|
|
491
|
+
let code = reader2.readBit();
|
|
492
|
+
for (let length = 1; length <= 16; length++) {
|
|
493
|
+
if (table.maxCode[length] >= 0 && code <= table.maxCode[length]) {
|
|
494
|
+
return table.values[table.valPtr[length] + code - table.minCode[length]];
|
|
495
|
+
}
|
|
496
|
+
code = code << 1 | reader2.readBit();
|
|
497
|
+
}
|
|
498
|
+
throw new ScanEnd("no huffman code of any length");
|
|
499
|
+
}
|
|
500
|
+
function extend(value, length) {
|
|
501
|
+
return value < 1 << length - 1 ? value - (1 << length) + 1 : value;
|
|
502
|
+
}
|
|
503
|
+
function decodeBlock(reader2, component) {
|
|
504
|
+
const dcLength = decodeHuffman(reader2, component.dc);
|
|
505
|
+
if (dcLength > 16)
|
|
506
|
+
throw new ScanEnd("dc magnitude out of range");
|
|
507
|
+
component.pred += dcLength === 0 ? 0 : extend(reader2.readBits(dcLength), dcLength);
|
|
508
|
+
let k = 1;
|
|
509
|
+
while (k < 64) {
|
|
510
|
+
const rs = decodeHuffman(reader2, component.ac);
|
|
511
|
+
const size = rs & 15;
|
|
512
|
+
const run = rs >> 4;
|
|
513
|
+
if (size === 0) {
|
|
514
|
+
if (run !== 15)
|
|
515
|
+
break;
|
|
516
|
+
k += 16;
|
|
517
|
+
} else {
|
|
518
|
+
k += run;
|
|
519
|
+
if (k > 63)
|
|
520
|
+
throw new ScanEnd("ac coefficient index out of range");
|
|
521
|
+
reader2.readBits(size);
|
|
522
|
+
k++;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return component.pred;
|
|
526
|
+
}
|
|
527
|
+
function scanEntropy(tail, subsampling, extraTables) {
|
|
528
|
+
const tables = /* @__PURE__ */ new Map();
|
|
529
|
+
for (const segment of extraTables)
|
|
530
|
+
readHuffmanTables(segment, tables);
|
|
531
|
+
let at = 0;
|
|
532
|
+
let restartInterval = 0;
|
|
533
|
+
let scanStart = -1;
|
|
534
|
+
let assignments = [];
|
|
535
|
+
while (at + 3 < tail.length) {
|
|
536
|
+
if (tail[at] !== 255)
|
|
537
|
+
return null;
|
|
538
|
+
const marker = tail[at + 1];
|
|
539
|
+
const length = tail[at + 2] << 8 | tail[at + 3];
|
|
540
|
+
if (marker === DHT) {
|
|
541
|
+
readHuffmanTables(tail.subarray(at, at + 2 + length), tables);
|
|
542
|
+
} else if (marker === DRI) {
|
|
543
|
+
restartInterval = tail[at + 4] << 8 | tail[at + 5];
|
|
544
|
+
} else if (marker === SOS) {
|
|
545
|
+
const count2 = tail[at + 4];
|
|
546
|
+
assignments = [];
|
|
547
|
+
for (let i = 0; i < count2; i++) {
|
|
548
|
+
const id = tail[at + 5 + i * 2];
|
|
549
|
+
const tableByte = tail[at + 6 + i * 2];
|
|
550
|
+
assignments.push({ id, dc: tableByte >> 4, ac: tableByte & 15 });
|
|
551
|
+
}
|
|
552
|
+
scanStart = at + 2 + length;
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
at += 2 + length;
|
|
556
|
+
}
|
|
557
|
+
if (scanStart < 0 || assignments.length !== 3)
|
|
558
|
+
return null;
|
|
559
|
+
const [lumaH, lumaV] = LUMA_SAMPLING[subsampling] ?? LUMA_SAMPLING[0];
|
|
560
|
+
const components = [];
|
|
561
|
+
for (const [index, assignment] of assignments.entries()) {
|
|
562
|
+
const dc = tables.get(assignment.dc);
|
|
563
|
+
const ac = tables.get(16 | assignment.ac);
|
|
564
|
+
if (!dc || !ac)
|
|
565
|
+
return null;
|
|
566
|
+
components.push({ h: index === 0 ? lumaH : 1, v: index === 0 ? lumaV : 1, dc, ac, pred: 0 });
|
|
567
|
+
}
|
|
568
|
+
const reader2 = new BitReader(tail, scanStart);
|
|
569
|
+
let luma = new Int32Array(1024);
|
|
570
|
+
const lumaBlocks = lumaH * lumaV;
|
|
571
|
+
let mcus = 0;
|
|
572
|
+
let consumed = reader2.consumed;
|
|
573
|
+
let complete = false;
|
|
574
|
+
try {
|
|
575
|
+
for (; ; ) {
|
|
576
|
+
if (restartInterval > 0 && mcus > 0 && mcus % restartInterval === 0) {
|
|
577
|
+
reader2.align();
|
|
578
|
+
for (const component of components)
|
|
579
|
+
component.pred = 0;
|
|
580
|
+
}
|
|
581
|
+
if (reader2.restarted) {
|
|
582
|
+
reader2.restarted = false;
|
|
583
|
+
for (const component of components)
|
|
584
|
+
component.pred = 0;
|
|
585
|
+
}
|
|
586
|
+
let lumaSum = 0;
|
|
587
|
+
for (const component of components) {
|
|
588
|
+
for (let block = 0; block < component.h * component.v; block++) {
|
|
589
|
+
const dc = decodeBlock(reader2, component);
|
|
590
|
+
if (component === components[0])
|
|
591
|
+
lumaSum += dc;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
if (mcus === luma.length) {
|
|
595
|
+
const grown = new Int32Array(luma.length * 2);
|
|
596
|
+
grown.set(luma);
|
|
597
|
+
luma = grown;
|
|
598
|
+
}
|
|
599
|
+
luma[mcus] = Math.round(lumaSum / lumaBlocks);
|
|
600
|
+
mcus++;
|
|
601
|
+
consumed = reader2.consumed;
|
|
602
|
+
if (consumed >= tail.length) {
|
|
603
|
+
complete = true;
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
} catch (error) {
|
|
608
|
+
if (!(error instanceof ScanEnd))
|
|
609
|
+
throw error;
|
|
610
|
+
complete = tail.length - consumed <= 2;
|
|
611
|
+
}
|
|
612
|
+
return { mcus, luma: luma.subarray(0, mcus), complete };
|
|
613
|
+
}
|
|
614
|
+
|
|
378
615
|
// dist/transport/http/decodeImageV2.js
|
|
379
|
-
import { decode as jpegDecode
|
|
616
|
+
import { decode as jpegDecode } from "jpeg-js";
|
|
380
617
|
var V2_PREFIX = "v2_eufysecurity:";
|
|
381
618
|
var DC_CHROMA = Buffer.from([255, 196, 0, 31, 1]);
|
|
382
619
|
var ZIGZAG = [
|
|
@@ -582,35 +819,6 @@ var DHT_AC_LUMA = Buffer.from("ffc400b5100002010303020403050504040000017d0102030
|
|
|
582
819
|
var SOI = Buffer.from([255, 216]);
|
|
583
820
|
var APP0 = Buffer.from("ffe000104a46494600010100000100010000", "hex");
|
|
584
821
|
var MCU = { 0: [8, 8], 1: [16, 8], 2: [16, 16] };
|
|
585
|
-
var LADDER = [
|
|
586
|
-
[160, 90],
|
|
587
|
-
[240, 135],
|
|
588
|
-
[256, 144],
|
|
589
|
-
[320, 180],
|
|
590
|
-
[384, 216],
|
|
591
|
-
[400, 225],
|
|
592
|
-
[480, 270],
|
|
593
|
-
[512, 288],
|
|
594
|
-
[576, 324],
|
|
595
|
-
[640, 360],
|
|
596
|
-
[704, 396],
|
|
597
|
-
[768, 432],
|
|
598
|
-
[848, 480],
|
|
599
|
-
[960, 540],
|
|
600
|
-
[1024, 576],
|
|
601
|
-
[1280, 720],
|
|
602
|
-
[1600, 900],
|
|
603
|
-
[1920, 1080],
|
|
604
|
-
[176, 144],
|
|
605
|
-
[320, 240],
|
|
606
|
-
[352, 288],
|
|
607
|
-
[480, 360],
|
|
608
|
-
[640, 480],
|
|
609
|
-
[800, 600],
|
|
610
|
-
[1024, 768],
|
|
611
|
-
[256, 480],
|
|
612
|
-
[320, 384]
|
|
613
|
-
];
|
|
614
822
|
function scaleQuant(base, quality) {
|
|
615
823
|
const factor = quality < 50 ? Math.floor(5e3 / quality) : 200 - quality * 2;
|
|
616
824
|
return base.map((v) => Math.min(255, Math.max(1, Math.floor((v * factor + 50) / 100))));
|
|
@@ -647,7 +855,7 @@ function sofSegment(width, height, subsampling) {
|
|
|
647
855
|
1
|
|
648
856
|
]);
|
|
649
857
|
}
|
|
650
|
-
function buildHeader(width, height, subsampling, quality
|
|
858
|
+
function buildHeader(width, height, subsampling, quality) {
|
|
651
859
|
return Buffer.concat([
|
|
652
860
|
SOI,
|
|
653
861
|
APP0,
|
|
@@ -658,99 +866,107 @@ function buildHeader(width, height, subsampling, quality = 85) {
|
|
|
658
866
|
DHT_AC_LUMA
|
|
659
867
|
]);
|
|
660
868
|
}
|
|
661
|
-
|
|
869
|
+
var PROBE_MEMORY_MB = 24;
|
|
870
|
+
function decodeSpliced(header, tail) {
|
|
662
871
|
try {
|
|
663
|
-
|
|
664
|
-
return jpegDecode(jpeg, { useTArray: true, maxMemoryUsageInMB: 128 });
|
|
872
|
+
return jpegDecode(Buffer.concat([header, tail]), { useTArray: true, maxMemoryUsageInMB: PROBE_MEMORY_MB });
|
|
665
873
|
} catch {
|
|
666
874
|
return null;
|
|
667
875
|
}
|
|
668
876
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
let
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
}
|
|
877
|
+
var REFERENCE_QUALITY = 85;
|
|
878
|
+
var MAX_STRETCH = 8;
|
|
879
|
+
var CUTOFF_PERCENT = 0.5;
|
|
880
|
+
function trimmedRange(data, channel, total, cutoff) {
|
|
881
|
+
const hist = new Array(256).fill(0);
|
|
882
|
+
for (let i = 0; i < total; i++)
|
|
883
|
+
hist[data[i * 4 + channel]]++;
|
|
884
|
+
let remaining = Math.floor(total * cutoff / 100);
|
|
885
|
+
let lo = 0;
|
|
886
|
+
while (lo < 255 && remaining > 0) {
|
|
887
|
+
if (remaining < hist[lo])
|
|
888
|
+
break;
|
|
889
|
+
remaining -= hist[lo];
|
|
890
|
+
lo++;
|
|
891
|
+
}
|
|
892
|
+
remaining = Math.floor(total * cutoff / 100);
|
|
893
|
+
let hi = 255;
|
|
894
|
+
while (hi > 0 && remaining > 0) {
|
|
895
|
+
if (remaining < hist[hi])
|
|
896
|
+
break;
|
|
897
|
+
remaining -= hist[hi];
|
|
898
|
+
hi--;
|
|
692
899
|
}
|
|
693
|
-
return
|
|
900
|
+
return { lo, hi };
|
|
694
901
|
}
|
|
695
|
-
function
|
|
696
|
-
const total = width * height;
|
|
902
|
+
function contrastScale(img, cutoff = CUTOFF_PERCENT) {
|
|
903
|
+
const total = img.width * img.height;
|
|
904
|
+
if (total === 0)
|
|
905
|
+
return 1;
|
|
906
|
+
let stretch = Number.POSITIVE_INFINITY;
|
|
697
907
|
for (let channel = 0; channel < 3; channel++) {
|
|
698
|
-
const
|
|
699
|
-
for (let i = 0; i < total; i++)
|
|
700
|
-
hist[data[i * 4 + channel]]++;
|
|
701
|
-
let remaining = Math.floor(total * cutoff / 100);
|
|
702
|
-
for (let bin = 0; bin < 256 && remaining > 0; bin++) {
|
|
703
|
-
if (remaining > hist[bin]) {
|
|
704
|
-
remaining -= hist[bin];
|
|
705
|
-
hist[bin] = 0;
|
|
706
|
-
} else {
|
|
707
|
-
hist[bin] -= remaining;
|
|
708
|
-
remaining = 0;
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
remaining = Math.floor(total * cutoff / 100);
|
|
712
|
-
for (let bin = 255; bin >= 0 && remaining > 0; bin--) {
|
|
713
|
-
if (remaining > hist[bin]) {
|
|
714
|
-
remaining -= hist[bin];
|
|
715
|
-
hist[bin] = 0;
|
|
716
|
-
} else {
|
|
717
|
-
hist[bin] -= remaining;
|
|
718
|
-
remaining = 0;
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
let lo = 0;
|
|
722
|
-
while (lo < 256 && hist[lo] === 0)
|
|
723
|
-
lo++;
|
|
724
|
-
let hi = 255;
|
|
725
|
-
while (hi >= 0 && hist[hi] === 0)
|
|
726
|
-
hi--;
|
|
908
|
+
const { lo, hi } = trimmedRange(img.data, channel, total, cutoff);
|
|
727
909
|
if (hi <= lo)
|
|
728
910
|
continue;
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
}
|
|
738
|
-
function
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
911
|
+
if (lo < 128)
|
|
912
|
+
stretch = Math.min(stretch, 128 / (128 - lo));
|
|
913
|
+
if (hi > 128)
|
|
914
|
+
stretch = Math.min(stretch, 127 / (hi - 128));
|
|
915
|
+
}
|
|
916
|
+
if (!Number.isFinite(stretch))
|
|
917
|
+
return 1;
|
|
918
|
+
return Math.min(MAX_STRETCH, Math.max(1, stretch));
|
|
919
|
+
}
|
|
920
|
+
function qualityForStretch(stretch) {
|
|
921
|
+
const referenceFactor = REFERENCE_QUALITY < 50 ? Math.floor(5e3 / REFERENCE_QUALITY) : 200 - REFERENCE_QUALITY * 2;
|
|
922
|
+
const factor = referenceFactor * stretch;
|
|
923
|
+
const quality = factor <= 100 ? (200 - factor) / 2 : 5e3 / factor;
|
|
924
|
+
return Math.min(99, Math.max(1, Math.round(quality)));
|
|
925
|
+
}
|
|
926
|
+
var MAX_EDGE = 4096;
|
|
927
|
+
var MIN_ASPECT = 0.4;
|
|
928
|
+
var MAX_ASPECT = 4;
|
|
929
|
+
function mcuRowShear(luma, mcuWidth, mcuHeight) {
|
|
930
|
+
if (mcuHeight < 2)
|
|
931
|
+
return Number.POSITIVE_INFINITY;
|
|
932
|
+
let sum = 0;
|
|
933
|
+
for (let y = 1; y < mcuHeight; y++) {
|
|
934
|
+
for (let x = 0; x < mcuWidth; x++)
|
|
935
|
+
sum += Math.abs(luma[y * mcuWidth + x] - luma[(y - 1) * mcuWidth + x]);
|
|
936
|
+
}
|
|
937
|
+
return sum / (mcuWidth * (mcuHeight - 1));
|
|
938
|
+
}
|
|
939
|
+
function bestGeometry(luma, mcus, subsampling) {
|
|
940
|
+
const [mcuPixelWidth, mcuPixelHeight] = MCU[subsampling];
|
|
941
|
+
let best = null;
|
|
942
|
+
for (let mcuWidth = 1; mcuWidth <= mcus; mcuWidth++) {
|
|
943
|
+
if (mcus % mcuWidth !== 0)
|
|
944
|
+
continue;
|
|
945
|
+
const mcuHeight = mcus / mcuWidth;
|
|
946
|
+
const width = mcuWidth * mcuPixelWidth;
|
|
947
|
+
const height = mcuHeight * mcuPixelHeight;
|
|
948
|
+
if (width > MAX_EDGE || height > MAX_EDGE)
|
|
949
|
+
continue;
|
|
950
|
+
const aspect = width / height;
|
|
951
|
+
if (aspect < MIN_ASPECT || aspect > MAX_ASPECT)
|
|
952
|
+
continue;
|
|
953
|
+
const shear = mcuRowShear(luma, mcuWidth, mcuHeight);
|
|
954
|
+
if (!best || shear < best.shear)
|
|
955
|
+
best = { subsampling, width, height, shear };
|
|
956
|
+
}
|
|
957
|
+
return best;
|
|
958
|
+
}
|
|
959
|
+
function findGeometry(tail) {
|
|
960
|
+
let best = null;
|
|
961
|
+
for (const subsampling of [0, 1, 2]) {
|
|
962
|
+
const scan = scanEntropy(tail, subsampling, [DHT_DC_LUMA, DHT_AC_LUMA]);
|
|
963
|
+
if (!scan?.complete || scan.mcus < 1)
|
|
964
|
+
continue;
|
|
965
|
+
const geometry = bestGeometry(scan.luma, scan.mcus, subsampling);
|
|
966
|
+
if (geometry && (!best || geometry.shear < best.shear))
|
|
967
|
+
best = geometry;
|
|
968
|
+
}
|
|
969
|
+
return best;
|
|
754
970
|
}
|
|
755
971
|
function isV2Image(data) {
|
|
756
972
|
return data.length >= V2_PREFIX.length && data.subarray(0, V2_PREFIX.length).toString("latin1") === V2_PREFIX;
|
|
@@ -762,53 +978,17 @@ function decodeImageV2(data) {
|
|
|
762
978
|
if (cut < 0)
|
|
763
979
|
return null;
|
|
764
980
|
const tail = data.subarray(cut);
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
let filled = null;
|
|
768
|
-
for (const [width2, height2] of ladderByMcuCount(subsampling2)) {
|
|
769
|
-
const img2 = decodeCandidate(tail, width2, height2, subsampling2);
|
|
770
|
-
if (!img2)
|
|
771
|
-
break;
|
|
772
|
-
if (!filled || width2 * height2 > filled.width * filled.height)
|
|
773
|
-
filled = { width: width2, height: height2, img: img2 };
|
|
774
|
-
}
|
|
775
|
-
if (!filled)
|
|
776
|
-
continue;
|
|
777
|
-
const spread = colorSpread(filled.img);
|
|
778
|
-
if (!best || spread < best.spread)
|
|
779
|
-
best = { spread, subsampling: subsampling2, width: filled.width, height: filled.height };
|
|
780
|
-
}
|
|
781
|
-
if (!best)
|
|
981
|
+
const geometry = findGeometry(tail);
|
|
982
|
+
if (!geometry)
|
|
782
983
|
return null;
|
|
783
|
-
const {
|
|
784
|
-
const
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
let width = coarseWidth;
|
|
788
|
-
let height = coarseHeight;
|
|
789
|
-
let bestShear = Number.POSITIVE_INFINITY;
|
|
790
|
-
const lowWidth = Math.max(mcuWidth * 4, Math.round(coarseWidth * 0.75 / mcuWidth) * mcuWidth);
|
|
791
|
-
const highWidth = Math.round(coarseWidth * 1.25 / mcuWidth) * mcuWidth;
|
|
792
|
-
for (let candidateWidth = lowWidth; candidateWidth <= highWidth; candidateWidth += mcuWidth) {
|
|
793
|
-
const candidateHeight = Math.floor(totalMcus / (candidateWidth / mcuWidth)) * mcuHeight;
|
|
794
|
-
if (candidateHeight < mcuHeight)
|
|
795
|
-
continue;
|
|
796
|
-
const img2 = decodeCandidate(tail, candidateWidth, candidateHeight, subsampling);
|
|
797
|
-
if (!img2)
|
|
798
|
-
continue;
|
|
799
|
-
const shear = rowShear(img2, candidateHeight);
|
|
800
|
-
if (shear < bestShear) {
|
|
801
|
-
bestShear = shear;
|
|
802
|
-
width = candidateWidth;
|
|
803
|
-
height = candidateHeight;
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
height = maxHeight(tail, width, subsampling, height);
|
|
807
|
-
const img = decodeCandidate(tail, width, height, subsampling);
|
|
808
|
-
if (!img)
|
|
984
|
+
const { width, height, subsampling } = geometry;
|
|
985
|
+
const reference = buildHeader(width, height, subsampling, REFERENCE_QUALITY);
|
|
986
|
+
const probe = decodeSpliced(reference, tail);
|
|
987
|
+
if (!probe)
|
|
809
988
|
return null;
|
|
810
|
-
|
|
811
|
-
|
|
989
|
+
const stretch = contrastScale(probe);
|
|
990
|
+
const header = stretch > 1.01 ? buildHeader(width, height, subsampling, qualityForStretch(stretch)) : reference;
|
|
991
|
+
return Buffer.concat([header, tail]);
|
|
812
992
|
}
|
|
813
993
|
|
|
814
994
|
// dist/transport/http/decodeImageV1.js
|
|
@@ -1420,9 +1600,20 @@ var MegaHttpClient = class {
|
|
|
1420
1600
|
throw error;
|
|
1421
1601
|
}
|
|
1422
1602
|
}
|
|
1423
|
-
/**
|
|
1603
|
+
/**
|
|
1604
|
+
* Download push image bytes and decrypt a recognized v1 wrapper when its device key input is available.
|
|
1605
|
+
*
|
|
1606
|
+
* A decoder throw is tagged `decode-failed`: to anything downstream, the difference between "the
|
|
1607
|
+
* bytes never arrived" and "the bytes arrived and the wrapper would not decrypt" is the difference
|
|
1608
|
+
* between a network problem and a key problem, and one of them is this SDK's to fix.
|
|
1609
|
+
*/
|
|
1424
1610
|
async downloadImage(url, p2pDid) {
|
|
1425
|
-
|
|
1611
|
+
const data = await this.downloadMedia(url);
|
|
1612
|
+
try {
|
|
1613
|
+
return normalizePushImage(data, p2pDid);
|
|
1614
|
+
} catch (error) {
|
|
1615
|
+
throw mediaFailureError("Push image could not be decoded", "decode-failed", error);
|
|
1616
|
+
}
|
|
1426
1617
|
}
|
|
1427
1618
|
/** The security-app data host for this region (face recognition, media, etc.). */
|
|
1428
1619
|
securityAppHost() {
|
|
@@ -12880,7 +13071,7 @@ function lz4BlockDecompress(block, size) {
|
|
|
12880
13071
|
const end = block.length;
|
|
12881
13072
|
let pos = 0;
|
|
12882
13073
|
let written = 0;
|
|
12883
|
-
const
|
|
13074
|
+
const extend2 = (base) => {
|
|
12884
13075
|
let len = base;
|
|
12885
13076
|
if (base !== 15)
|
|
12886
13077
|
return len;
|
|
@@ -12895,7 +13086,7 @@ function lz4BlockDecompress(block, size) {
|
|
|
12895
13086
|
};
|
|
12896
13087
|
while (pos < end) {
|
|
12897
13088
|
const token = block[pos++];
|
|
12898
|
-
const literals =
|
|
13089
|
+
const literals = extend2(token >> 4);
|
|
12899
13090
|
if (literals === void 0)
|
|
12900
13091
|
return void 0;
|
|
12901
13092
|
if (pos + literals > end || written + literals > size)
|
|
@@ -12910,7 +13101,7 @@ function lz4BlockDecompress(block, size) {
|
|
|
12910
13101
|
pos += 2;
|
|
12911
13102
|
if (offset === 0 || offset > written)
|
|
12912
13103
|
return void 0;
|
|
12913
|
-
const match =
|
|
13104
|
+
const match = extend2(token & 15);
|
|
12914
13105
|
if (match === void 0)
|
|
12915
13106
|
return void 0;
|
|
12916
13107
|
const length = match + 4;
|
|
@@ -14876,6 +15067,11 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
|
|
|
14876
15067
|
if (host && !this.closed)
|
|
14877
15068
|
this.selfAddress = { host, port: boundPort };
|
|
14878
15069
|
});
|
|
15070
|
+
this.trace({
|
|
15071
|
+
phase: "lookup-channels",
|
|
15072
|
+
local: !this.cfg.noBroadcast || this.cfg.localAddress !== void 0,
|
|
15073
|
+
cloud: this.cfg.dskKey !== void 0 && (this.cfg.cloudAddresses?.length ?? 0) > 0
|
|
15074
|
+
});
|
|
14879
15075
|
this.sendLookups();
|
|
14880
15076
|
this.lookupTimer = setInterval(() => this.sendLookups(), LOOKUP_RETRY_MS);
|
|
14881
15077
|
this.connectTimer = setTimeout(() => {
|
|
@@ -14958,8 +15154,6 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
|
|
|
14958
15154
|
for (const addr of this.cfg.cloudAddresses)
|
|
14959
15155
|
this.send(addr, type, payload);
|
|
14960
15156
|
this.logger.debug(`[p2p] ${this.cfg.stationSn} sendLookups: cloud -> ${this.cfg.cloudAddresses.map((a) => `${a.host}:${a.port}`).join(", ")} self=${this.selfAddress?.host}:${this.selfAddress?.port}`);
|
|
14961
|
-
} else {
|
|
14962
|
-
this.logger.debug(`[p2p] ${this.cfg.stationSn} sendLookups: NO cloud lookup sent (dskKey=${!!this.cfg.dskKey} cloudAddresses=${this.cfg.cloudAddresses?.length ?? 0})`);
|
|
14963
15157
|
}
|
|
14964
15158
|
}
|
|
14965
15159
|
/**
|
|
@@ -19392,7 +19586,6 @@ var FragmentRecording = class extends EventEmitter6 {
|
|
|
19392
19586
|
|
|
19393
19587
|
// dist/transport/p2p/command-router.js
|
|
19394
19588
|
var DIRECT_CMD_SENDS = 5;
|
|
19395
|
-
var CONNECT_WAIT_MS = 2e4;
|
|
19396
19589
|
function abortable(work, signal) {
|
|
19397
19590
|
if (!signal)
|
|
19398
19591
|
return work;
|
|
@@ -19407,7 +19600,7 @@ function abortable(work, signal) {
|
|
|
19407
19600
|
var LEVEL2_GRACE_MS = 25e3;
|
|
19408
19601
|
var LEVEL2_SETTLE_MS = 8e3;
|
|
19409
19602
|
var P2P_STATION_WAITS = {
|
|
19410
|
-
connect:
|
|
19603
|
+
connect: CONNECT_TIMEOUT_MS,
|
|
19411
19604
|
level2Grace: LEVEL2_GRACE_MS,
|
|
19412
19605
|
level2Settle: LEVEL2_SETTLE_MS
|
|
19413
19606
|
};
|
|
@@ -20578,12 +20771,20 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
20578
20771
|
this.manager.bumpCommand(parentSn, parentSn);
|
|
20579
20772
|
const channel = typeof raw.device_channel === "number" ? raw.device_channel : 0;
|
|
20580
20773
|
const stationAdminId = raw.member?.admin_user_id;
|
|
20774
|
+
const stationModel = this.recordFor(parentSn)?.model;
|
|
20581
20775
|
const accountId = stationAdminId ?? this.deps.mega.auth?.userId ?? "";
|
|
20776
|
+
this.traceOnStation(session, {
|
|
20777
|
+
phase: "station-resolved",
|
|
20778
|
+
topology: homeBaseAttached ? "attached" : "own",
|
|
20779
|
+
channel,
|
|
20780
|
+
stationAdmin: typeof stationAdminId !== "string" ? "unstated" : stationAdminId === this.deps.mega.auth?.userId ? "self" : "other",
|
|
20781
|
+
...stationModel ? { stationModel } : {}
|
|
20782
|
+
});
|
|
20582
20783
|
const t0 = Date.now();
|
|
20583
20784
|
let waitedMs = 0;
|
|
20584
20785
|
if (!session.isConnected) {
|
|
20585
|
-
this.traceOnStation(session, { phase: "session-connect-wait", waitMs:
|
|
20586
|
-
while (!session.isConnected && Date.now() - t0 <
|
|
20786
|
+
this.traceOnStation(session, { phase: "session-connect-wait", waitMs: P2P_STATION_WAITS.connect });
|
|
20787
|
+
while (!session.isConnected && Date.now() - t0 < P2P_STATION_WAITS.connect) {
|
|
20587
20788
|
opts.signal?.throwIfAborted();
|
|
20588
20789
|
await sleep2(200);
|
|
20589
20790
|
}
|
|
@@ -20593,12 +20794,6 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
20593
20794
|
opts.signal?.throwIfAborted();
|
|
20594
20795
|
if (!session.isConnected)
|
|
20595
20796
|
throw new StationUnreachableError(parentSn, waitedMs);
|
|
20596
|
-
this.traceOnStation(session, {
|
|
20597
|
-
phase: "station-resolved",
|
|
20598
|
-
topology: homeBaseAttached ? "attached" : "own",
|
|
20599
|
-
channel,
|
|
20600
|
-
stationAdmin: typeof stationAdminId !== "string" ? "unstated" : stationAdminId === this.deps.mega.auth?.userId ? "self" : "other"
|
|
20601
|
-
});
|
|
20602
20797
|
if (opts.waitLevel2) {
|
|
20603
20798
|
if (opts.waitLevel2 === "settle") {
|
|
20604
20799
|
await abortable(session.awaitLevel2Key(LEVEL2_SETTLE_MS, "session"), opts.signal);
|
|
@@ -23363,8 +23558,21 @@ var FileFcmStore = class {
|
|
|
23363
23558
|
}
|
|
23364
23559
|
};
|
|
23365
23560
|
|
|
23561
|
+
// dist/transport/media-failure.js
|
|
23562
|
+
var MEDIA_FAILURE_REASONS = [
|
|
23563
|
+
"url-not-allowed",
|
|
23564
|
+
"address-not-public",
|
|
23565
|
+
"redirect-not-allowed",
|
|
23566
|
+
"http-status",
|
|
23567
|
+
"too-large",
|
|
23568
|
+
"timeout",
|
|
23569
|
+
"network",
|
|
23570
|
+
"decode-failed"
|
|
23571
|
+
];
|
|
23572
|
+
|
|
23366
23573
|
// dist/transport/stored-image-cache.js
|
|
23367
23574
|
var MAX_JPEG_BYTES = 10 * 1024 * 1024;
|
|
23575
|
+
var MAX_REMEMBERED_URLS = 64;
|
|
23368
23576
|
var DIAGNOSTIC_INTERVAL_MS = 6e4;
|
|
23369
23577
|
var StoredImageCache = class {
|
|
23370
23578
|
downloader;
|
|
@@ -23381,7 +23589,13 @@ var StoredImageCache = class {
|
|
|
23381
23589
|
this.clock = clock;
|
|
23382
23590
|
this.isLifecycleError = isLifecycleError;
|
|
23383
23591
|
}
|
|
23384
|
-
/**
|
|
23592
|
+
/**
|
|
23593
|
+
* Observe a normalized thumbnail URL and start acquisition eagerly.
|
|
23594
|
+
*
|
|
23595
|
+
* A URL already inside this device's window of recent attempts is ignored, so one event arriving as
|
|
23596
|
+
* several pushes downloads one thumbnail. The window is a `Set`, which iterates in insertion order,
|
|
23597
|
+
* so the entry evicted once it is full is the oldest attempt.
|
|
23598
|
+
*/
|
|
23385
23599
|
observe(deviceKey, url) {
|
|
23386
23600
|
let state = this.devices.get(deviceKey);
|
|
23387
23601
|
if (!state) {
|
|
@@ -23396,6 +23610,12 @@ var StoredImageCache = class {
|
|
|
23396
23610
|
if (state.seenUrls.has(url))
|
|
23397
23611
|
return;
|
|
23398
23612
|
state.seenUrls.add(url);
|
|
23613
|
+
while (state.seenUrls.size > MAX_REMEMBERED_URLS) {
|
|
23614
|
+
const oldest = state.seenUrls.values().next();
|
|
23615
|
+
if (oldest.done)
|
|
23616
|
+
break;
|
|
23617
|
+
state.seenUrls.delete(oldest.value);
|
|
23618
|
+
}
|
|
23399
23619
|
state.queued = {
|
|
23400
23620
|
deviceKey,
|
|
23401
23621
|
url,
|
|
@@ -23445,7 +23665,7 @@ var StoredImageCache = class {
|
|
|
23445
23665
|
} else if (image === void 0) {
|
|
23446
23666
|
state.lifecycleError = void 0;
|
|
23447
23667
|
state.reason = "download-failed";
|
|
23448
|
-
this.diagnose(state, candidate, "download-failed");
|
|
23668
|
+
this.diagnose(state, candidate, "download-failed", error);
|
|
23449
23669
|
} else if (!this.isValidJpeg(image)) {
|
|
23450
23670
|
state.lifecycleError = void 0;
|
|
23451
23671
|
state.reason = "invalid-image";
|
|
@@ -23461,7 +23681,7 @@ var StoredImageCache = class {
|
|
|
23461
23681
|
isValidJpeg(image) {
|
|
23462
23682
|
return Buffer.isBuffer(image) && image.length > 0 && image.length <= MAX_JPEG_BYTES && image.length >= 5 && image[0] === 255 && image[1] === 216 && image[2] === 255 && image[image.length - 2] === 255 && image[image.length - 1] === 217;
|
|
23463
23683
|
}
|
|
23464
|
-
diagnose(state, candidate, failure) {
|
|
23684
|
+
diagnose(state, candidate, failure, error) {
|
|
23465
23685
|
const now2 = this.clock();
|
|
23466
23686
|
const last = state.loggedFailures.get(failure);
|
|
23467
23687
|
if (last !== void 0 && now2 - last < DIAGNOSTIC_INTERVAL_MS)
|
|
@@ -23469,11 +23689,23 @@ var StoredImageCache = class {
|
|
|
23469
23689
|
state.loggedFailures.set(failure, now2);
|
|
23470
23690
|
this.logger.warn("[stored-snapshot-cache] candidate failed", {
|
|
23471
23691
|
class: failure,
|
|
23692
|
+
...mediaFailureTag(error),
|
|
23472
23693
|
observedAt: candidate.observedAt,
|
|
23473
23694
|
retained: state.retained !== void 0
|
|
23474
23695
|
});
|
|
23475
23696
|
}
|
|
23476
23697
|
};
|
|
23698
|
+
function mediaFailureTag(error) {
|
|
23699
|
+
if (typeof error !== "object" || error === null)
|
|
23700
|
+
return {};
|
|
23701
|
+
const { mediaFailure, status } = error;
|
|
23702
|
+
if (typeof mediaFailure !== "string")
|
|
23703
|
+
return {};
|
|
23704
|
+
const cause = MEDIA_FAILURE_REASONS.find((reason) => reason === mediaFailure);
|
|
23705
|
+
if (!cause)
|
|
23706
|
+
return {};
|
|
23707
|
+
return Number.isInteger(status) && status >= 100 && status <= 599 ? { cause, status } : { cause };
|
|
23708
|
+
}
|
|
23477
23709
|
|
|
23478
23710
|
// dist/client/device-registry.js
|
|
23479
23711
|
function resolvedStationSn(raw, sn) {
|
|
@@ -26616,6 +26848,7 @@ export {
|
|
|
26616
26848
|
CLEAN_EXTENTS,
|
|
26617
26849
|
CLEAN_FINISH_REASONS,
|
|
26618
26850
|
CLEAN_PARAMS,
|
|
26851
|
+
CONNECT_TIMEOUT_MS,
|
|
26619
26852
|
CONTACT_MEMBERS,
|
|
26620
26853
|
CO_MEMBERS,
|
|
26621
26854
|
CameraDisabledError,
|
|
@@ -26742,7 +26975,6 @@ export {
|
|
|
26742
26975
|
aesKey,
|
|
26743
26976
|
asBool,
|
|
26744
26977
|
assertNever,
|
|
26745
|
-
autoContrast,
|
|
26746
26978
|
bizChannelName,
|
|
26747
26979
|
buildActions,
|
|
26748
26980
|
buildAppShapedClientId,
|