surf-cli 2.7.2 → 2.8.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.
@@ -652,9 +652,21 @@ async function runGeminiWebViaPage(input) {
652
652
  if (typed.error) throw new Error(typed.error);
653
653
 
654
654
  const beforeResult = await jsEval(tabId, `
655
- return String(Array.from(document.querySelectorAll('img[src*="gg-dl"]')).filter(i => i.naturalWidth >= 512).length);
655
+ const imageKey = (img) => {
656
+ const url = img.currentSrc || img.src || "";
657
+ return url + "|" + img.naturalWidth + "x" + img.naturalHeight;
658
+ };
659
+ const baselineKeys = Array.from(document.images)
660
+ .filter((img) => {
661
+ const url = img.currentSrc || img.src || "";
662
+ return img.naturalWidth >= 512
663
+ && img.naturalHeight >= 512
664
+ && (url.includes("gg-dl") || url.startsWith("blob:"));
665
+ })
666
+ .map(imageKey);
667
+ return JSON.stringify(baselineKeys);
656
668
  `);
657
- const imgCountBefore = parseInt(JSON.parse(checkJsResult(beforeResult, "Count images")) || "0", 10);
669
+ const baselineImageKeys = JSON.parse(JSON.parse(checkJsResult(beforeResult, "Count images")) || "[]");
658
670
 
659
671
  if (log) log("Submitting...");
660
672
  const sendResult = await jsEval(tabId, `
@@ -669,26 +681,60 @@ async function runGeminiWebViaPage(input) {
669
681
  // Poll for response
670
682
  if (log) log("Waiting for response...");
671
683
  const deadline = Date.now() + timeoutMs;
672
- let imageUrls = [];
684
+ let imageEntries = [];
673
685
  let responseText = "";
674
686
 
675
687
  while (Date.now() < deadline) {
676
688
  await new Promise(r => setTimeout(r, 2000));
677
689
  const pollResult = await jsEval(tabId, `
678
- const ggImgs = Array.from(document.querySelectorAll('img[src*="gg-dl"]'))
679
- .filter(i => i.naturalWidth >= 512)
680
- .map(i => i.src);
690
+ const baselineKeys = new Set(${JSON.stringify(baselineImageKeys)});
691
+ const imageKey = (img) => {
692
+ const url = img.currentSrc || img.src || "";
693
+ return url + "|" + img.naturalWidth + "x" + img.naturalHeight;
694
+ };
695
+ const generatedImgs = Array.from(document.images)
696
+ .filter((img) => {
697
+ const url = img.currentSrc || img.src || "";
698
+ return img.naturalWidth >= 512
699
+ && img.naturalHeight >= 512
700
+ && (url.includes("gg-dl") || url.startsWith("blob:"));
701
+ })
702
+ .filter((img) => !baselineKeys.has(imageKey(img)));
703
+ window.__surfGeminiBlobImages = window.__surfGeminiBlobImages || [];
704
+ window.__surfGeminiBlobImageIndexes = window.__surfGeminiBlobImageIndexes || Object.create(null);
705
+ const images = await Promise.all(generatedImgs.map(async (img) => {
706
+ const url = img.currentSrc || img.src || "";
707
+ if (!url.startsWith("blob:")) return { url };
708
+ const key = imageKey(img);
709
+ if (Number.isInteger(window.__surfGeminiBlobImageIndexes[key])) {
710
+ return { url, blobIndex: window.__surfGeminiBlobImageIndexes[key], type: "image/png" };
711
+ }
712
+ const canvas = document.createElement("canvas");
713
+ canvas.width = img.naturalWidth;
714
+ canvas.height = img.naturalHeight;
715
+ const ctx = canvas.getContext("2d");
716
+ if (!ctx) throw new Error("Canvas context unavailable");
717
+ ctx.drawImage(img, 0, 0);
718
+ const dataUrl = canvas.toDataURL("image/png");
719
+ const blobIndex = window.__surfGeminiBlobImages.push({
720
+ url,
721
+ b64: dataUrl.split(",")[1],
722
+ type: "image/png",
723
+ }) - 1;
724
+ window.__surfGeminiBlobImageIndexes[key] = blobIndex;
725
+ return { url, blobIndex, type: "image/png" };
726
+ }));
681
727
  const loading = !!document.querySelector('mat-progress-bar, .loading-indicator, message-loading');
682
728
  const turns = document.querySelectorAll('message-content');
683
729
  const lastTurn = turns.length ? turns[turns.length - 1] : null;
684
730
  const text = lastTurn ? lastTurn.textContent?.trim() : "";
685
- return JSON.stringify({ ggImgs, loading, text, turns: turns.length });
731
+ return JSON.stringify({ images, loading, text, turns: turns.length });
686
732
  `);
687
733
  const poll = JSON.parse(JSON.parse(checkJsResult(pollResult, "Poll response")));
688
- const newImgs = poll.ggImgs.slice(imgCountBefore);
734
+ const newImgs = poll.images || [];
689
735
 
690
736
  if (newImgs.length > 0) {
691
- imageUrls = newImgs;
737
+ imageEntries = newImgs;
692
738
  responseText = poll.text || "";
693
739
  if (log) log(`Found ${newImgs.length} generated image(s)`);
694
740
  break;
@@ -699,23 +745,47 @@ async function runGeminiWebViaPage(input) {
699
745
  }
700
746
  }
701
747
 
702
- if (!imageUrls.length && !responseText) {
748
+ if (!imageEntries.length && !responseText) {
703
749
  throw new Error("Gemini response timed out");
704
750
  }
705
751
 
706
- // Download images via extension
752
+ // Download URL-backed images via extension; read blob images from the page in chunks.
707
753
  const images = [];
708
- if (fetchUrl) {
709
- for (const url of imageUrls) {
710
- if (log) log(`Downloading image (${url.slice(0, 60)}...)...`);
711
- const dlResult = await fetchUrl(url);
712
- if (dlResult?.b64) {
713
- images.push({ url, b64: dlResult.b64, type: dlResult.type || "image/png" });
754
+ for (const img of imageEntries) {
755
+ if (Number.isInteger(img?.blobIndex)) {
756
+ let b64 = "";
757
+ let offset = 0;
758
+ const chunkSize = 40000;
759
+ let type = img.type || "image/png";
760
+ while (true) {
761
+ const chunkResult = await jsEval(tabId, `
762
+ const item = window.__surfGeminiBlobImages?.[${img.blobIndex}];
763
+ if (!item) return JSON.stringify({ error: "Blob image not found" });
764
+ return JSON.stringify({
765
+ chunk: item.b64.slice(${offset}, ${offset + chunkSize}),
766
+ done: ${offset + chunkSize} >= item.b64.length,
767
+ type: item.type || "image/png",
768
+ url: item.url,
769
+ });
770
+ `);
771
+ const chunk = JSON.parse(JSON.parse(checkJsResult(chunkResult, "Read blob image chunk")));
772
+ if (chunk.error) throw new Error(chunk.error);
773
+ b64 += chunk.chunk || "";
774
+ type = chunk.type || type;
775
+ if (chunk.done) break;
776
+ offset += chunkSize;
714
777
  }
778
+ images.push({ url: img.url, b64, type });
779
+ continue;
715
780
  }
716
- } else {
717
- for (const url of imageUrls) {
718
- images.push({ url });
781
+ if (img?.url && fetchUrl) {
782
+ if (log) log(`Downloading image (${img.url.slice(0, 60)}...)...`);
783
+ const dlResult = await fetchUrl(img.url);
784
+ if (dlResult?.b64) {
785
+ images.push({ url: img.url, b64: dlResult.b64, type: dlResult.type || "image/png" });
786
+ }
787
+ } else if (img?.url) {
788
+ images.push({ url: img.url });
719
789
  }
720
790
  }
721
791
 
@@ -917,6 +987,7 @@ module.exports = {
917
987
  hasRequiredCookies,
918
988
  buildCookieMap,
919
989
  parseGeminiStreamGenerateResponse,
990
+ runGeminiWebViaPage,
920
991
  REQUIRED_COOKIES,
921
992
  ALL_COOKIE_NAMES,
922
993
  GEMINI_APP_URL,