Repository navigation
Expand file tree
/
Copy pathbackground.js
More file actions
523 lines (452 loc) · 16.1 KB
/
Copy pathbackground.js
File metadata and controls
523 lines (452 loc) · 16.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
// 默认配置
const DEFAULT_CONFIG = {
coins: [
{
symbol: 'bitcoin',
targetPrice: 100000,
name: 'Bitcoin'
}
],
checkInterval: 30, // 分钟
enabled: true
};
// 初始化扩展
chrome.runtime.onInstalled.addListener(async () => {
console.log('扩展已安装/更新');
// 设置默认配置
const config = await chrome.storage.local.get('config');
if (!config.config) {
await chrome.storage.local.set({ config: DEFAULT_CONFIG });
}
// 创建定时任务
await createAlarm();
});
// Service Worker 启动时
chrome.runtime.onStartup.addListener(async () => {
console.log('Service Worker 启动');
await createAlarm();
});
// 监听popup打开事件
chrome.runtime.onConnect.addListener((port) => {
console.log('Popup连接建立');
// 当popup连接时,立即检查价格
checkPrices().catch(error => {
console.error('Popup连接时价格检查失败:', error);
});
port.onDisconnect.addListener(() => {
console.log('Popup连接断开');
});
});
// 保持Service Worker活跃的保活机制
let keepAliveInterval = null;
let priceCheckInterval = null;
function startKeepAlive() {
if (keepAliveInterval) {
clearInterval(keepAliveInterval);
}
// 创建保活定时任务
chrome.alarms.create('keepAlive', {
delayInMinutes: 0.5, // 30秒后开始
periodInMinutes: 0.5 // 每30秒执行一次
});
console.log('保活机制已启动');
}
function stopKeepAlive() {
if (keepAliveInterval) {
clearInterval(keepAliveInterval);
keepAliveInterval = null;
}
// 清除保活定时任务
chrome.alarms.clear('keepAlive');
console.log('保活机制已停止');
}
// 启动备用价格检查机制(使用setInterval作为备用)
function startBackupPriceCheck() {
if (priceCheckInterval) {
clearInterval(priceCheckInterval);
}
// 获取配置中的检查间隔
chrome.storage.local.get('config', (result) => {
const config = result.config;
if (config && config.coins && config.coins.length > 0) {
const enabledCoins = config.coins.filter(coin => coin.enabled !== false);
if (enabledCoins.length > 0) {
const checkIntervalMinutes = Math.max(1, config.checkInterval || 60);
const checkIntervalMs = checkIntervalMinutes * 60 * 1000;
console.log(`启动备用价格检查机制,间隔: ${checkIntervalMinutes} 分钟,监控${enabledCoins.length}个币种`);
// 使用配置的间隔时间
priceCheckInterval = setInterval(async () => {
console.log('备用价格检查机制触发');
try {
await checkPrices();
} catch (error) {
console.error('备用价格检查失败:', error);
}
}, checkIntervalMs);
}
}
});
}
function stopBackupPriceCheck() {
if (priceCheckInterval) {
clearInterval(priceCheckInterval);
priceCheckInterval = null;
}
console.log('备用价格检查机制已停止');
}
// 创建定时任务
async function createAlarm() {
try {
const { config } = await chrome.storage.local.get('config');
console.log('当前配置:', config);
if (config && config.coins && config.coins.length > 0) {
// 检查是否有启用的币种
const enabledCoins = config.coins.filter(coin => coin.enabled !== false);
if (enabledCoins.length > 0) {
// 清除现有的定时任务
await chrome.alarms.clear('checkPrices');
// 确保检查间隔至少为1分钟(Chrome扩展的限制)
const checkInterval = Math.max(1, config.checkInterval || 60);
// 创建新的定时任务
await chrome.alarms.create('checkPrices', {
delayInMinutes: 1, // 1分钟后开始第一次检查
periodInMinutes: checkInterval
});
console.log(`定时任务已创建,检查间隔: ${checkInterval} 分钟,监控${enabledCoins.length}个币种`);
// 启动保活机制
startKeepAlive();
// 启动备用价格检查机制
startBackupPriceCheck();
// 验证定时任务是否创建成功
chrome.alarms.get('checkPrices', (alarm) => {
if (alarm) {
console.log('定时任务创建成功:', alarm);
} else {
console.error('定时任务创建失败');
}
});
} else {
// 如果没有启用的币种,停止所有机制
stopKeepAlive();
stopBackupPriceCheck();
await chrome.alarms.clear('checkPrices');
console.log('没有启用的币种,所有定时任务已清除');
}
} else {
// 如果没有配置,停止所有机制
stopKeepAlive();
stopBackupPriceCheck();
await chrome.alarms.clear('checkPrices');
console.log('没有配置币种,所有定时任务已清除');
}
} catch (error) {
console.error('创建定时任务失败:', error);
}
}
// 处理定时任务
chrome.alarms.onAlarm.addListener(async (alarm) => {
console.log('=== 定时任务触发 ===');
console.log('任务名称:', alarm.name);
console.log('触发时间:', new Date().toLocaleString('zh-CN'));
console.log('任务详情:', alarm);
if (alarm.name === 'checkPrices') {
console.log('开始执行价格检查...');
try {
await checkPrices();
console.log('价格检查完成');
} catch (error) {
console.error('价格检查执行失败:', error);
}
} else if (alarm.name === 'keepAlive') {
// 保活任务,只记录日志保持Service Worker活跃
console.log('保活任务执行');
// 检查监控是否仍然启用
try {
const { config } = await chrome.storage.local.get('config');
if (!config || !config.coins || config.coins.length === 0) {
console.log('没有配置币种,停止保活机制');
stopKeepAlive();
return;
}
const enabledCoins = config.coins.filter(coin => coin.enabled !== false);
if (enabledCoins.length === 0) {
console.log('没有启用的币种,停止保活机制');
stopKeepAlive();
}
} catch (error) {
console.error('检查监控状态失败:', error);
}
}
});
// 价格检查函数
async function checkPrices() {
try {
const { config } = await chrome.storage.local.get('config');
if (!config.enabled) return;
const coinIds = config.coins.map(coin => coin.symbol).join(',');
// 尝试多个API端点
const apiEndpoints = [
`https://api.coingecko.com/api/v3/simple/price?ids=${coinIds}&vs_currencies=usd`,
`https://api.coingecko.com/api/v3/simple/price?ids=${coinIds}&vs_currencies=usd&x_cg_demo_api_key=CG-Demo`
];
let apiUrl = apiEndpoints[0]; // 默认使用第一个端点
console.log('正在检查价格,API URL:', apiUrl);
// 重试机制
let response;
let lastError;
for (let attempt = 1; attempt <= 3; attempt++) {
try {
console.log(`尝试第 ${attempt} 次请求...`);
// 如果是第二次尝试,使用备用API端点
if (attempt === 2) {
apiUrl = apiEndpoints[1];
console.log('切换到备用API端点:', apiUrl);
}
// 创建带超时的fetch请求
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
response = await fetch(apiUrl, {
method: 'GET',
headers: {
'Accept': 'application/json',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
},
signal: controller.signal
});
clearTimeout(timeoutId);
if (response.ok) {
break; // 成功获取响应,跳出重试循环
} else {
throw new Error(`HTTP error! status: ${response.status} - ${response.statusText}`);
}
} catch (error) {
lastError = error;
console.warn(`第 ${attempt} 次请求失败:`, error.message);
if (attempt < 3) {
// 等待一段时间后重试
await new Promise(resolve => setTimeout(resolve, 2000 * attempt));
}
}
}
if (!response || !response.ok) {
throw lastError || new Error('所有重试都失败了');
}
const data = await response.json();
console.log('API响应数据:', data);
// 检查每个币种的价格
config.coins.forEach(coin => {
// 检查币种是否启用
if (coin.enabled === false) {
console.log(`${coin.name} (${coin.symbol}) 已禁用,跳过检查`);
return;
}
const priceData = data[coin.symbol];
if (!priceData || !priceData.usd) {
console.warn(`未找到 ${coin.name} (${coin.symbol}) 的价格数据`);
return;
}
const price = priceData.usd;
console.log(`${coin.name} 当前价格: $${price}, 目标价格: $${coin.targetPrice}`);
if (price < coin.targetPrice) {
console.log(`触发通知: ${coin.name} 价格 $${price} 低于目标价格 $${coin.targetPrice}`);
// 创建通知选项
const notificationOptions = {
type: 'basic',
iconUrl: 'icons/icon48.png',
title: '💰 加密货币价格提醒',
message: `${coin.name} 当前价格 $${price.toFixed(2)} 已低于你设定的 $${coin.targetPrice}`,
requireInteraction: false,
silent: false
};
console.log('创建通知,选项:', notificationOptions);
chrome.notifications.create(notificationOptions, (notificationId) => {
if (chrome.runtime.lastError) {
console.error('创建通知失败:', chrome.runtime.lastError);
console.error('错误详情:', {
error: chrome.runtime.lastError,
price: price,
targetPrice: coin.targetPrice,
coin: coin
});
} else {
console.log('通知创建成功,ID:', notificationId);
console.log('通知详情:', {
notificationId: notificationId,
price: price,
targetPrice: coin.targetPrice,
coin: coin
});
}
});
} else {
console.log(`${coin.name} 价格 $${price} 高于或等于目标价格 $${coin.targetPrice},不触发通知`);
}
});
// 更新最后检查时间和清除错误信息
await chrome.storage.local.set({
lastCheck: new Date().toISOString(),
currentPrices: data,
lastError: null // 清除之前的错误信息
});
} catch (error) {
console.error('价格检查失败:', error);
// 更详细的错误信息
if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) {
console.error('网络连接失败,可能的原因:');
console.error('1. 网络连接问题');
console.error('2. CoinGecko API 服务不可用');
console.error('3. 防火墙或代理设置阻止了请求');
console.error('4. 扩展权限问题');
console.error('5. CORS 策略限制');
console.error('6. 浏览器安全设置');
} else if (error.message.includes('HTTP error')) {
console.error('API 请求失败,状态码:', error.message);
} else if (error.name === 'AbortError') {
console.error('请求超时,请检查网络连接');
}
console.error('完整错误信息:', error);
// 存储错误信息供popup显示
await chrome.storage.local.set({
lastError: {
message: error.message,
timestamp: new Date().toISOString()
}
});
}
}
// 一键检测功能
async function runSystemCheck() {
const results = {
notificationPermission: false,
apiConnection: false,
serviceWorker: false,
alarms: false,
config: false,
details: {}
};
try {
// 1. 检测通知权限
console.log('检测通知权限...');
try {
await new Promise((resolve, reject) => {
chrome.notifications.create({
type: 'basic',
iconUrl: 'icons/icon48.png',
title: '🔍 系统检测',
message: '正在检测系统功能...'
}, (notificationId) => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
// 立即删除测试通知
chrome.notifications.clear(notificationId);
resolve();
}
});
});
results.notificationPermission = true;
results.details.notification = '✅ 通知权限正常';
} catch (error) {
results.details.notification = `❌ 通知权限异常: ${error.message}`;
}
// 2. 检测API连接
console.log('检测API连接...');
try {
const response = await fetch('https://api.coingecko.com/api/v3/ping');
if (response.ok) {
results.apiConnection = true;
results.details.api = '✅ API连接正常';
} else {
results.details.api = `❌ API连接失败: HTTP ${response.status}`;
}
} catch (error) {
results.details.api = `❌ API连接异常: ${error.message}`;
}
// 3. 检测Service Worker状态
console.log('检测Service Worker状态...');
try {
const alarms = await new Promise((resolve) => {
chrome.alarms.getAll(resolve);
});
results.serviceWorker = true;
results.details.serviceWorker = `✅ Service Worker正常 (${alarms.length}个定时任务)`;
} catch (error) {
results.details.serviceWorker = `❌ Service Worker异常: ${error.message}`;
}
// 4. 检测定时任务
console.log('检测定时任务...');
try {
const checkPricesAlarm = await new Promise((resolve) => {
chrome.alarms.get('checkPrices', resolve);
});
if (checkPricesAlarm) {
results.alarms = true;
results.details.alarms = '✅ 定时任务正常';
} else {
results.details.alarms = '❌ 定时任务未创建';
}
} catch (error) {
results.details.alarms = `❌ 定时任务异常: ${error.message}`;
}
// 5. 检测配置
console.log('检测配置...');
try {
const { config } = await chrome.storage.local.get('config');
if (config && config.coins && config.coins.length > 0) {
const enabledCoins = config.coins.filter(coin => coin.enabled !== false);
results.config = true;
results.details.config = `✅ 配置正常 (${enabledCoins.length}/${config.coins.length}个币种启用)`;
} else {
results.details.config = '❌ 配置异常或未配置币种';
}
} catch (error) {
results.details.config = `❌ 配置异常: ${error.message}`;
}
} catch (error) {
console.error('系统检测失败:', error);
results.details.error = error.message;
}
return results;
}
// 监听来自popup的消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
// 处理保活消息
if (request.action === 'keepAlive') {
console.log('收到保活消息');
sendResponse({ success: true });
return true;
}
if (request.action === 'getConfig') {
chrome.storage.local.get(['config', 'lastCheck', 'currentPrices', 'lastError'], (result) => {
sendResponse(result);
});
return true;
}
if (request.action === 'saveConfig') {
chrome.storage.local.set({ config: request.config }, async () => {
// 重新创建定时任务
await chrome.alarms.clear('checkPrices');
await createAlarm();
sendResponse({ success: true });
});
return true;
}
if (request.action === 'checkPricesNow') {
checkPrices().then(() => {
sendResponse({ success: true });
}).catch((error) => {
console.error('立即检查价格失败:', error);
sendResponse({ success: false, error: error.message });
});
return true;
}
if (request.action === 'systemCheck') {
// 一键系统检测
runSystemCheck().then((results) => {
sendResponse({ success: true, results: results });
}).catch((error) => {
sendResponse({ success: false, error: error.message });
});
return true;
}
});