blob: ea7d32f887a504cd9059ed6e816a08482ec15621 [file] [log] [blame]
avm99963d3f4ac02021-08-12 18:36:58 +02001import {CCApi} from '../../common/api.js';
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +02002import {getAuthUser} from '../../common/communityConsoleUtils.js';
3
avm99963d3f4ac02021-08-12 18:36:58 +02004import {createExtBadge} from './utils/common.js';
5
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +02006var authuser = getAuthUser();
7
avm99963b6f68b62021-08-12 23:13:06 +02008const threadListRequestEvent = 'TWPT_ViewForumRequest';
avm99963dca87222021-08-16 19:10:25 +02009const threadListLoadEvent = 'TWPT_ViewForumResponse';
10const intervalMs = 3 * 60 * 1000; // 3 minutes
avm99963d3f4ac02021-08-12 18:36:58 +020011
12export default class AutoRefresh {
13 constructor() {
14 this.isLookingForUpdates = false;
15 this.isUpdatePromptShown = false;
16 this.lastTimestamp = null;
avm99963dca87222021-08-16 19:10:25 +020017 this.forumId = null;
avm99963d3f4ac02021-08-12 18:36:58 +020018 this.filter = null;
19 this.path = null;
avm99963b6f68b62021-08-12 23:13:06 +020020 this.requestId = null;
21 this.requestOrderOptions = null;
avm99963d3f4ac02021-08-12 18:36:58 +020022 this.snackbar = null;
avm9996358697fe2021-08-17 11:20:51 +020023 this.statusIndicator = null;
avm99963d3f4ac02021-08-12 18:36:58 +020024 this.interval = null;
avm99963b6f68b62021-08-12 23:13:06 +020025
26 this.setUpHandlers();
avm99963d3f4ac02021-08-12 18:36:58 +020027 }
28
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020029 isOrderedByTimestampDescending() {
avm99963b6f68b62021-08-12 23:13:06 +020030 // This means we didn't intercept the request.
Adrià Vilanova Martínez8ef13d42021-08-16 10:07:26 +020031 if (!this.requestOrderOptions) return false;
avm99963b6f68b62021-08-12 23:13:06 +020032
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020033 // Returns orderOptions.by == TIMESTAMP && orderOptions.desc == true
34 return (
avm99963b6f68b62021-08-12 23:13:06 +020035 this.requestOrderOptions?.[1] == 1 &&
36 this.requestOrderOptions?.[2] == true);
avm99963d3f4ac02021-08-12 18:36:58 +020037 }
38
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020039 getLastTimestamp() {
avm99963d3f4ac02021-08-12 18:36:58 +020040 return CCApi(
41 'ViewForum', {
avm99963dca87222021-08-16 19:10:25 +020042 1: this.forumId,
avm99963d3f4ac02021-08-12 18:36:58 +020043 // options
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020044 2: {
avm99963d3f4ac02021-08-12 18:36:58 +020045 // pagination
46 1: {
47 2: 2, // maxNum
48 },
49 // order
50 2: {
51 1: 1, // by
52 2: true, // desc
53 },
54 12: this.filter, // forumViewFilters
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020055 },
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020056 },
avm99963d3f4ac02021-08-12 18:36:58 +020057 /* authenticated = */ true, authuser)
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020058 .then(body => {
59 var timestamp = body?.[1]?.[2]?.[0]?.[2]?.[17];
60 if (timestamp === undefined)
61 throw new Error(
62 'Unexpected body of response (' +
63 (body?.[1]?.[2]?.[0] === undefined ?
64 'no threads were returned' :
65 'the timestamp value is not present in the first thread') +
66 ').');
67
68 return timestamp;
69 });
70 // TODO(avm99963): Add retry mechanism (sometimes thread lists are empty,
71 // but when loading the next page the thread appears).
72 //
73 // NOTE(avm99963): It seems like loading the first 2 threads instead of only
74 // the first one fixes this (empty lists are now rarely returned).
avm99963d3f4ac02021-08-12 18:36:58 +020075 }
76
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020077 unregister() {
78 console.debug('autorefresh_list: unregistering');
79
80 if (!this.isLookingForUpdates) return;
81
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020082 window.clearInterval(this.interval);
83 this.isUpdatePromptShown = false;
84 this.isLookingForUpdates = false;
avm99963d3f4ac02021-08-12 18:36:58 +020085 }
86
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020087 showUpdatePrompt() {
88 this.snackbar.classList.remove('TWPT-hidden');
89 document.title = '[!!!] ' + document.title.replace('[!!!] ', '');
90 this.isUpdatePromptShown = true;
avm99963d3f4ac02021-08-12 18:36:58 +020091 }
92
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020093 hideUpdatePrompt() {
avm999635203fa62021-08-16 18:36:02 +020094 if (this.snackbar) this.snackbar.classList.add('TWPT-hidden');
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020095 document.title = document.title.replace('[!!!] ', '');
96 this.isUpdatePromptShown = false;
avm99963d3f4ac02021-08-12 18:36:58 +020097 }
98
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020099 injectUpdatePrompt() {
100 var pane = document.createElement('div');
101 pane.classList.add('TWPT-pane-for-snackbar');
102
103 var snackbar = document.createElement('material-snackbar-panel');
104 snackbar.classList.add('TWPT-snackbar');
105 snackbar.classList.add('TWPT-hidden');
106
107 var ac = document.createElement('div');
108 ac.classList.add('TWPT-animation-container');
109
110 var nb = document.createElement('div');
111 nb.classList.add('TWPT-notification-bar');
112
113 var ft = document.createElement('focus-trap');
114
115 var content = document.createElement('div');
116 content.classList.add('TWPT-focus-content-wrapper');
117
118 var badge = createExtBadge();
119
120 var message = document.createElement('div');
121 message.classList.add('TWPT-message');
122 message.textContent =
123 chrome.i18n.getMessage('inject_autorefresh_list_snackbar_message');
124
125 var action = document.createElement('div');
126 action.classList.add('TWPT-action');
127 action.textContent =
128 chrome.i18n.getMessage('inject_autorefresh_list_snackbar_action');
129
130 action.addEventListener('click', e => {
131 this.hideUpdatePrompt();
132 document.querySelector('.app-title-button').click();
133 });
134
135 content.append(badge, message, action);
136 ft.append(content);
137 nb.append(ft);
138 ac.append(nb);
139 snackbar.append(ac);
140 pane.append(snackbar);
141 document.getElementById('default-acx-overlay-container').append(pane);
142 this.snackbar = snackbar;
avm99963d3f4ac02021-08-12 18:36:58 +0200143 }
144
avm9996358697fe2021-08-17 11:20:51 +0200145 // Create an indicator element.
146 createStatusIndicator(isSetUp) {
147 var container = document.createElement('div');
148 container.classList.add('TWPT-autorefresh-status-indicator-container');
149 var title = chrome.i18n.getMessage(
150 isSetUp ? 'inject_autorefresh_list_status_indicator_label_active' :
151 'inject_autorefresh_list_status_indicator_label_disabled');
152 container.setAttribute('title', title);
153
154 var indicator = document.createElement('div');
155 indicator.classList.add(
156 'TWPT-autorefresh-status-indicator',
157 isSetUp ? 'TWPT-autorefresh-status-indicator--active' :
158 'TWPT-autorefresh-status-indicator--disabled');
159 indicator.textContent =
160 isSetUp ? 'notifications_active' : 'notifications_off';
161
162 var badge = createExtBadge();
163
164 container.append(indicator, badge);
165 return container;
166 }
167
168 injectStatusIndicator(isSetUp) {
169 this.statusIndicator = this.createStatusIndicator(isSetUp);
170
171 var sortOptionsDiv = document.querySelector('ec-thread-list .sort-options');
172 if (sortOptionsDiv) {
173 sortOptionsDiv.prepend(this.statusIndicator);
174 return;
175 }
176
177 console.error('threadListAvatars: Couldn\'t inject status indicator.');
178 }
179
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200180 checkUpdate() {
181 if (location.pathname != this.path) {
182 this.unregister();
183 return;
184 }
185
avm99963dca87222021-08-16 19:10:25 +0200186 if (!this.lastTimestamp) {
187 console.error('autorefresh_list: this.lastTimestamp is not set.');
188 this.unregister();
189 return;
190 }
191
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200192 if (this.isUpdatePromptShown) return;
193
194 console.debug('Checking for update at: ', new Date());
195
196 this.getLastTimestamp()
197 .then(timestamp => {
198 if (timestamp != this.lastTimestamp) this.showUpdatePrompt();
199 })
200 .catch(
201 err => console.error(
202 'Coudln\'t get last timestamp (while updating): ', err));
avm99963d3f4ac02021-08-12 18:36:58 +0200203 }
204
avm99963b6f68b62021-08-12 23:13:06 +0200205 setUpHandlers() {
206 window.addEventListener(
207 threadListRequestEvent, e => this.handleListRequest(e));
avm99963dca87222021-08-16 19:10:25 +0200208 window.addEventListener(threadListLoadEvent, e => this.handleListLoad(e));
avm99963b6f68b62021-08-12 23:13:06 +0200209 }
210
avm99963dca87222021-08-16 19:10:25 +0200211 // This will set the forum ID and filter which is going to be used to check
212 // for new updates in the thread list.
avm99963b6f68b62021-08-12 23:13:06 +0200213 handleListRequest(e) {
Adrià Vilanova Martínez8ef13d42021-08-16 10:07:26 +0200214 // If the request was made before the last known one, return.
215 if (this.requestId !== null && e.detail.id < this.requestId) return;
216
217 // Ignore ViewForum requests made by the chat feature and the "Mark as
218 // duplicate" dialog.
219 //
220 // All those requests have |maxNum| set to 10 and 20 respectively, while the
221 // request that we want to handle is the initial request to load the thread
222 // list which currently requests 100 threads.
223 var maxNum = e.detail.body?.['2']?.['1']?.['2'];
224 if (maxNum == 10 || maxNum == 20) return;
225
226 // Ignore requests to load more threads in the current thread list. All
227 // those requests include a PaginationToken, and also have |maxNum| set
228 // to 50.
229 var token = e.detail.body?.['2']?.['1']?.['3'];
230 if (token) return;
231
Adrià Vilanova Martínez8ef13d42021-08-16 10:07:26 +0200232 this.requestId = e.detail.id;
233 this.requestOrderOptions = e.detail.body?.['2']?.['2'];
avm99963dca87222021-08-16 19:10:25 +0200234 this.forumId = e.detail.body?.['1'] ?? '0';
235 this.filter = e.detail.body?.['2']?.['12'] ?? '';
236
237 console.debug(
238 'autorefresh_list: handled valid ViewForum request (forumId: ' +
239 this.forumId + ', filter: [' + this.filter + '])');
avm99963b6f68b62021-08-12 23:13:06 +0200240 }
241
avm99963dca87222021-08-16 19:10:25 +0200242 // This will set the timestamp of the first thread in the list, so we can
243 // decide in the future whether there is an update or not.
244 handleListLoad(e) {
245 // We ignore past requests and only consider the most recent one.
246 if (this.requestId !== e.detail.id) return;
247
248 console.debug(
249 'autorefresh_list: handling corresponding ViewForum response');
250
251 this.lastTimestamp = e.detail.body?.['1']?.['2']?.[0]?.['2']?.['17'];
252 if (this.lastTimestamp === undefined)
253 console.error(
254 'autorefresh_list: Unexpected body of response (' +
255 (body?.['1']?.['2']?.[0] === undefined ?
256 'no threads were returned' :
257 'the timestamp value is not present in the first thread') +
258 ').');
259 }
260
261 // This is called when a thread list node is detected in the page. This
262 // initializes the interval to check for updates, and several other things.
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200263 setUp() {
avm99963b6f68b62021-08-12 23:13:06 +0200264 if (!this.isOrderedByTimestampDescending()) {
avm9996358697fe2021-08-17 11:20:51 +0200265 this.injectStatusIndicator(false);
avm99963b6f68b62021-08-12 23:13:06 +0200266 console.debug(
267 'autorefresh_list: refused to start up because the order is not by timestamp descending.');
268 return;
269 }
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200270
271 this.unregister();
272
273 console.debug('autorefresh_list: starting set up...');
274
Adrià Vilanova Martínezde7c0aa2021-08-16 14:17:10 +0000275 if (this.snackbar === null) this.injectUpdatePrompt();
avm9996358697fe2021-08-17 11:20:51 +0200276 this.injectStatusIndicator(true);
277
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200278 this.isLookingForUpdates = true;
279 this.path = location.pathname;
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200280
avm99963dca87222021-08-16 19:10:25 +0200281 var checkUpdateCallback = this.checkUpdate.bind(this);
282 this.interval = window.setInterval(checkUpdateCallback, intervalMs);
avm99963d3f4ac02021-08-12 18:36:58 +0200283 }
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200284};