blob: 4f40fee58a5a1e371bfb9d2e248ee1c2dc435492 [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';
avm99963d3f4ac02021-08-12 18:36:58 +02009const intervalMs = 3 * 60 * 1000; // 3 minutes
10const firstCallDelayMs = 3 * 1000; // 3 seconds
11
12export default class AutoRefresh {
13 constructor() {
14 this.isLookingForUpdates = false;
15 this.isUpdatePromptShown = false;
16 this.lastTimestamp = null;
17 this.filter = null;
18 this.path = null;
avm99963b6f68b62021-08-12 23:13:06 +020019 this.requestId = null;
20 this.requestOrderOptions = null;
avm99963d3f4ac02021-08-12 18:36:58 +020021 this.snackbar = null;
22 this.interval = null;
23 this.firstCallTimeout = null;
avm99963b6f68b62021-08-12 23:13:06 +020024
25 this.setUpHandlers();
avm99963d3f4ac02021-08-12 18:36:58 +020026 }
27
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020028 getStartupData() {
29 return JSON.parse(
30 document.querySelector('html').getAttribute('data-startup'));
avm99963d3f4ac02021-08-12 18:36:58 +020031 }
32
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020033 isOrderedByTimestampDescending() {
avm99963b6f68b62021-08-12 23:13:06 +020034 // This means we didn't intercept the request.
35 if (!this.requestOrderOptions)
36 return false;
37
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020038 // Returns orderOptions.by == TIMESTAMP && orderOptions.desc == true
39 return (
avm99963b6f68b62021-08-12 23:13:06 +020040 this.requestOrderOptions?.[1] == 1 &&
41 this.requestOrderOptions?.[2] == true);
avm99963d3f4ac02021-08-12 18:36:58 +020042 }
43
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020044 getCustomFilter(path) {
45 var searchRegex = /^\/s\/community\/search\/([^\/]*)/;
46 var matches = path.match(searchRegex);
47 if (matches !== null && matches.length > 1) {
48 var search = decodeURIComponent(matches[1]);
49 var params = new URLSearchParams(search);
50 return params.get('query') || '';
51 }
52
53 return '';
avm99963d3f4ac02021-08-12 18:36:58 +020054 }
55
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020056 filterHasOverride(filter, override) {
57 var escapedOverride = override.replace(/([^\w\d\s])/gi, '\\$1');
58 var regex = new RegExp('[^a-zA-Z0-9]?' + escapedOverride + ':');
59 return regex.test(filter);
avm99963d3f4ac02021-08-12 18:36:58 +020060 }
61
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020062 getFilter(path) {
63 var query = this.getCustomFilter(path);
64
65 // Note: This logic has been copied and adapted from the
66 // _buildQuery$1$threadId function in the Community Console
67 var conditions = '';
68 var startup = this.getStartupData();
69
70 // TODO(avm99963): if the selected forums are changed without reloading the
71 // page, this will get the old selected forums. Fix this.
72 var forums = startup?.[1]?.[1]?.[3]?.[8] ?? [];
73 if (!this.filterHasOverride(query, 'forum') && forums !== null &&
74 forums.length > 0)
75 conditions += ' forum:(' + forums.join(' | ') + ')';
76
77 var langs = startup?.[1]?.[1]?.[3]?.[5] ?? [];
78 if (!this.filterHasOverride(query, 'lang') && langs !== null &&
79 langs.length > 0)
80 conditions += ' lang:(' + langs.map(l => '"' + l + '"').join(' | ') + ')';
81
82 if (query.length !== 0 && conditions.length !== 0)
83 return '(' + query + ')' + conditions;
84 return query + conditions;
avm99963d3f4ac02021-08-12 18:36:58 +020085 }
86
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020087 getLastTimestamp() {
avm99963d3f4ac02021-08-12 18:36:58 +020088 return CCApi(
89 'ViewForum', {
90 1: '0', // TODO: Change, when only a forum is selected, it
91 // should be set here
92 // options
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020093 2: {
avm99963d3f4ac02021-08-12 18:36:58 +020094 // pagination
95 1: {
96 2: 2, // maxNum
97 },
98 // order
99 2: {
100 1: 1, // by
101 2: true, // desc
102 },
103 12: this.filter, // forumViewFilters
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200104 },
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200105 },
avm99963d3f4ac02021-08-12 18:36:58 +0200106 /* authenticated = */ true, authuser)
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200107 .then(body => {
108 var timestamp = body?.[1]?.[2]?.[0]?.[2]?.[17];
109 if (timestamp === undefined)
110 throw new Error(
111 'Unexpected body of response (' +
112 (body?.[1]?.[2]?.[0] === undefined ?
113 'no threads were returned' :
114 'the timestamp value is not present in the first thread') +
115 ').');
116
117 return timestamp;
118 });
119 // TODO(avm99963): Add retry mechanism (sometimes thread lists are empty,
120 // but when loading the next page the thread appears).
121 //
122 // NOTE(avm99963): It seems like loading the first 2 threads instead of only
123 // the first one fixes this (empty lists are now rarely returned).
avm99963d3f4ac02021-08-12 18:36:58 +0200124 }
125
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200126 unregister() {
127 console.debug('autorefresh_list: unregistering');
128
129 if (!this.isLookingForUpdates) return;
130
131 window.clearTimeout(this.firstCallTimeout);
132 window.clearInterval(this.interval);
133 this.isUpdatePromptShown = false;
134 this.isLookingForUpdates = false;
avm99963d3f4ac02021-08-12 18:36:58 +0200135 }
136
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200137 showUpdatePrompt() {
138 this.snackbar.classList.remove('TWPT-hidden');
139 document.title = '[!!!] ' + document.title.replace('[!!!] ', '');
140 this.isUpdatePromptShown = true;
avm99963d3f4ac02021-08-12 18:36:58 +0200141 }
142
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200143 hideUpdatePrompt() {
144 this.snackbar.classList.add('TWPT-hidden');
145 document.title = document.title.replace('[!!!] ', '');
146 this.isUpdatePromptShown = false;
avm99963d3f4ac02021-08-12 18:36:58 +0200147 }
148
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200149 injectUpdatePrompt() {
150 var pane = document.createElement('div');
151 pane.classList.add('TWPT-pane-for-snackbar');
152
153 var snackbar = document.createElement('material-snackbar-panel');
154 snackbar.classList.add('TWPT-snackbar');
155 snackbar.classList.add('TWPT-hidden');
156
157 var ac = document.createElement('div');
158 ac.classList.add('TWPT-animation-container');
159
160 var nb = document.createElement('div');
161 nb.classList.add('TWPT-notification-bar');
162
163 var ft = document.createElement('focus-trap');
164
165 var content = document.createElement('div');
166 content.classList.add('TWPT-focus-content-wrapper');
167
168 var badge = createExtBadge();
169
170 var message = document.createElement('div');
171 message.classList.add('TWPT-message');
172 message.textContent =
173 chrome.i18n.getMessage('inject_autorefresh_list_snackbar_message');
174
175 var action = document.createElement('div');
176 action.classList.add('TWPT-action');
177 action.textContent =
178 chrome.i18n.getMessage('inject_autorefresh_list_snackbar_action');
179
180 action.addEventListener('click', e => {
181 this.hideUpdatePrompt();
182 document.querySelector('.app-title-button').click();
183 });
184
185 content.append(badge, message, action);
186 ft.append(content);
187 nb.append(ft);
188 ac.append(nb);
189 snackbar.append(ac);
190 pane.append(snackbar);
191 document.getElementById('default-acx-overlay-container').append(pane);
192 this.snackbar = snackbar;
avm99963d3f4ac02021-08-12 18:36:58 +0200193 }
194
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200195 checkUpdate() {
196 if (location.pathname != this.path) {
197 this.unregister();
198 return;
199 }
200
201 if (this.isUpdatePromptShown) return;
202
203 console.debug('Checking for update at: ', new Date());
204
205 this.getLastTimestamp()
206 .then(timestamp => {
207 if (timestamp != this.lastTimestamp) this.showUpdatePrompt();
208 })
209 .catch(
210 err => console.error(
211 'Coudln\'t get last timestamp (while updating): ', err));
avm99963d3f4ac02021-08-12 18:36:58 +0200212 }
213
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200214 firstCall() {
215 console.debug(
216 'autorefresh_list: now performing first call to finish setup (filter: [' +
217 this.filter + '])');
218
219 if (location.pathname != this.path) {
220 this.unregister();
221 return;
222 }
223
224 this.getLastTimestamp()
225 .then(timestamp => {
226 this.lastTimestamp = timestamp;
227 var checkUpdateCallback = this.checkUpdate.bind(this);
avm99963d3f4ac02021-08-12 18:36:58 +0200228 this.interval = window.setInterval(checkUpdateCallback, intervalMs);
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200229 })
230 .catch(
231 err => console.error(
232 'Couldn\'t get last timestamp (while setting up): ', err));
avm99963d3f4ac02021-08-12 18:36:58 +0200233 }
234
avm99963b6f68b62021-08-12 23:13:06 +0200235 setUpHandlers() {
236 window.addEventListener(
237 threadListRequestEvent, e => this.handleListRequest(e));
238 }
239
240 handleListRequest(e) {
241 console.debug('autorefresh_list: handling ViewForum request');
242 if (this.requestId === null || e.detail.id > this.requestId) {
243 this.requestId = e.detail.id;
244 this.requestOrderOptions = e.detail.body?.['2']?.['2'];
245 }
246 }
247
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200248 setUp() {
avm99963b6f68b62021-08-12 23:13:06 +0200249 if (!this.isOrderedByTimestampDescending()) {
250 console.debug(
251 'autorefresh_list: refused to start up because the order is not by timestamp descending.');
252 return;
253 }
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200254
255 this.unregister();
256
257 console.debug('autorefresh_list: starting set up...');
258
259 if (this.snackbar === null) this.injectUpdatePrompt();
260 this.isLookingForUpdates = true;
261 this.path = location.pathname;
262 this.filter = this.getFilter(this.path);
263
264 var firstCall = this.firstCall.bind(this);
avm99963d3f4ac02021-08-12 18:36:58 +0200265 this.firstCallTimeout = window.setTimeout(firstCall, firstCallDelayMs);
266 }
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200267};