threadListAvatars: dynamic unauthorized forums
This is an implementation of point 3 in the "Idea" section of the
following doc: go/eu7T9m (public link available in the linked bug).
Paraphrasing the previous document:
This change adds code to maintain a list of forums from which the
extension couldn't load any thread, which probably correspond to private
forums. When a forum is in this list, avatars are no longer retrieved
from threads in that forum until the entry expires.
Bug: 2
Change-Id: I05e7b02818181f410855948e1af6ec75fc7c792b
diff --git a/src/common/api.js b/src/common/api.js
index 065d84c..b2075c3 100644
--- a/src/common/api.js
+++ b/src/common/api.js
@@ -2,7 +2,9 @@
// Function to wrap calls to the Community Console API with intelligent error
// handling.
-export function CCApi(method, data, authenticated, authuser = 0) {
+export function CCApi(
+ method, data, authenticated, authuser = 0,
+ returnUnauthorizedStatus = false) {
var authuserPart =
authuser == '0' ? '' : '?authuser=' + encodeURIComponent(authuser);
@@ -29,12 +31,24 @@
})
.then(res => {
if (res.status == 400) {
+ // If the canonicalCode is PERMISSION_DENIED:
+ if (returnUnauthorizedStatus && res.body?.[2] == 7)
+ return {
+ unauthorized: true,
+ };
+
throw new Error(
res.body[4] ||
('Response status 400 for method ' + method + '. ' +
'Error code: ' + (res.body[2] ?? 'unknown')));
}
+ if (returnUnauthorizedStatus)
+ return {
+ unauthorized: false,
+ body: res.body,
+ };
+
return res.body;
});
}
diff --git a/src/contentScripts/communityConsole/avatars.js b/src/contentScripts/communityConsole/avatars.js
index 3b27333..5e5eeee 100644
--- a/src/contentScripts/communityConsole/avatars.js
+++ b/src/contentScripts/communityConsole/avatars.js
@@ -58,7 +58,9 @@
// the thread belongs to a known private forum.
shouldRetrieveAvatars(thread) {
return this.getPrivateForums().then(privateForums => {
- return !privateForums.includes(thread.forum);
+ if (privateForums.includes(thread.forum)) return false;
+
+ return this.db.isForumUnauthorized(thread.forum).then(res => !res);
});
}
@@ -90,8 +92,16 @@
// Due to the fact that we have to call this endpoint
// anonymously, this means we can't retrieve information about
// threads in private forums.
- /* authentication = */ false)
- .then(data => {
+ /* authentication = */ false, /* authuser = */ 0,
+ /* returnUnauthorizedStatus = */ true)
+ .then(response => {
+ if (response.unauthorized)
+ return this.db.putUnauthorizedForum(thread.forum).then(() => {
+ throw new Error('Permission denied to load thread.');
+ });
+
+ var data = response.body;
+
var numMessages = data?.['1']?.['8'];
if (numMessages === undefined)
throw new Error(
diff --git a/src/contentScripts/communityConsole/utils/AvatarsDB.js b/src/contentScripts/communityConsole/utils/AvatarsDB.js
index b59460d..a8c155c 100644
--- a/src/contentScripts/communityConsole/utils/AvatarsDB.js
+++ b/src/contentScripts/communityConsole/utils/AvatarsDB.js
@@ -3,11 +3,14 @@
const dbName = 'TWPTAvatarsDB';
const threadListLoadEvent = 'TWPT_ViewForumResponse';
// Time after the last use when a cache entry should be deleted (in s):
-const expirationTime = 4 * 24 * 60 * 60; // 4 days
+const cacheExpirationTime = 4 * 24 * 60 * 60; // 4 days
// Probability of running the piece of code to remove unused cache entries after
// loading the thread list.
const probRemoveUnusedCacheEntries = 0.10; // 10%
+// Time after which an unauthorized forum entry expires (in s).
+const unauthorizedForumExpirationTime = 1 * 24 * 60 * 60; // 1 day
+
export default class AvatarsDB {
constructor() {
this.dbPromise = undefined;
@@ -38,6 +41,7 @@
});
}
+ // avatarsCache methods:
getCacheEntry(threadId) {
return this.dbPromise.then(db => db.get('avatarsCache', threadId));
}
@@ -55,7 +59,7 @@
return this.dbPromise
.then(db => {
var upperBoundTimestamp =
- Math.floor(Date.now() / 1000) - expirationTime;
+ Math.floor(Date.now() / 1000) - cacheExpirationTime;
var range = IDBKeyRange.upperBound(upperBoundTimestamp);
var tx = db.transaction('avatarsCache', 'readwrite');
@@ -127,4 +131,30 @@
});
});
}
+
+ // unauthorizedForums methods:
+ isForumUnauthorized(forumId) {
+ return this.dbPromise.then(db => db.get('unauthorizedForums', forumId))
+ .then(entry => {
+ if (entry === undefined) return false;
+
+ var now = Math.floor(Date.now() / 1000);
+ if (entry.expirationTimestamp > now) return true;
+
+ this.invalidateUnauthorizedForum(forumId);
+ return false;
+ });
+ }
+
+ putUnauthorizedForum(forumId) {
+ return this.dbPromise.then(db => db.put('unauthorizedForums', {
+ forumId,
+ expirationTimestamp:
+ Math.floor(Date.now() / 1000) + unauthorizedForumExpirationTime,
+ }));
+ }
+
+ invalidateUnauthorizedForum(forumId) {
+ return this.dbPromise.then(db => db.delete('unauthorizedForums', forumId));
+ }
};