跳到主要内容

github/tai

公开

Tai · Windows 软件与网站使用时长统计。上游 Planshit/Tai 的源码镜像,非灵栈原创;MIT 许可。来源:https://github.com/Planshit/Tai 。本次为手动同步,暂不提供安装包。

浏览文件

service-worker.js

318 行 · 7.1 KB历史下载
1// ws心跳间隔
2const TEN_SECONDS_MS = 10 * 1000;
3// 焦点检测间隔
4const FOCUSED_CHECK_MS = 1000;
5// 自动重新通知间隔
6const RENOTIFY_MS = 10 * 1000;
7// 重连失败次数进入睡眠状态
8const RECONNECTFAIL_SLEEP = 5;
9// Tai WS服务器URL
10const WSURL = 'ws://127.0.0.1:8908/TaiWebSentry';
11
12let webSocket = null;
13let isConnected = false;
14let isChromeFocused = true;
15let autoReConnectIntervalId = null;
16// 是否处于睡眠状态(来源Tai通知)
17let isSleep = false;
18// 焦点网页数据
19let activePage = {
20 url: '',
21 title: '',
22 icon: '',
23 startTime: '',
24 endTime: '',
25 duration: 0
26};
27// 重连失败次数
28let reconnectFail = 0;
29
30init();
31
32/**
33 * 初始化插件(入口
34 */
35function init() {
36 connect();
37 startWatchFocus();
38 startRenofity();
39}
40
41/**
42 * 连接Tai
43 */
44function connect() {
45 webSocket = new WebSocket(WSURL);
46 webSocket.onopen = (event) => {
47 isConnected = true;
48 isSleep = false;
49 reconnectFail = 0;
50 clearInterval(autoReConnectIntervalId);
51 chrome.action.setIcon({ path: 'icons/socket-active.png' });
52 keepAlive();
53 console.log("已连接Tai!");
54 };
55 webSocket.onmessage = (event) => {
56 console.log(event.data);
57 if (event.data === 'sleep') {
58 isSleep = true;
59 calDuration();
60 console.log("睡眠");
61 }
62 else if (event.data === 'wake') {
63 isSleep = false;
64 console.log("唤醒");
65 }
66
67 };
68 webSocket.onclose = (event) => {
69 isConnected = false;
70 chrome.action.setIcon({ path: 'icons/socket-inactive.png' });
71 console.warn('与Tai失去连接...');
72 webSocket = null;
73 startAutoReConnect();
74 };
75}
76
77function disconnect() {
78 if (isConnected && webSocket) {
79 webSocket.close();
80 isConnected = false;
81 }
82}
83
84function startAutoReConnect() {
85 clearInterval(autoReConnectIntervalId);
86 autoReConnectIntervalId = setInterval(
87 () => {
88 if (!isConnected) {
89 console.log("尝试重新连接Tai...")
90 connect();
91 reconnectFail++;
92 // 达到重连失败阈值时进入睡眠状态
93 if (reconnectFail >= RECONNECTFAIL_SLEEP && !isSleep) {
94 isSleep = true;
95 }
96 }
97 },
98 TEN_SECONDS_MS
99 );
100}
101
102/**
103 * 保持心跳,防止插件掉线
104 */
105function keepAlive() {
106 const keepAliveIntervalId = setInterval(
107 () => {
108 if (isConnected && webSocket) {
109 console.log('ping');
110 webSocket.send('ping');
111 } else {
112 clearInterval(keepAliveIntervalId);
113 }
114 },
115 // It's important to pick an interval that's shorter than 30s, to
116 // avoid that the service worker becomes inactive.
117 TEN_SECONDS_MS
118 );
119}
120
121
122// Chrome functions
123
124// 监听Chrome选项卡切换事件
125chrome.tabs.onActivated.addListener(async function
126 (e) {
127 const tab = await getTab(e.tabId);
128 onActivePage(tab);
129}
130);
131
132// 监听Chrome选项卡更新事件
133chrome.tabs.onUpdated.addListener(function
134 (tabId, changeInfo, tab) {
135 if (changeInfo.status == 'complete' && tab.active) {
136 onActivePage(tab);
137 }
138}
139);
140
141/**
142 * 通过TabId获取Tab
143 * @param {Number} tabId TabId
144 * @returns Tab
145 */
146function getTab(tabId) {
147 return new Promise((resolve, reject) => {
148 chrome.tabs.get(
149 tabId,
150 function (e) {
151 resolve(e);
152 }
153 )
154 });
155}
156
157/**
158 * 指示当前浏览器是否处于焦点中
159 * @returns 焦点中返回true
160 */
161function isFocused() {
162 return new Promise((resolve, reject) => {
163 chrome.windows.getCurrent(function (w) {
164 resolve(w.focused);
165 });
166 });
167
168}
169
170/**
171 * 获取当前焦点Tab
172 * @returns Tab Obj
173 */
174function getCurrentTab() {
175 return new Promise((resolve, reject) => {
176 chrome.tabs.query({ active: true }, function (tab) {
177 if (tab && tab.length > 0) {
178 resolve(tab[0]);
179 }
180 else {
181 reject(tab);
182 }
183 });
184 });
185}
186
187
188// Tai functions
189
190// 通知失败的集合
191let notifyFailList = [];
192
193/**
194 * 重新发送通知失败的数据(最早的一条)
195 */
196function renotify() {
197 if (isConnected && webSocket && notifyFailList.length > 0) {
198 const item = notifyFailList[0];
199 notifyFailList.splice(0, 1);
200 notifyTai(item);
201 }
202}
203
204/**
205 * 通知Tai更新数据
206 * @param {*} data 统计数据
207 */
208function notifyTai(data = {
209 Url,
210 Title,
211 Icon,
212 Duration,
213 ActiveTime
214}) {
215 console.log("notify", data);
216 if (isConnected && webSocket) {
217 webSocket.send(JSON.stringify(data));
218 }
219 else {
220 // let item = notifyFailList.find(s => s.Url === data.Url);
221 // if (item) {
222 // item.Duration += data.Duration;
223 // }
224 // else {
225 notifyFailList.push(data);
226 // }
227
228 console.log("failList", notifyFailList)
229 }
230}
231
232/**
233 * 响应网页焦点切换
234 * @param {ChromeTab} tab Tab
235 */
236function onActivePage(tab) {
237 // 睡眠状态不处理
238 if (isSleep) return;
239
240 if (activePage && activePage.url) {
241 if (activePage.url !== tab.url) {
242 // 统计时间
243 calDuration();
244 setActive(tab);
245 }
246 }
247 else {
248 setActive(tab);
249 }
250}
251
252/**
253 * 设置当前焦点网页数据
254 * @param {*} tab Tab
255 */
256function setActive(tab) {
257 const { url, title, favIconUrl: icon } = tab;
258 if (url) {
259 activePage = {
260 url, title, icon, startTime: new Date().getTime()
261 };
262 }
263 else {
264 activePage = null;
265 }
266}
267
268/**
269 * 计算并通知焦点网页浏览时长
270 */
271function calDuration() {
272 if (activePage && activePage.url) {
273 let duration = parseInt((new Date().getTime() - activePage.startTime) / 1000);
274 let activeTime = parseInt(activePage.startTime / 1000);
275 const { url: Url, title: Title, icon: Icon } = activePage;
276 activePage = null;
277 notifyTai({ Url, Title, Icon, Duration: duration, ActiveTime: activeTime });
278 }
279}
280
281/**
282 * 启动焦点监听(用于检测用户是否处于浏览器中
283 */
284function startWatchFocus() {
285 console.log("开始监听焦点");
286 setInterval(
287 async () => {
288 const focused = await isFocused();
289 if (focused) {
290 if (!isChromeFocused) {
291 isChromeFocused = true;
292 // 重置统计
293 const tab = await getCurrentTab();
294 onActivePage(tab);
295 console.warn("重置统计")
296 }
297 }
298 else {
299 if (isChromeFocused) {
300 isChromeFocused = false;
301 // 更新时间
302 calDuration();
303 console.warn("更新时间")
304 }
305 }
306 },
307 FOCUSED_CHECK_MS
308 );
309}
310
311/**
312 * 启动定时重新发送失败的数据通知
313 */
314function startRenofity() {
315 setInterval(() => {
316 renotify();
317 }, RENOTIFY_MS);
318}