blob: 87ca203bd7b281a852204c29d15207cc98056e78 [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;
23 this.interval = 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 isOrderedByTimestampDescending() {
avm99963b6f68b62021-08-12 23:13:06 +020029 // This means we didn't intercept the request.
Adrià Vilanova Martínez8ef13d42021-08-16 10:07:26 +020030 if (!this.requestOrderOptions) return false;
avm99963b6f68b62021-08-12 23:13:06 +020031
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020032 // Returns orderOptions.by == TIMESTAMP && orderOptions.desc == true
33 return (
avm99963b6f68b62021-08-12 23:13:06 +020034 this.requestOrderOptions?.[1] == 1 &&
35 this.requestOrderOptions?.[2] == true);
avm99963d3f4ac02021-08-12 18:36:58 +020036 }
37
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020038 getLastTimestamp() {
avm99963d3f4ac02021-08-12 18:36:58 +020039 return CCApi(
40 'ViewForum', {
avm99963dca87222021-08-16 19:10:25 +020041 1: this.forumId,
avm99963d3f4ac02021-08-12 18:36:58 +020042 // options
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020043 2: {
avm99963d3f4ac02021-08-12 18:36:58 +020044 // pagination
45 1: {
46 2: 2, // maxNum
47 },
48 // order
49 2: {
50 1: 1, // by
51 2: true, // desc
52 },
53 12: this.filter, // forumViewFilters
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020054 },
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020055 },
avm99963d3f4ac02021-08-12 18:36:58 +020056 /* authenticated = */ true, authuser)
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020057 .then(body => {
58 var timestamp = body?.[1]?.[2]?.[0]?.[2]?.[17];
59 if (timestamp === undefined)
60 throw new Error(
61 'Unexpected body of response (' +
62 (body?.[1]?.[2]?.[0] === undefined ?
63 'no threads were returned' :
64 'the timestamp value is not present in the first thread') +
65 ').');
66
67 return timestamp;
68 });
69 // TODO(avm99963): Add retry mechanism (sometimes thread lists are empty,
70 // but when loading the next page the thread appears).
71 //
72 // NOTE(avm99963): It seems like loading the first 2 threads instead of only
73 // the first one fixes this (empty lists are now rarely returned).
avm99963d3f4ac02021-08-12 18:36:58 +020074 }
75
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020076 unregister() {
77 console.debug('autorefresh_list: unregistering');
78
79 if (!this.isLookingForUpdates) return;
80
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020081 window.clearInterval(this.interval);
82 this.isUpdatePromptShown = false;
83 this.isLookingForUpdates = false;
avm99963d3f4ac02021-08-12 18:36:58 +020084 }
85
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020086 showUpdatePrompt() {
87 this.snackbar.classList.remove('TWPT-hidden');
88 document.title = '[!!!] ' + document.title.replace('[!!!] ', '');
89 this.isUpdatePromptShown = true;
avm99963d3f4ac02021-08-12 18:36:58 +020090 }
91
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020092 hideUpdatePrompt() {
avm999635203fa62021-08-16 18:36:02 +020093 if (this.snackbar) this.snackbar.classList.add('TWPT-hidden');
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020094 document.title = document.title.replace('[!!!] ', '');
95 this.isUpdatePromptShown = false;
avm99963d3f4ac02021-08-12 18:36:58 +020096 }
97
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020098 injectUpdatePrompt() {
99 var pane = document.createElement('div');
100 pane.classList.add('TWPT-pane-for-snackbar');
101
102 var snackbar = document.createElement('material-snackbar-panel');
103 snackbar.classList.add('TWPT-snackbar');
104 snackbar.classList.add('TWPT-hidden');
105
106 var ac = document.createElement('div');
107 ac.classList.add('TWPT-animation-container');
108
109 var nb = document.createElement('div');
110 nb.classList.add('TWPT-notification-bar');
111
112 var ft = document.createElement('focus-trap');
113
114 var content = document.createElement('div');
115 content.classList.add('TWPT-focus-content-wrapper');
116
117 var badge = createExtBadge();
118
119 var message = document.createElement('div');
120 message.classList.add('TWPT-message');
121 message.textContent =
122 chrome.i18n.getMessage('inject_autorefresh_list_snackbar_message');
123
124 var action = document.createElement('div');
125 action.classList.add('TWPT-action');
126 action.textContent =
127 chrome.i18n.getMessage('inject_autorefresh_list_snackbar_action');
128
129 action.addEventListener('click', e => {
130 this.hideUpdatePrompt();
131 document.querySelector('.app-title-button').click();
132 });
133
134 content.append(badge, message, action);
135 ft.append(content);
136 nb.append(ft);
137 ac.append(nb);
138 snackbar.append(ac);
139 pane.append(snackbar);
140 document.getElementById('default-acx-overlay-container').append(pane);
141 this.snackbar = snackbar;
avm99963d3f4ac02021-08-12 18:36:58 +0200142 }
143
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200144 checkUpdate() {
145 if (location.pathname != this.path) {
146 this.unregister();
147 return;
148 }
149
avm99963dca87222021-08-16 19:10:25 +0200150 if (!this.lastTimestamp) {
151 console.error('autorefresh_list: this.lastTimestamp is not set.');
152 this.unregister();
153 return;
154 }
155
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200156 if (this.isUpdatePromptShown) return;
157
158 console.debug('Checking for update at: ', new Date());
159
160 this.getLastTimestamp()
161 .then(timestamp => {
162 if (timestamp != this.lastTimestamp) this.showUpdatePrompt();
163 })
164 .catch(
165 err => console.error(
166 'Coudln\'t get last timestamp (while updating): ', err));
avm99963d3f4ac02021-08-12 18:36:58 +0200167 }
168
avm99963b6f68b62021-08-12 23:13:06 +0200169 setUpHandlers() {
170 window.addEventListener(
171 threadListRequestEvent, e => this.handleListRequest(e));
avm99963dca87222021-08-16 19:10:25 +0200172 window.addEventListener(threadListLoadEvent, e => this.handleListLoad(e));
avm99963b6f68b62021-08-12 23:13:06 +0200173 }
174
avm99963dca87222021-08-16 19:10:25 +0200175 // This will set the forum ID and filter which is going to be used to check
176 // for new updates in the thread list.
avm99963b6f68b62021-08-12 23:13:06 +0200177 handleListRequest(e) {
Adrià Vilanova Martínez8ef13d42021-08-16 10:07:26 +0200178 // If the request was made before the last known one, return.
179 if (this.requestId !== null && e.detail.id < this.requestId) return;
180
181 // Ignore ViewForum requests made by the chat feature and the "Mark as
182 // duplicate" dialog.
183 //
184 // All those requests have |maxNum| set to 10 and 20 respectively, while the
185 // request that we want to handle is the initial request to load the thread
186 // list which currently requests 100 threads.
187 var maxNum = e.detail.body?.['2']?.['1']?.['2'];
188 if (maxNum == 10 || maxNum == 20) return;
189
190 // Ignore requests to load more threads in the current thread list. All
191 // those requests include a PaginationToken, and also have |maxNum| set
192 // to 50.
193 var token = e.detail.body?.['2']?.['1']?.['3'];
194 if (token) return;
195
Adrià Vilanova Martínez8ef13d42021-08-16 10:07:26 +0200196 this.requestId = e.detail.id;
197 this.requestOrderOptions = e.detail.body?.['2']?.['2'];
avm99963dca87222021-08-16 19:10:25 +0200198 this.forumId = e.detail.body?.['1'] ?? '0';
199 this.filter = e.detail.body?.['2']?.['12'] ?? '';
200
201 console.debug(
202 'autorefresh_list: handled valid ViewForum request (forumId: ' +
203 this.forumId + ', filter: [' + this.filter + '])');
avm99963b6f68b62021-08-12 23:13:06 +0200204 }
205
avm99963dca87222021-08-16 19:10:25 +0200206 // This will set the timestamp of the first thread in the list, so we can
207 // decide in the future whether there is an update or not.
208 handleListLoad(e) {
209 // We ignore past requests and only consider the most recent one.
210 if (this.requestId !== e.detail.id) return;
211
212 console.debug(
213 'autorefresh_list: handling corresponding ViewForum response');
214
215 this.lastTimestamp = e.detail.body?.['1']?.['2']?.[0]?.['2']?.['17'];
216 if (this.lastTimestamp === undefined)
217 console.error(
218 'autorefresh_list: Unexpected body of response (' +
219 (body?.['1']?.['2']?.[0] === undefined ?
220 'no threads were returned' :
221 'the timestamp value is not present in the first thread') +
222 ').');
223 }
224
225 // This is called when a thread list node is detected in the page. This
226 // initializes the interval to check for updates, and several other things.
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200227 setUp() {
avm99963b6f68b62021-08-12 23:13:06 +0200228 if (!this.isOrderedByTimestampDescending()) {
229 console.debug(
230 'autorefresh_list: refused to start up because the order is not by timestamp descending.');
231 return;
232 }
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200233
234 this.unregister();
235
236 console.debug('autorefresh_list: starting set up...');
237
Adrià Vilanova Martínezde7c0aa2021-08-16 14:17:10 +0000238 if (this.snackbar === null) this.injectUpdatePrompt();
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200239 this.isLookingForUpdates = true;
240 this.path = location.pathname;
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200241
avm99963dca87222021-08-16 19:10:25 +0200242 var checkUpdateCallback = this.checkUpdate.bind(this);
243 this.interval = window.setInterval(checkUpdateCallback, intervalMs);
avm99963d3f4ac02021-08-12 18:36:58 +0200244 }
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200245};