-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbackground.js
More file actions
161 lines (146 loc) · 5.4 KB
/
Copy pathbackground.js
File metadata and controls
161 lines (146 loc) · 5.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// Service worker: orchestrates the hidden helper tab through pahe.win -> kwik -> .mp4.
//
// The MV3 service worker can be terminated between events, so all in-flight state lives in
// chrome.storage.session (re-read on every event) rather than in module variables.
//
// Requests are serialized (one in flight at a time; extra clicks queue) because
// chrome.downloads.onCreated does NOT reliably report the initiating tab — with a single active
// request, matching the new download to its helper tab is unambiguous.
importScripts('config.js');
const CFG = globalThis.APD_CONFIG;
const B = CFG.behavior;
const REG_KEY = 'apd_registry'; // { [helperTabId]: { originTabId, state, startedAt, downloadId } }
const QUEUE_KEY = 'apd_queue'; // [ { paheUrl, originTabId } ]
const getReg = async () => (await chrome.storage.session.get(REG_KEY))[REG_KEY] || {};
const setReg = (reg) => chrome.storage.session.set({ [REG_KEY]: reg });
const getQueue = async () => (await chrome.storage.session.get(QUEUE_KEY))[QUEUE_KEY] || [];
const setQueue = (q) => chrome.storage.session.set({ [QUEUE_KEY]: q });
async function hasActive() {
return Object.keys(await getReg()).length > 0;
}
async function notify(originTabId, state, detail) {
if (originTabId == null) return;
try {
await chrome.tabs.sendMessage(originTabId, { type: 'APD_STATUS', state, detail });
} catch (e) {
/* origin tab gone; ignore */
}
}
// Start the next queued request if nothing is currently active.
async function pump() {
if (await hasActive()) return;
const q = await getQueue();
if (!q.length) return;
const next = q.shift();
await setQueue(q);
await beginRequest(next);
}
async function beginRequest({ paheUrl, originTabId }) {
const tab = await chrome.tabs.create({ url: paheUrl, active: false });
const reg = await getReg();
reg[tab.id] = { originTabId, state: 'pahe', startedAt: Date.now(), downloadId: null };
await setReg(reg);
await notify(originTabId, 'opening');
// Watchdog: if this request hasn't produced a download in time, reveal/clean it up.
setTimeout(() => failRequest(tab.id), B.overallTimeoutMs);
}
async function failRequest(tabId) {
const reg = await getReg();
const entry = reg[tabId];
if (!entry || entry.downloadId != null) return; // already resolved / download is underway
await notify(entry.originTabId, 'manual');
if (B.revealTabOnFailure) {
try {
await chrome.tabs.update(tabId, { active: true });
const t = await chrome.tabs.get(tabId);
await chrome.windows.update(t.windowId, { focused: true });
} catch (e) {
/* tab gone */
}
}
delete reg[tabId];
await setReg(reg);
pump();
}
async function closeHelper(tabId) {
const reg = await getReg();
if (!reg[tabId]) return;
delete reg[tabId];
await setReg(reg);
try {
await chrome.tabs.remove(Number(tabId));
} catch (e) {
/* already closed */
}
pump();
}
// --- messages from content scripts ---
chrome.runtime.onMessage.addListener((msg, sender) => {
(async () => {
if (!msg) return;
if (msg.type === 'APD_START') {
const q = await getQueue();
q.push({ paheUrl: msg.paheUrl, originTabId: sender.tab ? sender.tab.id : null });
await setQueue(q);
pump();
return;
}
const tabId = sender.tab ? sender.tab.id : null;
if (tabId == null) return;
const reg = await getReg();
if (!reg[tabId]) return;
if (msg.type === 'APD_PROGRESS') {
await notify(reg[tabId].originTabId, msg.stage);
} else if (msg.type === 'APD_KWIK_READY') {
reg[tabId].state = 'kwik-submit';
await setReg(reg);
await notify(reg[tabId].originTabId, 'submitting', msg.detail);
} else if (msg.type === 'APD_FAIL') {
await failRequest(tabId);
}
})();
return false;
});
// --- download lifecycle ---
chrome.downloads.onCreated.addListener((item) => {
(async () => {
const reg = await getReg();
const ids = Object.keys(reg);
if (!ids.length) return;
// Serialized => at most one active request. Only attach to the request that just submitted the
// kwik form (state 'kwik-submit'), so an unrelated download started while our helper tab is still
// clearing Cloudflare on pahe.win/kwik is never misattributed to us.
const tabId = ids.find((id) => reg[id].state === 'kwik-submit');
if (!tabId) return;
reg[tabId].downloadId = item.id;
reg[tabId].state = 'downloading';
await setReg(reg);
await notify(reg[tabId].originTabId, 'started');
// Don't close immediately (would abort the in-flight request). Fallback close after a delay.
setTimeout(() => closeHelper(tabId), B.closeTabAfterMs);
})();
});
chrome.downloads.onChanged.addListener((delta) => {
(async () => {
const reg = await getReg();
const tabId = Object.keys(reg).find((id) => reg[id].downloadId === delta.id);
if (!tabId) return;
const fname = delta.filename && delta.filename.current;
if (fname) {
await notify(reg[tabId].originTabId, 'downloading', fname.split(/[\\/]/).pop());
}
const done = fname || (delta.state && delta.state.current === 'complete');
if (done) closeHelper(tabId); // the browser now owns the file; the helper tab can go
})();
});
// Clean up the registry if a helper tab is closed (by us or the user).
chrome.tabs.onRemoved.addListener((tabId) => {
(async () => {
const reg = await getReg();
if (reg[tabId]) {
delete reg[tabId];
await setReg(reg);
pump();
}
})();
});