Skip to content

Commit 252b7b8

Browse files
committed
Keep long YouTube imports alive
1 parent 61bd884 commit 252b7b8

4 files changed

Lines changed: 59 additions & 12 deletions

File tree

chrome-extension/background.js

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,6 @@ async function startHistoryImport() {
228228
observedAt,
229229
});
230230
if (!result?.ok) throw new Error(result?.error || 'YouTube History import failed');
231-
await historyStatus({
232-
state: 'complete',
233-
videos: result.videos,
234-
completedAt: new Date().toISOString(),
235-
lastError: '',
236-
});
237231
} catch (error) {
238232
const storedStatus = await chrome.storage.local.get(HISTORY_STATUS_KEY);
239233
if (storedStatus[HISTORY_STATUS_KEY]?.state === 'cancelled') return;
@@ -260,6 +254,17 @@ async function cancelHistoryImport() {
260254
});
261255
}
262256

257+
async function finishHistoryImport(scanId, patch) {
258+
const stored = await chrome.storage.local.get(HISTORY_STATUS_KEY);
259+
const status = stored[HISTORY_STATUS_KEY] ?? {};
260+
if (status.scanId !== scanId || status.state !== 'running') return false;
261+
await historyStatus({
262+
...patch,
263+
completedAt: new Date().toISOString(),
264+
});
265+
return true;
266+
}
267+
263268
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
264269
if (message?.type === 'capture' && message.payload) {
265270
enqueue(message.payload)
@@ -306,6 +311,25 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
306311
.catch((error) => sendResponse({ ok: false, error: String(error) }));
307312
return true;
308313
}
314+
if (message?.type === 'history-import-complete') {
315+
finishHistoryImport(message.scanId, {
316+
state: 'complete',
317+
videos: message.videos,
318+
lastError: '',
319+
})
320+
.then((updated) => sendResponse({ ok: true, updated }))
321+
.catch((error) => sendResponse({ ok: false, error: String(error) }));
322+
return true;
323+
}
324+
if (message?.type === 'history-import-error') {
325+
finishHistoryImport(message.scanId, {
326+
state: 'error',
327+
lastError: String(message.error || 'YouTube History import failed'),
328+
})
329+
.then((updated) => sendResponse({ ok: true, updated }))
330+
.catch((error) => sendResponse({ ok: false, error: String(error) }));
331+
return true;
332+
}
309333
return false;
310334
});
311335

chrome-extension/content.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,12 +242,18 @@
242242
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
243243
if (message?.type === 'start-history-import') {
244244
runHistoryImport(message.scanId, message.observedAt)
245-
.then((videos) => sendResponse({ ok: true, videos }))
246-
.catch((error) => sendResponse({
247-
ok: false,
245+
.then((videos) => chrome.runtime.sendMessage({
246+
type: 'history-import-complete',
247+
scanId: message.scanId,
248+
videos,
249+
}))
250+
.catch((error) => chrome.runtime.sendMessage({
251+
type: 'history-import-error',
252+
scanId: message.scanId,
248253
error: error instanceof Error ? error.message : String(error),
249254
}));
250-
return true;
255+
sendResponse({ ok: true, started: true });
256+
return false;
251257
}
252258
if (message?.type === 'cancel-history-import') {
253259
historyImportCancelled = true;

chrome-extension/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "infovore YouTube Capture",
4-
"version": "1.2.0",
4+
"version": "1.3.0",
55
"description": "Privately records measured YouTube watch time and imports saved viewing progress to infovore.",
66
"minimum_chrome_version": "120",
77
"permissions": [

tests/extension.test.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ test('Chrome extension manifest is least-privilege and captures YouTube SPA page
1111
'utf8',
1212
));
1313
assert.equal(manifest.manifest_version, 3);
14-
assert.equal(manifest.version, '1.2.0');
14+
assert.equal(manifest.version, '1.3.0');
1515
assert.deepEqual(manifest.permissions.sort(), ['alarms', 'storage']);
1616
assert.deepEqual(manifest.host_permissions, ['https://infovore.skyhong.tw/*']);
1717
assert.deepEqual(manifest.content_scripts[0].matches, [
@@ -34,6 +34,23 @@ test('dashboard bridge exposes import status without exposing capture credential
3434
assert.doesNotMatch(source, /captureSettings|captureToken|authorization|Bearer/);
3535
});
3636

37+
test('history import reports long-running completion without holding the start message open', () => {
38+
const background = readFileSync(
39+
new URL('../chrome-extension/background.js', import.meta.url),
40+
'utf8',
41+
);
42+
const content = readFileSync(
43+
new URL('../chrome-extension/content.js', import.meta.url),
44+
'utf8',
45+
);
46+
assert.match(content, /history-import-complete/);
47+
assert.match(content, /history-import-error/);
48+
assert.match(content, /sendResponse\(\{ ok: true, started: true \}\)/);
49+
assert.match(background, /status\.scanId !== scanId \|\| status\.state !== 'running'/);
50+
assert.match(background, /finishHistoryImport\(message\.scanId/);
51+
assert.doesNotMatch(background, /videos: result\.videos/);
52+
});
53+
3754
test('capture retry queue keeps only the newest cumulative session update', () => {
3855
const first = {
3956
sessionId: 'capture-session-123456',

0 commit comments

Comments
 (0)