@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,426 @@
|
|
|
1
|
+
// background/service-worker.js
|
|
2
|
+
// v1.1 — Persistent data, onSuspend flush, deeper scan, stop-scroll, no auto-clear
|
|
3
|
+
|
|
4
|
+
import { CaptureRegistry } from './capture-registry.js';
|
|
5
|
+
import { DebuggerCapture } from './debugger-capture.js';
|
|
6
|
+
import { DownloadEngine } from './download-engine.js';
|
|
7
|
+
|
|
8
|
+
const registry = new CaptureRegistry();
|
|
9
|
+
const debuggerCapture = new DebuggerCapture(registry);
|
|
10
|
+
const downloadEngine = new DownloadEngine(registry, debuggerCapture);
|
|
11
|
+
|
|
12
|
+
// Track which tabs have active capture (in-memory, rehydrated from storage)
|
|
13
|
+
const activeTabs = new Set();
|
|
14
|
+
let keepAliveInterval = null;
|
|
15
|
+
|
|
16
|
+
function startKeepAlive() {
|
|
17
|
+
if (keepAliveInterval) return;
|
|
18
|
+
keepAliveInterval = setInterval(() => chrome.runtime.getPlatformInfo(() => {}), 25000);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function stopKeepAlive() {
|
|
22
|
+
if (keepAliveInterval) {
|
|
23
|
+
clearInterval(keepAliveInterval);
|
|
24
|
+
keepAliveInterval = null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─── Synchronous top-level event registration (CRITICAL for MV3) ───
|
|
29
|
+
|
|
30
|
+
chrome.runtime.onInstalled.addListener(() => {
|
|
31
|
+
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
chrome.runtime.onStartup.addListener(async () => {
|
|
35
|
+
await registry.init();
|
|
36
|
+
// Rehydrate active tab set from session storage (capture state is session-scoped)
|
|
37
|
+
const allData = await chrome.storage.session.get(null);
|
|
38
|
+
for (const [key, value] of Object.entries(allData)) {
|
|
39
|
+
if (key.startsWith('capture_state_') && value?.active) {
|
|
40
|
+
const tabId = parseInt(key.replace('capture_state_', ''), 10);
|
|
41
|
+
activeTabs.add(tabId);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (activeTabs.size > 0) startKeepAlive();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Eagerly init on first module load too (not just onStartup)
|
|
48
|
+
registry.init().then(() => {
|
|
49
|
+
chrome.storage.session.get(null).then(allData => {
|
|
50
|
+
for (const [key, value] of Object.entries(allData)) {
|
|
51
|
+
if (key.startsWith('capture_state_') && value?.active) {
|
|
52
|
+
const tabId = parseInt(key.replace('capture_state_', ''), 10);
|
|
53
|
+
activeTabs.add(tabId);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (activeTabs.size > 0) startKeepAlive();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// ─── Service worker suspend — flush all pending data ───
|
|
61
|
+
|
|
62
|
+
chrome.runtime.onSuspend.addListener(() => {
|
|
63
|
+
registry.flushAll();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// ─── Tab lifecycle ───
|
|
67
|
+
|
|
68
|
+
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
69
|
+
activeTabs.delete(tabId);
|
|
70
|
+
// DON'T clear registry on tab close — user must explicitly clear.
|
|
71
|
+
// Only clear the session-scoped capture state.
|
|
72
|
+
chrome.storage.session.remove(`capture_state_${tabId}`);
|
|
73
|
+
chrome.storage.session.remove(`download_progress_${tabId}`);
|
|
74
|
+
if (activeTabs.size === 0) stopKeepAlive();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
|
|
78
|
+
if (changeInfo.url && activeTabs.has(tabId)) {
|
|
79
|
+
await registry.addNavigationMarker(tabId, changeInfo.url);
|
|
80
|
+
|
|
81
|
+
// Update capture state URL
|
|
82
|
+
const stateKey = `capture_state_${tabId}`;
|
|
83
|
+
const stateData = await chrome.storage.session.get(stateKey);
|
|
84
|
+
if (stateData[stateKey]) {
|
|
85
|
+
await chrome.storage.session.set({
|
|
86
|
+
[stateKey]: { ...stateData[stateKey], tabUrl: changeInfo.url }
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Re-inject content scripts on navigation
|
|
91
|
+
try {
|
|
92
|
+
await chrome.scripting.executeScript({ target: { tabId, allFrames: true }, files: ['content-scripts/main-world-hook.js'], world: 'MAIN', injectImmediately: true });
|
|
93
|
+
await chrome.scripting.executeScript({ target: { tabId, allFrames: true }, files: ['content-scripts/isolated-bridge.js'], world: 'ISOLATED', injectImmediately: true });
|
|
94
|
+
} catch(e) { /* Tab may not be ready yet */ }
|
|
95
|
+
|
|
96
|
+
// Re-scan resource tree after navigation if debugger is attached
|
|
97
|
+
const state = stateData[stateKey];
|
|
98
|
+
if (state?.deepScan) {
|
|
99
|
+
setTimeout(async () => {
|
|
100
|
+
try { await debuggerCapture.getResourceTree(tabId); } catch(e) {}
|
|
101
|
+
}, 1500); // Wait for page to load resources
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ─── webRequest listeners (fire only after host_permissions granted) ───
|
|
107
|
+
|
|
108
|
+
chrome.webRequest.onBeforeRequest.addListener(
|
|
109
|
+
(details) => {
|
|
110
|
+
if (details.tabId >= 0 && activeTabs.has(details.tabId)) {
|
|
111
|
+
registry.upsert(details.tabId, {
|
|
112
|
+
url: details.url,
|
|
113
|
+
source: 'webRequest',
|
|
114
|
+
status: 'requested',
|
|
115
|
+
resourceType: details.type
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{ urls: ['<all_urls>'] }
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
chrome.webRequest.onResponseStarted.addListener(
|
|
123
|
+
(details) => {
|
|
124
|
+
if (details.tabId >= 0 && activeTabs.has(details.tabId)) {
|
|
125
|
+
let size = null;
|
|
126
|
+
const cl = details.responseHeaders?.find(h => h.name.toLowerCase() === 'content-length');
|
|
127
|
+
if (cl) size = parseInt(cl.value, 10);
|
|
128
|
+
const ct = details.responseHeaders?.find(h => h.name.toLowerCase() === 'content-type');
|
|
129
|
+
|
|
130
|
+
registry.upsert(details.tabId, {
|
|
131
|
+
url: details.url,
|
|
132
|
+
statusCode: details.statusCode,
|
|
133
|
+
mimeType: ct ? ct.value.split(';')[0].trim() : null,
|
|
134
|
+
size: isNaN(size) ? null : size,
|
|
135
|
+
resourceType: details.type
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
{ urls: ['<all_urls>'] },
|
|
140
|
+
['responseHeaders']
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
chrome.webRequest.onCompleted.addListener(
|
|
144
|
+
(details) => {
|
|
145
|
+
if (details.tabId >= 0 && activeTabs.has(details.tabId)) {
|
|
146
|
+
registry.upsert(details.tabId, {
|
|
147
|
+
url: details.url,
|
|
148
|
+
status: 'completed'
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
{ urls: ['<all_urls>'] }
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
chrome.webRequest.onErrorOccurred.addListener(
|
|
156
|
+
(details) => {
|
|
157
|
+
if (details.tabId >= 0 && activeTabs.has(details.tabId)) {
|
|
158
|
+
registry.upsert(details.tabId, {
|
|
159
|
+
url: details.url,
|
|
160
|
+
status: 'error'
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{ urls: ['<all_urls>'] }
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// ─── Message router ───
|
|
168
|
+
|
|
169
|
+
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
|
170
|
+
// Content script resource detection
|
|
171
|
+
if (msg.type === 'RESOURCE_DETECTED') {
|
|
172
|
+
if (sender.tab && sender.tab.id && activeTabs.has(sender.tab.id)) {
|
|
173
|
+
const payload = msg.payload;
|
|
174
|
+
const tabId = sender.tab.id;
|
|
175
|
+
|
|
176
|
+
if (payload.type === 'RESOURCE_BLOB_DATA') {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const entry = { url: payload.url, source: payload.source || 'domHook' };
|
|
181
|
+
if (payload.statusCode) entry.statusCode = payload.statusCode;
|
|
182
|
+
if (payload.mimeType) entry.mimeType = payload.mimeType;
|
|
183
|
+
if (payload.size) entry.size = payload.size;
|
|
184
|
+
if (payload.filename) entry.filename = payload.filename;
|
|
185
|
+
if (payload.type === 'FETCH_RESPONSE' || payload.type === 'XHR_RESPONSE') {
|
|
186
|
+
entry.status = 'completed';
|
|
187
|
+
}
|
|
188
|
+
if (payload.type === 'INLINE_SVG') {
|
|
189
|
+
entry.mimeType = 'image/svg+xml';
|
|
190
|
+
entry.status = 'completed';
|
|
191
|
+
entry.domContext = 'inline-svg';
|
|
192
|
+
}
|
|
193
|
+
if (payload.type === 'CSS_BACKGROUND') {
|
|
194
|
+
entry.domContext = 'css-background';
|
|
195
|
+
}
|
|
196
|
+
if (payload.type === 'DOM_RESOURCE') {
|
|
197
|
+
entry.domContext = payload.domContext || 'mutation';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
registry.upsert(tabId, entry);
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ─── Side Panel messages ───
|
|
206
|
+
|
|
207
|
+
if (msg.type === 'START_CAPTURE') {
|
|
208
|
+
(async () => {
|
|
209
|
+
try {
|
|
210
|
+
const tabId = msg.tabId;
|
|
211
|
+
activeTabs.add(tabId);
|
|
212
|
+
|
|
213
|
+
// Inject content scripts into the current tab
|
|
214
|
+
try {
|
|
215
|
+
await chrome.scripting.executeScript({ target: { tabId, allFrames: true }, files: ['content-scripts/main-world-hook.js'], world: 'MAIN', injectImmediately: true });
|
|
216
|
+
} catch(e) { console.warn('Main hook inject:', e.message); }
|
|
217
|
+
try {
|
|
218
|
+
await chrome.scripting.executeScript({ target: { tabId, allFrames: true }, files: ['content-scripts/isolated-bridge.js'], world: 'ISOLATED', injectImmediately: true });
|
|
219
|
+
} catch(e) { console.warn('Bridge inject:', e.message); }
|
|
220
|
+
|
|
221
|
+
// Register for future navigations (may already be registered, so try-catch)
|
|
222
|
+
try {
|
|
223
|
+
await chrome.scripting.registerContentScripts([
|
|
224
|
+
{ id: 'sg-main-hook', js: ['content-scripts/main-world-hook.js'], matches: ['<all_urls>'], runAt: 'document_start', world: 'MAIN', allFrames: true },
|
|
225
|
+
{ id: 'sg-bridge', js: ['content-scripts/isolated-bridge.js'], matches: ['<all_urls>'], runAt: 'document_start', world: 'ISOLATED', allFrames: true }
|
|
226
|
+
]);
|
|
227
|
+
} catch (regErr) {
|
|
228
|
+
console.log('Content scripts already registered:', regErr.message);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Attempt debugger attach (debugger is now a required permission)
|
|
232
|
+
let deepScan = false;
|
|
233
|
+
try {
|
|
234
|
+
await debuggerCapture.attach(tabId);
|
|
235
|
+
deepScan = true;
|
|
236
|
+
|
|
237
|
+
// Schedule a second resource tree scan after 3s for late-loading resources
|
|
238
|
+
setTimeout(async () => {
|
|
239
|
+
try { await debuggerCapture.getResourceTree(tabId); } catch(e) {}
|
|
240
|
+
}, 3000);
|
|
241
|
+
} catch (e) {
|
|
242
|
+
console.warn('Debugger attach failed:', e);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const tab = await chrome.tabs.get(tabId);
|
|
246
|
+
await chrome.storage.session.set({
|
|
247
|
+
[`capture_state_${tabId}`]: { active: true, deepScan, tabUrl: tab.url, startTime: Date.now() }
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
startKeepAlive();
|
|
251
|
+
sendResponse({ success: true });
|
|
252
|
+
} catch (e) {
|
|
253
|
+
sendResponse({ success: false, error: e.message });
|
|
254
|
+
}
|
|
255
|
+
})();
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (msg.type === 'STOP_CAPTURE') {
|
|
260
|
+
(async () => {
|
|
261
|
+
try {
|
|
262
|
+
const tabId = msg.tabId;
|
|
263
|
+
activeTabs.delete(tabId);
|
|
264
|
+
|
|
265
|
+
// Detach debugger
|
|
266
|
+
await debuggerCapture.detach(tabId);
|
|
267
|
+
|
|
268
|
+
// Unregister content scripts
|
|
269
|
+
try {
|
|
270
|
+
await chrome.scripting.unregisterContentScripts({ ids: ['sg-main-hook', 'sg-bridge'] });
|
|
271
|
+
} catch (e) { /* may not be registered */ }
|
|
272
|
+
|
|
273
|
+
// Update state — mark as inactive but DON'T clear registry
|
|
274
|
+
const stateKey = `capture_state_${tabId}`;
|
|
275
|
+
const data = await chrome.storage.session.get(stateKey);
|
|
276
|
+
if (data[stateKey]) {
|
|
277
|
+
await chrome.storage.session.set({
|
|
278
|
+
[stateKey]: { ...data[stateKey], active: false }
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Flush any pending registry writes to ensure data is saved
|
|
283
|
+
await registry.flushAll();
|
|
284
|
+
|
|
285
|
+
if (activeTabs.size === 0) stopKeepAlive();
|
|
286
|
+
sendResponse({ success: true });
|
|
287
|
+
} catch (e) {
|
|
288
|
+
sendResponse({ success: false, error: e.message });
|
|
289
|
+
}
|
|
290
|
+
})();
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (msg.type === 'TOGGLE_DEEP_SCAN') {
|
|
295
|
+
(async () => {
|
|
296
|
+
try {
|
|
297
|
+
const tabId = msg.tabId;
|
|
298
|
+
const enabled = msg.enabled;
|
|
299
|
+
|
|
300
|
+
if (enabled) {
|
|
301
|
+
try {
|
|
302
|
+
await debuggerCapture.attach(tabId);
|
|
303
|
+
// Immediate + delayed resource tree scan for thorough coverage
|
|
304
|
+
setTimeout(async () => {
|
|
305
|
+
try { await debuggerCapture.getResourceTree(tabId); } catch(e) {}
|
|
306
|
+
}, 2000);
|
|
307
|
+
} catch(e) {
|
|
308
|
+
console.warn('Debugger attach for deep scan:', e);
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
await debuggerCapture.detach(tabId);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Update capture state
|
|
315
|
+
const stateKey = `capture_state_${tabId}`;
|
|
316
|
+
const data = await chrome.storage.session.get(stateKey);
|
|
317
|
+
if (data[stateKey]) {
|
|
318
|
+
await chrome.storage.session.set({
|
|
319
|
+
[stateKey]: { ...data[stateKey], deepScan: enabled }
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
sendResponse({ success: true });
|
|
324
|
+
} catch (e) {
|
|
325
|
+
sendResponse({ success: false, error: e.message });
|
|
326
|
+
}
|
|
327
|
+
})();
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (msg.type === 'DOWNLOAD_SELECTED') {
|
|
332
|
+
(async () => {
|
|
333
|
+
try {
|
|
334
|
+
const batchId = await downloadEngine.downloadBatch(msg.tabId, msg.urls);
|
|
335
|
+
sendResponse({ batchId });
|
|
336
|
+
} catch (e) {
|
|
337
|
+
sendResponse({ error: e.message });
|
|
338
|
+
}
|
|
339
|
+
})();
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (msg.type === 'DOWNLOAD_ZIP') {
|
|
344
|
+
(async () => {
|
|
345
|
+
try {
|
|
346
|
+
const res = await downloadEngine.downloadZip(msg.tabId, msg.urls, msg.zipFilename);
|
|
347
|
+
sendResponse(res);
|
|
348
|
+
} catch (e) {
|
|
349
|
+
sendResponse({ success: false, error: e.message });
|
|
350
|
+
}
|
|
351
|
+
})();
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (msg.type === 'AUTO_SCROLL') {
|
|
356
|
+
(async () => {
|
|
357
|
+
try {
|
|
358
|
+
await chrome.scripting.executeScript({ target: { tabId: msg.tabId }, files: ['content-scripts/auto-scroll.js'], world: 'MAIN' });
|
|
359
|
+
sendResponse({ success: true });
|
|
360
|
+
} catch (e) {
|
|
361
|
+
sendResponse({ success: false, error: e.message });
|
|
362
|
+
}
|
|
363
|
+
})();
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (msg.type === 'STOP_AUTO_SCROLL') {
|
|
368
|
+
(async () => {
|
|
369
|
+
try {
|
|
370
|
+
await chrome.scripting.executeScript({
|
|
371
|
+
target: { tabId: msg.tabId },
|
|
372
|
+
func: () => { window.__sgStopScroll = true; },
|
|
373
|
+
world: 'MAIN'
|
|
374
|
+
});
|
|
375
|
+
sendResponse({ success: true });
|
|
376
|
+
} catch (e) {
|
|
377
|
+
sendResponse({ success: false, error: e.message });
|
|
378
|
+
}
|
|
379
|
+
})();
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (msg.type === 'CLEAR_CAPTURE') {
|
|
384
|
+
(async () => {
|
|
385
|
+
try {
|
|
386
|
+
await registry.clear(msg.tabId);
|
|
387
|
+
sendResponse({ success: true });
|
|
388
|
+
} catch (e) {
|
|
389
|
+
sendResponse({ success: false, error: e.message });
|
|
390
|
+
}
|
|
391
|
+
})();
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (msg.type === 'RETRY_DOWNLOAD') {
|
|
396
|
+
(async () => {
|
|
397
|
+
try {
|
|
398
|
+
await downloadEngine.downloadBatch(msg.tabId, [msg.url]);
|
|
399
|
+
sendResponse({ success: true });
|
|
400
|
+
} catch (e) {
|
|
401
|
+
sendResponse({ success: false, error: e.message });
|
|
402
|
+
}
|
|
403
|
+
})();
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// GET_REGISTRY — side panel requests full registry data for a tab
|
|
408
|
+
if (msg.type === 'GET_REGISTRY') {
|
|
409
|
+
(async () => {
|
|
410
|
+
try {
|
|
411
|
+
const tabId = msg.tabId;
|
|
412
|
+
const data = await chrome.storage.local.get(`registry_${tabId}`);
|
|
413
|
+
sendResponse({ registry: data[`registry_${tabId}`] || null });
|
|
414
|
+
} catch (e) {
|
|
415
|
+
sendResponse({ registry: null });
|
|
416
|
+
}
|
|
417
|
+
})();
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
// ─── Downloads tracker ───
|
|
423
|
+
|
|
424
|
+
chrome.downloads.onChanged.addListener((delta) => {
|
|
425
|
+
// DownloadEngine tracks its own downloads internally via chromeDownload()
|
|
426
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// content-scripts/auto-scroll.js
|
|
2
|
+
// v1.1 — Stoppable auto-scroll via window.__sgStopScroll flag
|
|
3
|
+
|
|
4
|
+
(function() {
|
|
5
|
+
// Reset stop flag
|
|
6
|
+
window.__sgStopScroll = false;
|
|
7
|
+
|
|
8
|
+
const SCROLL_STEP = 400;
|
|
9
|
+
const SCROLL_INTERVAL = 600;
|
|
10
|
+
const IDLE_THRESHOLD = 3;
|
|
11
|
+
const MAX_SCROLLS = 500;
|
|
12
|
+
|
|
13
|
+
let scrollCount = 0;
|
|
14
|
+
let lastHeight = document.documentElement.scrollHeight;
|
|
15
|
+
let idleCount = 0;
|
|
16
|
+
|
|
17
|
+
const interval = setInterval(() => {
|
|
18
|
+
// Check stop flag
|
|
19
|
+
if (window.__sgStopScroll) {
|
|
20
|
+
clearInterval(interval);
|
|
21
|
+
window.__sgStopScroll = false;
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const currentHeight = document.documentElement.scrollHeight;
|
|
26
|
+
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
|
27
|
+
const clientHeight = window.innerHeight;
|
|
28
|
+
|
|
29
|
+
// At the bottom?
|
|
30
|
+
if (scrollTop + clientHeight >= currentHeight - 10) {
|
|
31
|
+
if (currentHeight === lastHeight) {
|
|
32
|
+
idleCount++;
|
|
33
|
+
} else {
|
|
34
|
+
idleCount = 0;
|
|
35
|
+
lastHeight = currentHeight;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (idleCount >= IDLE_THRESHOLD) {
|
|
39
|
+
clearInterval(interval);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
window.scrollBy({ top: SCROLL_STEP, behavior: 'smooth' });
|
|
45
|
+
scrollCount++;
|
|
46
|
+
|
|
47
|
+
if (scrollCount >= MAX_SCROLLS) {
|
|
48
|
+
clearInterval(interval);
|
|
49
|
+
}
|
|
50
|
+
}, SCROLL_INTERVAL);
|
|
51
|
+
})();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// content-scripts/isolated-bridge.js
|
|
2
|
+
// Runs in ISOLATED world. Relays messages between MAIN world and service worker.
|
|
3
|
+
|
|
4
|
+
(function() {
|
|
5
|
+
if (window.__SOURCEGRAB_BRIDGE__) return;
|
|
6
|
+
window.__SOURCEGRAB_BRIDGE__ = true;
|
|
7
|
+
|
|
8
|
+
const CAPTURE_CHANNEL = '__SOURCEGRAB_CAPTURE__';
|
|
9
|
+
const CMD_CHANNEL = '__SOURCEGRAB_COMMAND__';
|
|
10
|
+
|
|
11
|
+
// MAIN world → Service Worker
|
|
12
|
+
window.addEventListener('message', (event) => {
|
|
13
|
+
if (event.source !== window) return;
|
|
14
|
+
if (event.data?.channel !== CAPTURE_CHANNEL) return;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
chrome.runtime.sendMessage({
|
|
18
|
+
type: 'RESOURCE_DETECTED',
|
|
19
|
+
payload: event.data.payload
|
|
20
|
+
});
|
|
21
|
+
} catch (e) {
|
|
22
|
+
// Extension context may be invalidated (e.g., extension reloaded)
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Service Worker → MAIN world
|
|
27
|
+
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
|
28
|
+
if (msg.type === 'COMMAND_TO_PAGE') {
|
|
29
|
+
window.postMessage({
|
|
30
|
+
channel: CMD_CHANNEL,
|
|
31
|
+
command: msg.command,
|
|
32
|
+
payload: msg.payload
|
|
33
|
+
}, '*');
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
})();
|