Skip to content

Commit 0023527

Browse files
committed
Enhance ESLint configuration by adding jQuery plugin and updating rules. Update package dependencies and version in manifest. Refactor background and core scripts for improved initialization and async handling. Modify options and popup scripts to use modern event listeners and improve audio playback handling.
1 parent 8bc8921 commit 0023527

File tree

8 files changed

+134
-67
lines changed

8 files changed

+134
-67
lines changed

.eslintrc.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"sourceType": "script"
1010
},
1111
"extends": ["eslint:recommended"],
12+
"plugins": ["jquery"],
1213
"globals": {
1314
"browser": "readonly",
1415
"chrome": "readonly",
@@ -30,7 +31,9 @@
3031
"toggleSavedFeed": "readonly",
3132
"openFeedlyTab": "readonly",
3233
"resetCounter": "readonly",
33-
"getAccessToken": "readonly"
34+
"getAccessToken": "readonly",
35+
"readOptions": "readonly",
36+
"initialize": "readonly"
3437
}
3538
},
3639
{
@@ -50,6 +53,10 @@
5053
"no-console": "off",
5154
"no-unused-vars": ["warn", { "args": "none" }],
5255
"no-var": "off",
53-
"no-empty": ["error", { "allowEmptyCatch": true }]
56+
// Warn on empty blocks (including catch)
57+
"no-empty": ["warn", { "allowEmptyCatch": false }],
58+
59+
// jQuery deprecations and antipatterns
60+
"jquery/no-ready": "warn"
5461
}
5562
}

package-lock.json

Lines changed: 14 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
"name": "feedly-notifier",
33
"description": "Feedly Notifier is a tiny extension that keeps you up to date with your feedly subscriptions.",
44
"devDependencies": {
5+
"eslint": "^8.57.0",
6+
"eslint-plugin-jquery": "^1.5.1",
57
"grunt": "^1.6.1",
68
"grunt-contrib-clean": "^2.0.1",
79
"grunt-contrib-copy": "^1.0.0",
810
"grunt-preprocess": "^5.1.0",
911
"grunt-string-replace": "^1.3.3",
10-
"grunt-zip": "^1.0.0",
11-
"eslint": "^8.57.0"
12+
"grunt-zip": "^1.0.0"
1213
},
1314
"repository": {
1415
"type": "git",

src/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
"name": "Feedly Notifier",
55
"description": "__MSG_ExtensionDescription__",
6-
"version": "2.28.0",
6+
"version": "3.0.0",
77
"default_locale": "en",
88

99
"permissions": [

src/scripts/background.js

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,42 +6,57 @@
66
// Load polyfill and core logic (relative to this file in /scripts)
77
importScripts("browser-polyfill.min.js", "feedly.api.js", "core.js");
88

9+
// Ensure options/tokens are loaded after each worker start
10+
let __initPromise = null;
11+
function ensureInitialized() {
12+
if (!__initPromise) {
13+
__initPromise = (async () => {
14+
await readOptions();
15+
await initialize(false);
16+
})();
17+
}
18+
return __initPromise;
19+
}
20+
21+
// Kick off initialization eagerly on worker boot
22+
ensureInitialized();
23+
924
// Route messages from UI pages to background functions
1025
browser.runtime.onMessage.addListener((message, sender) => {
26+
const ready = ensureInitialized();
1127
try {
1228
switch (message && message.type) {
1329
case "getState":
14-
return Promise.resolve({
30+
return ready.then(() => ({
1531
options: appGlobal.options,
1632
environment: appGlobal.environment,
1733
isLoggedIn: appGlobal.isLoggedIn || false
18-
});
34+
}));
1935
case "getOptions":
20-
return Promise.resolve({ options: appGlobal.options });
36+
return ready.then(() => ({ options: appGlobal.options }));
2137
case "getFeeds":
22-
return getFeeds(Boolean(message.forceUpdate));
38+
return ready.then(() => getFeeds(Boolean(message.forceUpdate)));
2339
case "getSavedFeeds":
24-
return getSavedFeeds(Boolean(message.forceUpdate));
40+
return ready.then(() => getSavedFeeds(Boolean(message.forceUpdate)));
2541
case "markAsRead":
26-
return markAsRead(message.feedIds || []).then(ok => ({ ok: !!ok }));
42+
return ready.then(() => markAsRead(message.feedIds || []).then(ok => ({ ok: !!ok })));
2743
case "toggleSavedFeed":
28-
return toggleSavedFeed(message.feedIds || [], !!message.save).then(ok => ({ ok: !!ok }));
44+
return ready.then(() => toggleSavedFeed(message.feedIds || [], !!message.save).then(ok => ({ ok: !!ok })));
2945
case "openFeedlyTab":
30-
return openFeedlyTab().then(() => ({ ok: true }));
46+
return ready.then(() => openFeedlyTab().then(() => ({ ok: true })));
3147
case "resetCounter":
32-
return (typeof resetCounter === "function" ? Promise.resolve(resetCounter()) : Promise.resolve()).then(() => ({ ok: true }));
48+
return ready.then(() => (typeof resetCounter === "function" ? Promise.resolve(resetCounter()) : Promise.resolve()).then(() => ({ ok: true })));
3349
case "getFeedTabId":
34-
return Promise.resolve({ feedTabId: appGlobal.feedTabId || null });
50+
return ready.then(() => ({ feedTabId: appGlobal.feedTabId || null }));
3551
case "setFeedTabId":
36-
appGlobal.feedTabId = message.tabId;
37-
return Promise.resolve({ ok: true });
52+
return ready.then(() => { appGlobal.feedTabId = message.tabId; return { ok: true }; });
3853
case "getAccessToken":
39-
return getAccessToken().then(() => ({ ok: true }));
54+
return ready.then(() => getAccessToken().then(() => ({ ok: true })));
4055
default:
4156
return Promise.resolve({ error: "Unknown message type" });
4257
}
4358
} catch (e) {
44-
try { console.error("background message error", e); } catch (_) {}
59+
console.error("background message error", e);
4560
return Promise.resolve({ error: "Internal error" });
4661
}
4762
});

src/scripts/core.js

Lines changed: 67 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -174,17 +174,19 @@ browser.tabs.onRemoved.addListener(function(tabId){
174174
});
175175

176176
/* Listener for adding or removing feeds on the feedly website */
177-
browser.webRequest.onCompleted.addListener(function (details) {
177+
browser.webRequest.onCompleted.addListener(async function (details) {
178178
if (details.method === "POST" || details.method === "DELETE") {
179+
await ensureOptionsLoaded();
179180
updateCounter();
180181
updateFeeds();
181182
appGlobal.getUserSubscriptionsPromise = null;
182183
}
183184
}, {urls: ["*://*.feedly.com/v3/subscriptions*", "*://*.feedly.com/v3/markers?*ct=feedly.desktop*"]});
184185

185186
/* Listener for adding or removing saved feeds */
186-
browser.webRequest.onCompleted.addListener(function (details) {
187+
browser.webRequest.onCompleted.addListener(async function (details) {
187188
if (details.method === "PUT" || details.method === "DELETE") {
189+
await ensureOptionsLoaded();
188190
updateSavedFeeds();
189191
}
190192
}, {urls: ["*://*.feedly.com/v3/tags*global.saved*"]});
@@ -201,7 +203,7 @@ browser.action.onClicked.addListener(async function () {
201203
});
202204

203205
/* Initialization all parameters and run feeds check */
204-
async function initialize() {
206+
async function initialize(immediate) {
205207
if (appGlobal.options.openSiteOnIconClick) {
206208
await browser.action.setPopup({popup: ""});
207209
} else {
@@ -211,18 +213,29 @@ async function initialize() {
211213

212214
const platformInfo = await browser.runtime.getPlatformInfo();
213215
appGlobal.environment.os = platformInfo.os;
214-
startSchedule(appGlobal.options.updateInterval);
216+
startSchedule(appGlobal.options.updateInterval, immediate);
215217
}
216218

217-
function startSchedule(updateInterval) {
218-
stopSchedule();
219-
updateCounter();
220-
updateFeeds();
221-
if (appGlobal.options.showCounter) {
222-
browser.alarms.create("updateCounter", { periodInMinutes: updateInterval });
223-
}
224-
if (appGlobal.options.showDesktopNotifications || appGlobal.options.playSound || !appGlobal.options.openSiteOnIconClick) {
225-
browser.alarms.create("updateFeeds", { periodInMinutes: updateInterval });
219+
async function ensureOptionsLoaded() {
220+
await readOptions();
221+
appGlobal.feedlyApiClient.accessToken = appGlobal.options.accessToken;
222+
appGlobal.isLoggedIn = Boolean(appGlobal.options.accessToken);
223+
}
224+
225+
function startSchedule(updateInterval, immediate) {
226+
const shouldRecreate = (immediate !== false);
227+
228+
// When shouldRecreate is false (worker wake), keep existing alarms as-is and avoid immediate fetch.
229+
if (shouldRecreate) {
230+
stopSchedule();
231+
if (appGlobal.options.showCounter) {
232+
browser.alarms.create("updateCounter", { periodInMinutes: updateInterval });
233+
}
234+
if (appGlobal.options.showDesktopNotifications || appGlobal.options.playSound || !appGlobal.options.openSiteOnIconClick) {
235+
browser.alarms.create("updateFeeds", { periodInMinutes: updateInterval });
236+
}
237+
updateCounter();
238+
updateFeeds();
226239
}
227240
}
228241

@@ -231,7 +244,10 @@ function stopSchedule() {
231244
browser.alarms.clear("updateFeeds");
232245
}
233246

234-
browser.alarms.onAlarm.addListener(function (alarm) {
247+
browser.alarms.onAlarm.addListener(async function (alarm) {
248+
// Ensure tokens/options are loaded when worker wakes for an alarm
249+
await ensureOptionsLoaded();
250+
235251
if (alarm && alarm.name === "updateCounter") {
236252
updateCounter();
237253
} else if (alarm && alarm.name === "updateFeeds") {
@@ -297,15 +313,16 @@ async function sendDesktopNotification(feeds) {
297313
let showBlogIcons = false;
298314
let showThumbnails = false;
299315

300-
try {
301-
const hasAllOrigins = await browser.permissions.contains({ origins: ["<all_urls>"] });
302-
if (appGlobal.options.showBlogIconInNotifications && hasAllOrigins) {
303-
showBlogIcons = true;
304-
}
305-
if (appGlobal.options.showThumbnailInNotifications && hasAllOrigins) {
306-
showThumbnails = true;
307-
}
308-
} catch (_) { /* ignore */ }
316+
const canCheckPermissions = browser.permissions && typeof browser.permissions.contains === "function";
317+
const hasAllOrigins = canCheckPermissions
318+
? await browser.permissions.contains({ origins: ["<all_urls>"] }).catch(function () { return false; })
319+
: false;
320+
if (appGlobal.options.showBlogIconInNotifications && hasAllOrigins) {
321+
showBlogIcons = true;
322+
}
323+
if (appGlobal.options.showThumbnailInNotifications && hasAllOrigins) {
324+
showThumbnails = true;
325+
}
309326

310327
createNotifications(feeds, showBlogIcons, showThumbnails, isSoundEnabled);
311328
}
@@ -388,13 +405,12 @@ function removeFeedFromCache(feedId) {
388405

389406
/* Plays alert sound */
390407
function playSound(){
391-
try {
392-
var audio = new Audio(appGlobal.options.sound);
393-
audio.volume = appGlobal.options.soundVolume;
394-
audio.play();
395-
} catch (e) {
396-
// In MV3 service worker there is no Audio context; ignore.
408+
if (typeof Audio !== "function") {
409+
return;
397410
}
411+
var audio = new Audio(appGlobal.options.sound);
412+
audio.volume = appGlobal.options.soundVolume;
413+
Promise.resolve(audio.play()).catch(function () {});
398414
}
399415

400416
/* Returns only new feeds and set date of last feed
@@ -439,6 +455,7 @@ async function updateSavedFeeds() {
439455
const response = await apiRequestWrapper("streams/" + encodeURIComponent(appGlobal.savedGroup) + "/contents");
440456
const feeds = await parseFeeds(response);
441457
appGlobal.cachedSavedFeeds = feeds;
458+
browser.storage.local.set({ cachedSavedFeeds: feeds }).catch(function () {});
442459
}
443460

444461
/* Sets badge counter if unread feeds more than zero */
@@ -534,7 +551,7 @@ async function makeMarkersRequest(parameters){
534551
* If silentUpdate is true, then notifications will not be shown
535552
* */
536553
async function updateFeeds(silentUpdate) {
537-
appGlobal.cachedFeeds = [];
554+
const previousCache = appGlobal.cachedFeeds.slice(0);
538555
appGlobal.options.filters = appGlobal.options.filters || [];
539556

540557
let streamIds = appGlobal.options.isFiltersEnabled && appGlobal.options.filters.length
@@ -558,12 +575,13 @@ async function updateFeeds(silentUpdate) {
558575
const responses = await Promise.all(promises);
559576
const parsedFeeds = await Promise.all(responses.map(response => parseFeeds(response)));
560577

578+
let newCache = [];
561579
for (let parsedFeed of parsedFeeds) {
562-
appGlobal.cachedFeeds = appGlobal.cachedFeeds.concat(parsedFeed);
580+
newCache = newCache.concat(parsedFeed);
563581
}
564582

565583
// Remove duplicates
566-
appGlobal.cachedFeeds = appGlobal.cachedFeeds.filter(function (value, index, feeds) {
584+
newCache = newCache.filter(function (value, index, feeds) {
567585
for (let i = ++index; i < feeds.length; i++) {
568586
if (feeds[i].id === value.id) {
569587
return false;
@@ -572,7 +590,7 @@ async function updateFeeds(silentUpdate) {
572590
return true;
573591
});
574592

575-
appGlobal.cachedFeeds = appGlobal.cachedFeeds.sort(function (a, b) {
593+
newCache = newCache.sort(function (a, b) {
576594
if (appGlobal.options.sortBy === "newest") {
577595
if (a.date > b.date) {
578596
return -1;
@@ -602,12 +620,16 @@ async function updateFeeds(silentUpdate) {
602620
}
603621
});
604622

605-
appGlobal.cachedFeeds = appGlobal.cachedFeeds.splice(0, appGlobal.options.maxNumberOfFeeds);
623+
newCache = newCache.splice(0, appGlobal.options.maxNumberOfFeeds);
624+
appGlobal.cachedFeeds = newCache;
625+
browser.storage.local.set({ cachedFeeds: newCache }).catch(function () {});
606626
if (!silentUpdate && (appGlobal.options.showDesktopNotifications)) {
607627
const newFeeds = await filterByNewFeeds(appGlobal.cachedFeeds);
608628
await sendDesktopNotification(newFeeds);
609629
}
610630
} catch (e) {
631+
// Preserve previous cache on failure
632+
appGlobal.cachedFeeds = previousCache;
611633
console.info("Unable to update feeds.", e);
612634
}
613635
}
@@ -904,7 +926,9 @@ async function getAccessToken() {
904926
let codeParse = /code=(.+?)(?:&|$)/i;
905927
let matches = codeParse.exec(information.url);
906928
if (matches) {
907-
try { browser.tabs.onUpdated.removeListener(processCode); } catch (_) {}
929+
if (browser.tabs && browser.tabs.onUpdated && typeof browser.tabs.onUpdated.removeListener === "function") {
930+
browser.tabs.onUpdated.removeListener(processCode);
931+
}
908932
resolve(matches[1]);
909933
}
910934
}
@@ -1000,6 +1024,14 @@ async function readOptions() {
10001024
}
10011025

10021026
appGlobal.options.currentUiLanguage = browser.i18n.getUILanguage();
1027+
1028+
// Preload cached feeds from local storage to serve immediately on popup open
1029+
const cache = await browser.storage.local.get(["cachedFeeds", "cachedSavedFeeds"]).catch(function () { return {}; });
1030+
appGlobal.cachedFeeds = Array.isArray(cache.cachedFeeds) ? cache.cachedFeeds : [];
1031+
appGlobal.cachedSavedFeeds = Array.isArray(cache.cachedSavedFeeds) ? cache.cachedSavedFeeds : [];
1032+
1033+
// If we have a token, treat as logged in until a request proves otherwise
1034+
appGlobal.isLoggedIn = Boolean(appGlobal.options.accessToken);
10031035
}
10041036

10051037
async function apiRequestWrapper(methodName, settings) {

0 commit comments

Comments
 (0)