@imsovikde/source-grab 1.0.1
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/CHANGELOG.md +27 -0
- package/LICENSE +21 -0
- package/README.md +281 -0
- package/background/capture-registry.js +306 -0
- package/background/debugger-capture.js +171 -0
- package/background/download-engine.js +316 -0
- package/background/service-worker.js +426 -0
- package/content-scripts/auto-scroll.js +51 -0
- package/content-scripts/isolated-bridge.js +36 -0
- package/content-scripts/main-world-hook.js +278 -0
- package/design-tokens.css +67 -0
- package/icons/2-double-tap.svg +12 -0
- package/icons/2-scroll-left.svg +11 -0
- package/icons/2-scroll-right.svg +11 -0
- package/icons/2-scroll-up-1.svg +11 -0
- package/icons/2-scroll-up.svg +11 -0
- package/icons/2-tap-1.svg +15 -0
- package/icons/2-tap.svg +11 -0
- package/icons/2-touch-hold.svg +13 -0
- package/icons/3-scroll-down.svg +11 -0
- package/icons/3-scroll-up.svg +11 -0
- package/icons/3-tap-scroll.svg +13 -0
- package/icons/3-tap.svg +11 -0
- package/icons/analytics.svg +10 -0
- package/icons/anchor.svg +10 -0
- package/icons/apple.svg +11 -0
- package/icons/arrow-circle-down.svg +4 -0
- package/icons/arrow-circle-left.svg +4 -0
- package/icons/arrow-circle-right.svg +4 -0
- package/icons/arrow-circle-up.svg +4 -0
- package/icons/arrow-down-2.svg +3 -0
- package/icons/arrow-down.svg +11 -0
- package/icons/arrow-left.svg +3 -0
- package/icons/arrow-ne.svg +3 -0
- package/icons/arrow-nw.svg +3 -0
- package/icons/arrow-right.svg +3 -0
- package/icons/arrow-se.svg +3 -0
- package/icons/arrow-single-down.svg +3 -0
- package/icons/arrow-single-left.svg +3 -0
- package/icons/arrow-single-right.svg +3 -0
- package/icons/arrow-single-up.svg +3 -0
- package/icons/arrow-square-down.svg +4 -0
- package/icons/arrow-square-left.svg +4 -0
- package/icons/arrow-square-right.svg +4 -0
- package/icons/arrow-square-up.svg +4 -0
- package/icons/arrow-sw.svg +3 -0
- package/icons/arrow-up-2.svg +3 -0
- package/icons/arrow-up.svg +11 -0
- package/icons/arrow.svg +10 -0
- package/icons/at-the-rate.svg +10 -0
- package/icons/automation.svg +4 -0
- package/icons/backward.svg +11 -0
- package/icons/bag-2.svg +3 -0
- package/icons/bag.svg +3 -0
- package/icons/balloon-2.svg +10 -0
- package/icons/balloon.svg +10 -0
- package/icons/bank.svg +10 -0
- package/icons/basket.svg +6 -0
- package/icons/behance.svg +12 -0
- package/icons/bell-2.svg +10 -0
- package/icons/bell.svg +10 -0
- package/icons/bill.svg +19 -0
- package/icons/bisexual.svg +10 -0
- package/icons/blood-bag.svg +10 -0
- package/icons/blood.svg +12 -0
- package/icons/bluetooth.svg +10 -0
- package/icons/bookmark.svg +10 -0
- package/icons/bot.svg +6 -0
- package/icons/bottle.svg +15 -0
- package/icons/box-2.svg +6 -0
- package/icons/box.svg +3 -0
- package/icons/bug.svg +3 -0
- package/icons/bulb.svg +10 -0
- package/icons/burger.svg +16 -0
- package/icons/bus.svg +8 -0
- package/icons/cake.svg +10 -0
- package/icons/calculator.svg +15 -0
- package/icons/calendar.svg +15 -0
- package/icons/call.svg +12 -0
- package/icons/camera-2.svg +10 -0
- package/icons/camera.svg +12 -0
- package/icons/candy.svg +10 -0
- package/icons/car.svg +8 -0
- package/icons/card-2.svg +9 -0
- package/icons/card-3.svg +6 -0
- package/icons/card.svg +4 -0
- package/icons/cart-add.svg +4 -0
- package/icons/cart-delete.svg +4 -0
- package/icons/cart-remove.svg +4 -0
- package/icons/cash.svg +11 -0
- package/icons/caution.svg +12 -0
- package/icons/center-align-2.svg +10 -0
- package/icons/center-align.svg +13 -0
- package/icons/checklist.svg +4 -0
- package/icons/chevrons-down.svg +4 -0
- package/icons/chevrons-left.svg +4 -0
- package/icons/chevrons-right.svg +4 -0
- package/icons/chevrons-up.svg +4 -0
- package/icons/chip.svg +4 -0
- package/icons/clap.svg +14 -0
- package/icons/clock.svg +11 -0
- package/icons/cloud-down.svg +10 -0
- package/icons/cloud-up.svg +10 -0
- package/icons/cloud.svg +10 -0
- package/icons/cloudy-day.svg +15 -0
- package/icons/cloudy-night.svg +10 -0
- package/icons/codepen.svg +10 -0
- package/icons/coffee-cup-1.svg +7 -0
- package/icons/coffee-cup-2.svg +6 -0
- package/icons/coin-2.svg +11 -0
- package/icons/coin.svg +11 -0
- package/icons/confused-emoji.svg +8 -0
- package/icons/conveyor-belt.svg +8 -0
- package/icons/cookie.svg +14 -0
- package/icons/cool-emoji.svg +4 -0
- package/icons/copy.svg +10 -0
- package/icons/crop.svg +10 -0
- package/icons/cross.svg +10 -0
- package/icons/crown.svg +14 -0
- package/icons/crying-emoji.svg +4 -0
- package/icons/cut.svg +10 -0
- package/icons/cutlery.svg +11 -0
- package/icons/dashboard-2.svg +10 -0
- package/icons/dashboard-3.svg +11 -0
- package/icons/dashboard-4.svg +11 -0
- package/icons/dashboard.svg +11 -0
- package/icons/delete.svg +12 -0
- package/icons/diamond.svg +10 -0
- package/icons/dish.svg +10 -0
- package/icons/doc-add.svg +11 -0
- package/icons/doc-remove.svg +11 -0
- package/icons/doc.svg +15 -0
- package/icons/dollar-2.svg +11 -0
- package/icons/dollar.svg +10 -0
- package/icons/double-tap.svg +12 -0
- package/icons/download.svg +11 -0
- package/icons/drawer.svg +10 -0
- package/icons/dribbble.svg +10 -0
- package/icons/drink.svg +10 -0
- package/icons/dropbox.svg +10 -0
- package/icons/dropper.svg +10 -0
- package/icons/egg.svg +11 -0
- package/icons/eject.svg +12 -0
- package/icons/eraser.svg +10 -0
- package/icons/euro.svg +11 -0
- package/icons/facebook-2.svg +10 -0
- package/icons/facebook.svg +3 -0
- package/icons/factory.svg +7 -0
- package/icons/fast-forward.svg +10 -0
- package/icons/fast-rewind.svg +10 -0
- package/icons/fb-messenger.svg +11 -0
- package/icons/femaile.svg +10 -0
- package/icons/file-attachment.svg +10 -0
- package/icons/file-audio.svg +13 -0
- package/icons/file-code.svg +13 -0
- package/icons/file-contract.svg +15 -0
- package/icons/file-csv.svg +13 -0
- package/icons/file-figma.svg +14 -0
- package/icons/file-form.svg +15 -0
- package/icons/file-image-2.svg +12 -0
- package/icons/file-image.svg +12 -0
- package/icons/file-invoice.svg +15 -0
- package/icons/file-jpg.svg +13 -0
- package/icons/file-list.svg +17 -0
- package/icons/file-mov.svg +13 -0
- package/icons/file-mp4.svg +13 -0
- package/icons/file-notes.svg +12 -0
- package/icons/file-pdf.svg +13 -0
- package/icons/file-png.svg +13 -0
- package/icons/file-spreadsheet.svg +11 -0
- package/icons/file-svg.svg +13 -0
- package/icons/file-text.svg +14 -0
- package/icons/file-vector.svg +11 -0
- package/icons/file-zip.svg +10 -0
- package/icons/file.svg +10 -0
- package/icons/filter.svg +10 -0
- package/icons/fire.svg +5 -0
- package/icons/first-aid.svg +11 -0
- package/icons/firstaid-2.svg +12 -0
- package/icons/flag-2.svg +10 -0
- package/icons/flag.svg +10 -0
- package/icons/flashlight.svg +8 -0
- package/icons/flask-round.svg +10 -0
- package/icons/flask.svg +10 -0
- package/icons/flip.svg +12 -0
- package/icons/floppy.svg +11 -0
- package/icons/folded-hands.svg +12 -0
- package/icons/folder-add.svg +11 -0
- package/icons/folder-delete.svg +11 -0
- package/icons/folder-empty.svg +10 -0
- package/icons/folder-remove.svg +11 -0
- package/icons/folder.svg +10 -0
- package/icons/fork.svg +10 -0
- package/icons/forward.svg +11 -0
- package/icons/frame.svg +11 -0
- package/icons/franc.svg +11 -0
- package/icons/free-drag.svg +11 -0
- package/icons/gay.svg +10 -0
- package/icons/genderless.svg +10 -0
- package/icons/gift.svg +10 -0
- package/icons/globe.svg +10 -0
- package/icons/google.svg +10 -0
- package/icons/grid-2.svg +10 -0
- package/icons/grid.svg +10 -0
- package/icons/grinning-squinting-emoji.svg +6 -0
- package/icons/guitar.svg +10 -0
- package/icons/hand-truck.svg +5 -0
- package/icons/hand.svg +10 -0
- package/icons/happy-emoji.svg +8 -0
- package/icons/headphone.svg +10 -0
- package/icons/heart-beat.svg +10 -0
- package/icons/heart-eyes-emoji.svg +6 -0
- package/icons/heart.svg +10 -0
- package/icons/hetro.svg +10 -0
- package/icons/hide.svg +12 -0
- package/icons/home-1.svg +10 -0
- package/icons/home.svg +10 -0
- package/icons/horizontal-scroll.svg +12 -0
- package/icons/hot-air-balloon.svg +3 -0
- package/icons/ice-cream.svg +10 -0
- package/icons/icon.svg +12 -0
- package/icons/info.svg +5 -0
- package/icons/injection.svg +10 -0
- package/icons/instagram.svg +12 -0
- package/icons/intersex.svg +10 -0
- package/icons/key.svg +11 -0
- package/icons/laugh-emoji.svg +8 -0
- package/icons/layer.svg +10 -0
- package/icons/left-align-2.svg +12 -0
- package/icons/left-align.svg +13 -0
- package/icons/lesbian.svg +10 -0
- package/icons/link.svg +11 -0
- package/icons/linkedin.svg +13 -0
- package/icons/list.svg +15 -0
- package/icons/location-pin.svg +10 -0
- package/icons/location.svg +4 -0
- package/icons/lock.svg +11 -0
- package/icons/login.svg +11 -0
- package/icons/logout.svg +11 -0
- package/icons/lungs.svg +10 -0
- package/icons/magic-wand.svg +13 -0
- package/icons/mail-gift.svg +10 -0
- package/icons/mail-open.svg +10 -0
- package/icons/mail-surprise.svg +10 -0
- package/icons/mail.svg +10 -0
- package/icons/male.svg +10 -0
- package/icons/map.svg +13 -0
- package/icons/maximize.svg +13 -0
- package/icons/medical-logo.svg +10 -0
- package/icons/megaphone.svg +10 -0
- package/icons/menu-2.svg +12 -0
- package/icons/menu.svg +12 -0
- package/icons/message-2.svg +13 -0
- package/icons/message.svg +13 -0
- package/icons/mic-2.svg +11 -0
- package/icons/mic.svg +10 -0
- package/icons/microscope.svg +10 -0
- package/icons/minimize.svg +13 -0
- package/icons/money-plant.svg +11 -0
- package/icons/move.svg +10 -0
- package/icons/movie-clapper.svg +13 -0
- package/icons/multi-touch.svg +11 -0
- package/icons/music-2.svg +10 -0
- package/icons/music-3.svg +10 -0
- package/icons/music.svg +10 -0
- package/icons/mute.svg +11 -0
- package/icons/navigation-2.svg +10 -0
- package/icons/navigation.svg +10 -0
- package/icons/night.svg +10 -0
- package/icons/non-binary-transgender.svg +10 -0
- package/icons/note.svg +11 -0
- package/icons/offer.svg +9 -0
- package/icons/ok.svg +10 -0
- package/icons/other-gender.svg +10 -0
- package/icons/page-move.svg +11 -0
- package/icons/paint-brush-2.svg +10 -0
- package/icons/paint-brush.svg +10 -0
- package/icons/paint-bucket-2.svg +10 -0
- package/icons/paint-bucket.svg +10 -0
- package/icons/paint-roller.svg +10 -0
- package/icons/paper-clip-2.svg +10 -0
- package/icons/paper-clip.svg +10 -0
- package/icons/paste.svg +10 -0
- package/icons/pause.svg +11 -0
- package/icons/paypal.svg +10 -0
- package/icons/pen-tool.svg +10 -0
- package/icons/pen.svg +10 -0
- package/icons/pencil-2.svg +10 -0
- package/icons/pencil-3.svg +11 -0
- package/icons/pencil-ruler.svg +11 -0
- package/icons/pencil.svg +10 -0
- package/icons/phone-setting.svg +12 -0
- package/icons/phone.svg +11 -0
- package/icons/photo.svg +11 -0
- package/icons/pie.svg +11 -0
- package/icons/piggy-bank.svg +13 -0
- package/icons/pills.svg +10 -0
- package/icons/pin.svg +10 -0
- package/icons/pinterest.svg +10 -0
- package/icons/pizza-2.svg +10 -0
- package/icons/pizza.svg +33 -0
- package/icons/plane.svg +3 -0
- package/icons/play.svg +10 -0
- package/icons/png.svg +11 -0
- package/icons/point-down.svg +10 -0
- package/icons/point-left.svg +10 -0
- package/icons/point-right.svg +3 -0
- package/icons/point-up.svg +10 -0
- package/icons/popsicle.svg +10 -0
- package/icons/pound.svg +11 -0
- package/icons/product-hunt.svg +10 -0
- package/icons/puzzle-2.svg +10 -0
- package/icons/puzzle.svg +10 -0
- package/icons/qr.svg +21 -0
- package/icons/question-2.svg +11 -0
- package/icons/question.svg +11 -0
- package/icons/rain-heavy.svg +17 -0
- package/icons/rain-light.svg +13 -0
- package/icons/record.svg +11 -0
- package/icons/rectangle.svg +10 -0
- package/icons/right-align-2.svg +12 -0
- package/icons/right-align.svg +13 -0
- package/icons/rocket.svg +7 -0
- package/icons/rotate.svg +11 -0
- package/icons/rss.svg +13 -0
- package/icons/ruble.svg +11 -0
- package/icons/ruler.svg +10 -0
- package/icons/rupee.svg +11 -0
- package/icons/sad-emoji.svg +8 -0
- package/icons/safe.svg +13 -0
- package/icons/sale.svg +7 -0
- package/icons/satellite.svg +3 -0
- package/icons/saving.svg +15 -0
- package/icons/scan-2.svg +15 -0
- package/icons/scan-fingerprint.svg +20 -0
- package/icons/scan.svg +14 -0
- package/icons/screen-rotate.svg +12 -0
- package/icons/scroll-down.svg +11 -0
- package/icons/scroll-left-1.svg +11 -0
- package/icons/scroll-left.svg +11 -0
- package/icons/scroll-up.svg +11 -0
- package/icons/search.svg +10 -0
- package/icons/send-2.svg +10 -0
- package/icons/send-3.svg +11 -0
- package/icons/send.svg +10 -0
- package/icons/server-2.svg +15 -0
- package/icons/server.svg +3 -0
- package/icons/setting-2.svg +11 -0
- package/icons/setting-3.svg +10 -0
- package/icons/setting.svg +12 -0
- package/icons/shape.svg +10 -0
- package/icons/shield-2.svg +11 -0
- package/icons/shield.svg +11 -0
- package/icons/ship.svg +4 -0
- package/icons/shocked-emoji.svg +8 -0
- package/icons/shop.svg +4 -0
- package/icons/shopping-cart-2.svg +4 -0
- package/icons/shopping-cart-3.svg +4 -0
- package/icons/shopping-cart.svg +5 -0
- package/icons/shuffle.svg +10 -0
- package/icons/signal.svg +14 -0
- package/icons/skype.svg +11 -0
- package/icons/smiling-emoji.svg +6 -0
- package/icons/smiling-with-eyes-emoji.svg +8 -0
- package/icons/snapchat.svg +10 -0
- package/icons/snow.svg +22 -0
- package/icons/snowflake.svg +10 -0
- package/icons/snowman.svg +16 -0
- package/icons/sofa.svg +10 -0
- package/icons/speaker.svg +12 -0
- package/icons/spoon.svg +10 -0
- package/icons/spotify.svg +13 -0
- package/icons/square.svg +10 -0
- package/icons/star.svg +10 -0
- package/icons/stethoscope.svg +11 -0
- package/icons/stopwatch.svg +11 -0
- package/icons/suitcase.svg +10 -0
- package/icons/sun-2.svg +11 -0
- package/icons/sun-3.svg +18 -0
- package/icons/sun-4.svg +11 -0
- package/icons/sun.svg +18 -0
- package/icons/sunny.svg +18 -0
- package/icons/surprised-emoji.svg +8 -0
- package/icons/swipe-left.svg +11 -0
- package/icons/swipe-right.svg +11 -0
- package/icons/switch-1.svg +11 -0
- package/icons/switch.svg +10 -0
- package/icons/sync.svg +11 -0
- package/icons/tablet-2.svg +19 -0
- package/icons/tablet.svg +11 -0
- package/icons/tag-2.svg +3 -0
- package/icons/tag.svg +4 -0
- package/icons/tap-1.svg +15 -0
- package/icons/tap.svg +11 -0
- package/icons/target-2.svg +11 -0
- package/icons/target.svg +13 -0
- package/icons/test-tube.svg +10 -0
- package/icons/thumbs-down.svg +11 -0
- package/icons/thumbs-up-2.svg +11 -0
- package/icons/thumbs-up.svg +11 -0
- package/icons/thunderstorm.svg +11 -0
- package/icons/tick-2.svg +4 -0
- package/icons/tick.svg +3 -0
- package/icons/tik-tok.svg +10 -0
- package/icons/tooth.svg +12 -0
- package/icons/tornado.svg +25 -0
- package/icons/touch-hold.svg +13 -0
- package/icons/transform.svg +10 -0
- package/icons/transgender-2.svg +10 -0
- package/icons/transgender.svg +10 -0
- package/icons/tree-2.svg +10 -0
- package/icons/tree.svg +10 -0
- package/icons/trend-down-square.svg +11 -0
- package/icons/trend-down.svg +10 -0
- package/icons/trend-up-square.svg +11 -0
- package/icons/trend-up.svg +10 -0
- package/icons/trophy-2.svg +3 -0
- package/icons/trophy.svg +10 -0
- package/icons/truck.svg +4 -0
- package/icons/tumblr.svg +11 -0
- package/icons/tv.svg +11 -0
- package/icons/twitch.svg +13 -0
- package/icons/twitter.svg +10 -0
- package/icons/uber.svg +10 -0
- package/icons/unbox.svg +3 -0
- package/icons/unhide.svg +11 -0
- package/icons/unlink.svg +12 -0
- package/icons/upload.svg +11 -0
- package/icons/user-add.svg +12 -0
- package/icons/user-caution.svg +13 -0
- package/icons/user-delete.svg +12 -0
- package/icons/user-female.svg +11 -0
- package/icons/user-male.svg +11 -0
- package/icons/user-remove.svg +12 -0
- package/icons/user.svg +11 -0
- package/icons/v.svg +10 -0
- package/icons/vector.svg +10 -0
- package/icons/vibrate.svg +15 -0
- package/icons/video-camera.svg +10 -0
- package/icons/volume-down.svg +11 -0
- package/icons/volume-up.svg +12 -0
- package/icons/wallet.svg +11 -0
- package/icons/warehouse.svg +3 -0
- package/icons/water.svg +10 -0
- package/icons/wave-left.svg +11 -0
- package/icons/wave-right.svg +11 -0
- package/icons/webflow.svg +10 -0
- package/icons/whatsapp.svg +11 -0
- package/icons/wheelchair.svg +11 -0
- package/icons/wind.svg +14 -0
- package/icons/windows.svg +10 -0
- package/icons/wink-emoji.svg +6 -0
- package/icons/won.svg +11 -0
- package/icons/worried-emoji.svg +8 -0
- package/icons/y-combinator.svg +10 -0
- package/icons/yen.svg +11 -0
- package/icons/youtube.svg +11 -0
- package/icons/zap.svg +10 -0
- package/icons/zoom-in-2.svg +12 -0
- package/icons/zoom-in.svg +11 -0
- package/icons/zoom-out-1.svg +11 -0
- package/icons/zoom-out-2.svg +11 -0
- package/icons/zoom-out.svg +11 -0
- package/manifest.json +43 -0
- package/package.json +53 -0
- package/sidepanel/app.js +437 -0
- package/sidepanel/components/bottom-dock.js +148 -0
- package/sidepanel/components/empty-state.js +40 -0
- package/sidepanel/components/file-list-item.js +58 -0
- package/sidepanel/components/file-list.js +134 -0
- package/sidepanel/components/filter-bar.js +261 -0
- package/sidepanel/components/progress-overlay.js +79 -0
- package/sidepanel/components/recording-indicator.js +32 -0
- package/sidepanel/components/stats-header.js +37 -0
- package/sidepanel/icons/file-type-icons.svg +55 -0
- package/sidepanel/icons/ui-icons.svg +85 -0
- package/sidepanel/index.html +37 -0
- package/sidepanel/styles.css +934 -0
- package/sidepanel/utils/clipboard.js +33 -0
- package/sidepanel/utils/format.js +23 -0
- package/sidepanel/utils/virtual-scroll.js +107 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// background/debugger-capture.js
|
|
2
|
+
// v1.1 — Deeper scan: periodic re-scan, stylesheet sub-resource extraction
|
|
3
|
+
|
|
4
|
+
export class DebuggerCapture {
|
|
5
|
+
constructor(registry) {
|
|
6
|
+
this.registry = registry;
|
|
7
|
+
this.attachedTabs = new Set();
|
|
8
|
+
this.boundOnEvent = this.onDebuggerEvent.bind(this);
|
|
9
|
+
this.boundOnDetach = this.onDetach.bind(this);
|
|
10
|
+
|
|
11
|
+
if (typeof chrome !== 'undefined' && chrome.debugger) {
|
|
12
|
+
if (chrome.debugger.onEvent) {
|
|
13
|
+
chrome.debugger.onEvent.addListener(this.boundOnEvent);
|
|
14
|
+
}
|
|
15
|
+
if (chrome.debugger.onDetach) {
|
|
16
|
+
chrome.debugger.onDetach.addListener(this.boundOnDetach);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async attach(tabId) {
|
|
22
|
+
if (!chrome?.debugger) return;
|
|
23
|
+
if (this.attachedTabs.has(tabId)) return; // Already attached
|
|
24
|
+
try {
|
|
25
|
+
await chrome.debugger.attach({ tabId }, '1.3');
|
|
26
|
+
this.attachedTabs.add(tabId);
|
|
27
|
+
await chrome.debugger.sendCommand({ tabId }, 'Network.enable');
|
|
28
|
+
await chrome.debugger.sendCommand({ tabId }, 'Page.enable');
|
|
29
|
+
// Also enable CSS domain for stylesheet sub-resource tracking
|
|
30
|
+
try { await chrome.debugger.sendCommand({ tabId }, 'CSS.enable'); } catch(e) {}
|
|
31
|
+
|
|
32
|
+
// Immediate full resource tree scan
|
|
33
|
+
await this.getResourceTree(tabId);
|
|
34
|
+
} catch (e) {
|
|
35
|
+
console.error('Debugger attach failed:', e, chrome.runtime?.lastError);
|
|
36
|
+
this.attachedTabs.delete(tabId);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getResourceTree(tabId) {
|
|
41
|
+
if (!chrome?.debugger || !this.attachedTabs.has(tabId)) return;
|
|
42
|
+
try {
|
|
43
|
+
const response = await chrome.debugger.sendCommand({ tabId }, 'Page.getResourceTree');
|
|
44
|
+
if (response && response.frameTree) {
|
|
45
|
+
this.walkFrameTree(tabId, response.frameTree);
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.error('Failed to get resource tree:', e);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
walkFrameTree(tabId, frameTree) {
|
|
53
|
+
if (frameTree.resources) {
|
|
54
|
+
for (const res of frameTree.resources) {
|
|
55
|
+
// Skip chrome-extension resources
|
|
56
|
+
if (res.url && (res.url.startsWith('chrome-extension://') || res.url.startsWith('chrome://'))) continue;
|
|
57
|
+
|
|
58
|
+
this.registry.upsert(tabId, {
|
|
59
|
+
url: res.url,
|
|
60
|
+
mimeType: res.mimeType,
|
|
61
|
+
source: 'resourceTree',
|
|
62
|
+
frameId: frameTree.frame?.id,
|
|
63
|
+
status: 'completed'
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (frameTree.childFrames) {
|
|
68
|
+
for (const child of frameTree.childFrames) {
|
|
69
|
+
this.walkFrameTree(tabId, child);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
onDebuggerEvent(source, method, params) {
|
|
75
|
+
const tabId = source.tabId;
|
|
76
|
+
if (!tabId || !this.attachedTabs.has(tabId)) return;
|
|
77
|
+
|
|
78
|
+
if (method === 'Network.requestWillBeSent') {
|
|
79
|
+
// Skip chrome-extension and data URLs from network capture
|
|
80
|
+
const reqUrl = params.request?.url;
|
|
81
|
+
if (!reqUrl || reqUrl.startsWith('chrome-extension://') || reqUrl.startsWith('data:')) return;
|
|
82
|
+
|
|
83
|
+
this.registry.upsert(tabId, {
|
|
84
|
+
url: reqUrl,
|
|
85
|
+
requestId: params.requestId,
|
|
86
|
+
timestamp: Date.now(),
|
|
87
|
+
source: 'cdp'
|
|
88
|
+
});
|
|
89
|
+
} else if (method === 'Network.responseReceived') {
|
|
90
|
+
const respUrl = params.response?.url;
|
|
91
|
+
if (!respUrl || respUrl.startsWith('chrome-extension://')) return;
|
|
92
|
+
|
|
93
|
+
let size = null;
|
|
94
|
+
if (params.response.headers && params.response.headers['content-length']) {
|
|
95
|
+
size = parseInt(params.response.headers['content-length'], 10);
|
|
96
|
+
}
|
|
97
|
+
this.registry.upsert(tabId, {
|
|
98
|
+
url: respUrl,
|
|
99
|
+
requestId: params.requestId,
|
|
100
|
+
statusCode: params.response.status,
|
|
101
|
+
mimeType: params.response.mimeType,
|
|
102
|
+
size: isNaN(size) ? null : size
|
|
103
|
+
});
|
|
104
|
+
} else if (method === 'Network.loadingFinished') {
|
|
105
|
+
this.updateByRequestId(tabId, params.requestId, {
|
|
106
|
+
status: 'completed',
|
|
107
|
+
size: params.encodedDataLength || null
|
|
108
|
+
});
|
|
109
|
+
} else if (method === 'Network.dataReceived') {
|
|
110
|
+
// track cumulative dataLength (informational)
|
|
111
|
+
} else if (method === 'CSS.styleSheetAdded') {
|
|
112
|
+
// Deeper scan: capture fonts/images referenced inside stylesheets
|
|
113
|
+
const header = params.header;
|
|
114
|
+
if (header?.sourceURL && !header.sourceURL.startsWith('chrome-extension://')) {
|
|
115
|
+
this.registry.upsert(tabId, {
|
|
116
|
+
url: header.sourceURL,
|
|
117
|
+
mimeType: 'text/css',
|
|
118
|
+
source: 'cdp',
|
|
119
|
+
status: 'completed'
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async updateByRequestId(tabId, requestId, partial) {
|
|
126
|
+
const entries = await this.registry.getAll(tabId);
|
|
127
|
+
const entry = entries.find(e => e.requestId === requestId);
|
|
128
|
+
if (entry) {
|
|
129
|
+
partial.url = entry.originalUrl;
|
|
130
|
+
this.registry.upsert(tabId, partial);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async getResponseBody(tabId, requestId) {
|
|
135
|
+
if (!chrome?.debugger || !this.attachedTabs.has(tabId)) return null;
|
|
136
|
+
try {
|
|
137
|
+
const res = await chrome.debugger.sendCommand({ tabId }, 'Network.getResponseBody', { requestId });
|
|
138
|
+
return res;
|
|
139
|
+
} catch (e) {
|
|
140
|
+
console.error('getResponseBody failed:', e);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async getResourceContent(tabId, frameId, url) {
|
|
146
|
+
if (!chrome?.debugger || !this.attachedTabs.has(tabId)) return null;
|
|
147
|
+
try {
|
|
148
|
+
const res = await chrome.debugger.sendCommand({ tabId }, 'Page.getResourceContent', { frameId, url });
|
|
149
|
+
return res;
|
|
150
|
+
} catch (e) {
|
|
151
|
+
console.error('getResourceContent failed:', e);
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async detach(tabId) {
|
|
157
|
+
if (!chrome?.debugger) return;
|
|
158
|
+
this.attachedTabs.delete(tabId);
|
|
159
|
+
try {
|
|
160
|
+
await chrome.debugger.detach({ tabId });
|
|
161
|
+
} catch (e) {
|
|
162
|
+
// Already detached — fine
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
onDetach(source, reason) {
|
|
167
|
+
if (source.tabId) {
|
|
168
|
+
this.attachedTabs.delete(source.tabId);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
// background/download-engine.js
|
|
2
|
+
|
|
3
|
+
export class DownloadEngine {
|
|
4
|
+
constructor(registry, debuggerCapture) {
|
|
5
|
+
this.registry = registry;
|
|
6
|
+
this.debuggerCapture = debuggerCapture;
|
|
7
|
+
this.CONCURRENCY_LIMIT = 5;
|
|
8
|
+
this.RETRY_LIMIT = 3;
|
|
9
|
+
this.MAX_ZIP_SIZE = 100 * 1024 * 1024;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async downloadBatch(tabId, urls) {
|
|
13
|
+
const batchId = Date.now();
|
|
14
|
+
|
|
15
|
+
const items = urls.map(url => ({
|
|
16
|
+
url,
|
|
17
|
+
filename: this.registry.extractFilename(url),
|
|
18
|
+
status: 'queued'
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
await chrome.storage.session.set({
|
|
22
|
+
[`download_progress_${tabId}`]: {
|
|
23
|
+
batchId,
|
|
24
|
+
total: urls.length,
|
|
25
|
+
completed: 0,
|
|
26
|
+
failed: 0,
|
|
27
|
+
items
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const queue = [...urls];
|
|
32
|
+
const workers = [];
|
|
33
|
+
|
|
34
|
+
const processQueue = async () => {
|
|
35
|
+
while (queue.length > 0) {
|
|
36
|
+
const url = queue.shift();
|
|
37
|
+
await this.processItem(tabId, url, batchId);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < this.CONCURRENCY_LIMIT; i++) {
|
|
42
|
+
workers.push(processQueue());
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await Promise.all(workers);
|
|
46
|
+
await this.registry.markBatchDownloaded(tabId, urls, batchId);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async processItem(tabId, url, batchId, retries = 0) {
|
|
50
|
+
let success = false;
|
|
51
|
+
let errorMsg = null;
|
|
52
|
+
try {
|
|
53
|
+
const entries = await this.registry.getAll(tabId);
|
|
54
|
+
const normalized = this.registry.normalizeUrl(url);
|
|
55
|
+
const entry = entries.find(e => e.url === normalized);
|
|
56
|
+
|
|
57
|
+
const filename = entry ? entry.filename : this.registry.extractFilename(url);
|
|
58
|
+
const path = await this.buildMirroredPath(filename);
|
|
59
|
+
|
|
60
|
+
if (entry && entry.requestId) {
|
|
61
|
+
const bodyRes = await this.debuggerCapture.getResponseBody(tabId, entry.requestId);
|
|
62
|
+
if (bodyRes && bodyRes.body) {
|
|
63
|
+
let dataUrl;
|
|
64
|
+
if (bodyRes.base64Encoded) {
|
|
65
|
+
dataUrl = `data:${entry.mimeType || 'application/octet-stream'};base64,${bodyRes.body}`;
|
|
66
|
+
} else {
|
|
67
|
+
dataUrl = `data:${entry.mimeType || 'text/plain'};charset=utf-8,${encodeURIComponent(bodyRes.body)}`;
|
|
68
|
+
}
|
|
69
|
+
await this.chromeDownload(dataUrl, path);
|
|
70
|
+
success = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!success) {
|
|
75
|
+
try {
|
|
76
|
+
await this.chromeDownload(entry ? entry.originalUrl : url, path);
|
|
77
|
+
success = true;
|
|
78
|
+
} catch(e) {
|
|
79
|
+
throw e;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch (e) {
|
|
83
|
+
if (retries < this.RETRY_LIMIT) {
|
|
84
|
+
return this.processItem(tabId, url, batchId, retries + 1);
|
|
85
|
+
}
|
|
86
|
+
errorMsg = e.message;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await this.updateProgress(tabId, url, success, errorMsg);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async chromeDownload(url, filename) {
|
|
93
|
+
return new Promise((resolve, reject) => {
|
|
94
|
+
chrome.downloads.download({
|
|
95
|
+
url,
|
|
96
|
+
filename,
|
|
97
|
+
conflictAction: 'uniquify',
|
|
98
|
+
saveAs: false
|
|
99
|
+
}, (downloadId) => {
|
|
100
|
+
if (chrome.runtime.lastError) {
|
|
101
|
+
return reject(new Error(chrome.runtime.lastError.message));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const listener = (delta) => {
|
|
105
|
+
if (delta.id === downloadId && delta.state) {
|
|
106
|
+
if (delta.state.current === 'complete') {
|
|
107
|
+
chrome.downloads.onChanged.removeListener(listener);
|
|
108
|
+
resolve();
|
|
109
|
+
} else if (delta.state.current === 'interrupted') {
|
|
110
|
+
chrome.downloads.onChanged.removeListener(listener);
|
|
111
|
+
reject(new Error('Download interrupted'));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
chrome.downloads.onChanged.addListener(listener);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async updateProgress(tabId, url, success, errorMsg) {
|
|
121
|
+
const key = `download_progress_${tabId}`;
|
|
122
|
+
const data = await chrome.storage.session.get(key);
|
|
123
|
+
if (data[key]) {
|
|
124
|
+
const prog = data[key];
|
|
125
|
+
const item = prog.items.find(i => i.url === url);
|
|
126
|
+
if (item) {
|
|
127
|
+
item.status = success ? 'completed' : 'failed';
|
|
128
|
+
if (errorMsg) item.error = errorMsg;
|
|
129
|
+
}
|
|
130
|
+
if (success) prog.completed++;
|
|
131
|
+
else prog.failed++;
|
|
132
|
+
await chrome.storage.session.set({ [key]: prog });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async buildMirroredPath(filename) {
|
|
137
|
+
const folder = await this.getUserDownloadFolder();
|
|
138
|
+
const safeFilename = filename.replace(/[<>:"|?*\\]/g, '_').replace(/\s+/g, '_').substring(0, 200);
|
|
139
|
+
return `${folder}/${safeFilename}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async getUserDownloadFolder() {
|
|
143
|
+
const res = await chrome.storage.local.get('user_prefs');
|
|
144
|
+
return res.user_prefs?.downloadFolder || 'SourceGrab';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async downloadZip(tabId, urls, zipFilename) {
|
|
148
|
+
const entries = await this.registry.getAll(tabId);
|
|
149
|
+
let totalSize = 0;
|
|
150
|
+
const targets = [];
|
|
151
|
+
|
|
152
|
+
for (const url of urls) {
|
|
153
|
+
const normalized = this.registry.normalizeUrl(url);
|
|
154
|
+
const entry = entries.find(e => e.url === normalized);
|
|
155
|
+
if (entry) {
|
|
156
|
+
totalSize += (entry.size || 0);
|
|
157
|
+
targets.push(entry);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (totalSize > this.MAX_ZIP_SIZE) {
|
|
162
|
+
return { success: false, error: 'Exceeds maximum zip size' };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const zip = new ZipBuilder();
|
|
166
|
+
|
|
167
|
+
for (const entry of targets) {
|
|
168
|
+
let data = null;
|
|
169
|
+
if (entry.requestId) {
|
|
170
|
+
const bodyRes = await this.debuggerCapture.getResponseBody(tabId, entry.requestId);
|
|
171
|
+
if (bodyRes && bodyRes.body) {
|
|
172
|
+
if (bodyRes.base64Encoded) {
|
|
173
|
+
data = base64ToUint8Array(bodyRes.body);
|
|
174
|
+
} else {
|
|
175
|
+
data = new TextEncoder().encode(bodyRes.body);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!data) {
|
|
181
|
+
try {
|
|
182
|
+
const res = await fetch(entry.originalUrl);
|
|
183
|
+
const buf = await res.arrayBuffer();
|
|
184
|
+
data = new Uint8Array(buf);
|
|
185
|
+
} catch (e) {
|
|
186
|
+
console.warn('ZIP fallback fetch failed for:', entry.originalUrl, e);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (data) {
|
|
191
|
+
const path = `${new URL(entry.originalUrl).hostname}/${entry.filename}`;
|
|
192
|
+
zip.addFile(path, data);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const zipBytes = zip.generate();
|
|
197
|
+
const dataUrl = uint8ArrayToDataUrl(zipBytes, 'application/zip');
|
|
198
|
+
|
|
199
|
+
return new Promise((resolve) => {
|
|
200
|
+
chrome.downloads.download({
|
|
201
|
+
url: dataUrl,
|
|
202
|
+
filename: zipFilename,
|
|
203
|
+
conflictAction: 'uniquify',
|
|
204
|
+
saveAs: false
|
|
205
|
+
}, (downloadId) => {
|
|
206
|
+
if (chrome.runtime.lastError) {
|
|
207
|
+
console.error("ZIP download failed:", chrome.runtime.lastError.message);
|
|
208
|
+
resolve({ success: false, error: chrome.runtime.lastError.message });
|
|
209
|
+
} else {
|
|
210
|
+
resolve({ success: true });
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
class ZipBuilder {
|
|
218
|
+
constructor() {
|
|
219
|
+
this.files = [];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
addFile(filename, uint8ArrayData) {
|
|
223
|
+
this.files.push({ name: filename, data: uint8ArrayData });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
generate() {
|
|
227
|
+
let localHeaders = [];
|
|
228
|
+
let centralDirs = [];
|
|
229
|
+
let offset = 0;
|
|
230
|
+
|
|
231
|
+
for (const file of this.files) {
|
|
232
|
+
const nameBuf = new TextEncoder().encode(file.name);
|
|
233
|
+
const data = file.data;
|
|
234
|
+
const crc = crc32(data);
|
|
235
|
+
const size = data.length;
|
|
236
|
+
|
|
237
|
+
const lh = new Uint8Array(30 + nameBuf.length + size);
|
|
238
|
+
const lhView = new DataView(lh.buffer);
|
|
239
|
+
lhView.setUint32(0, 0x04034b50, true);
|
|
240
|
+
lhView.setUint16(4, 10, true);
|
|
241
|
+
lhView.setUint16(6, 0, true);
|
|
242
|
+
lhView.setUint16(8, 0, true); // STORE
|
|
243
|
+
lhView.setUint32(14, crc, true);
|
|
244
|
+
lhView.setUint32(18, size, true);
|
|
245
|
+
lhView.setUint32(22, size, true);
|
|
246
|
+
lhView.setUint16(26, nameBuf.length, true);
|
|
247
|
+
lh.set(nameBuf, 30);
|
|
248
|
+
lh.set(data, 30 + nameBuf.length);
|
|
249
|
+
localHeaders.push(lh);
|
|
250
|
+
|
|
251
|
+
const cd = new Uint8Array(46 + nameBuf.length);
|
|
252
|
+
const cdView = new DataView(cd.buffer);
|
|
253
|
+
cdView.setUint32(0, 0x02014b50, true);
|
|
254
|
+
cdView.setUint16(4, 10, true);
|
|
255
|
+
cdView.setUint16(6, 10, true);
|
|
256
|
+
cdView.setUint16(8, 0, true);
|
|
257
|
+
cdView.setUint16(10, 0, true); // STORE
|
|
258
|
+
cdView.setUint32(16, crc, true);
|
|
259
|
+
cdView.setUint32(20, size, true);
|
|
260
|
+
cdView.setUint32(24, size, true);
|
|
261
|
+
cdView.setUint16(28, nameBuf.length, true);
|
|
262
|
+
cdView.setUint32(42, offset, true);
|
|
263
|
+
cd.set(nameBuf, 46);
|
|
264
|
+
centralDirs.push(cd);
|
|
265
|
+
|
|
266
|
+
offset += lh.length;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let cdSize = centralDirs.reduce((acc, cd) => acc + cd.length, 0);
|
|
270
|
+
const eocd = new Uint8Array(22);
|
|
271
|
+
const eocdView = new DataView(eocd.buffer);
|
|
272
|
+
eocdView.setUint32(0, 0x06054b50, true);
|
|
273
|
+
eocdView.setUint16(8, this.files.length, true);
|
|
274
|
+
eocdView.setUint16(10, this.files.length, true);
|
|
275
|
+
eocdView.setUint32(12, cdSize, true);
|
|
276
|
+
eocdView.setUint32(16, offset, true);
|
|
277
|
+
|
|
278
|
+
const totalSize = offset + cdSize + 22;
|
|
279
|
+
const out = new Uint8Array(totalSize);
|
|
280
|
+
let pos = 0;
|
|
281
|
+
for (const lh of localHeaders) { out.set(lh, pos); pos += lh.length; }
|
|
282
|
+
for (const cd of centralDirs) { out.set(cd, pos); pos += cd.length; }
|
|
283
|
+
out.set(eocd, pos);
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const crcTable = new Uint32Array(256);
|
|
289
|
+
for (let i = 0; i < 256; i++) {
|
|
290
|
+
let c = i;
|
|
291
|
+
for (let j = 0; j < 8; j++) c = c & 1 ? 0xEDB88320 ^ (c >>> 1) : c >>> 1;
|
|
292
|
+
crcTable[i] = c;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function crc32(buf) {
|
|
296
|
+
let c = 0xFFFFFFFF;
|
|
297
|
+
for (let i = 0; i < buf.length; i++) c = crcTable[(c ^ buf[i]) & 0xFF] ^ (c >>> 8);
|
|
298
|
+
return (c ^ 0xFFFFFFFF) >>> 0;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function base64ToUint8Array(base64) {
|
|
302
|
+
const binary = atob(base64);
|
|
303
|
+
const bytes = new Uint8Array(binary.length);
|
|
304
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
305
|
+
return bytes;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function uint8ArrayToDataUrl(bytes, mimeType) {
|
|
309
|
+
let binary = '';
|
|
310
|
+
const chunkSize = 8192;
|
|
311
|
+
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
312
|
+
const chunk = bytes.subarray(i, Math.min(i + chunkSize, bytes.length));
|
|
313
|
+
binary += String.fromCharCode.apply(null, chunk);
|
|
314
|
+
}
|
|
315
|
+
return `data:${mimeType};base64,${btoa(binary)}`;
|
|
316
|
+
}
|