@@ -95,6 +109,9 @@ export const UserCenter = () => {
} onClick={() => handleModifyInfo(modifyRoutes.passkey)}>管理 Passkey
} onClick={() => handleModifyInfo(modifyRoutes.backup_code)}>备份代码
+ {isSupport && (
+
}>授权通知权限
+ )}
diff --git a/src/services/KaiqiuService.ts b/src/services/KaiqiuService.ts
index 89cca1a..5907b53 100644
--- a/src/services/KaiqiuService.ts
+++ b/src/services/KaiqiuService.ts
@@ -1,5 +1,5 @@
import * as cheerio from "cheerio";
-import { htmlRequestHeaders, parseEventInfo, redis, REDIS_CACHE_HOUR } from "../utils/server";
+import { htmlRequestHeaders, parseEventInfo, redis } from "../utils/server";
import type { ClubInfo, IEventInfo } from "../types";
import dayjs from "dayjs";
import utc from 'dayjs/plugin/utc';
@@ -66,12 +66,7 @@ export class KaiqiuService {
data: [],
total: 0,
};
- const key = `my-kaiqiuwang:club-events:${clubId}:page-${page}`;
- let html = await redis.get(key).catch(() => '');
- if (!html) {
- html = await this.#fetchEventListHTML(clubId, page);
- redis.setex(key, 60 * 60 * REDIS_CACHE_HOUR, html);
- }
+ const html = await this.#fetchEventListHTML(clubId, page);
const data = await this.#parseEventList(html);
return data;
}
@@ -128,14 +123,9 @@ export class KaiqiuService {
}
public static async getEventInfo(eventId: string, force?: boolean) {
- const key = `my-kaiqiuwang:match:${eventId}`;
- let eventPage = await redis.get(key) ?? '';
// https://kaiqiuwang.cc/home/space-event-id-175775.html
const eventURL = `${this.#baseURL}/home/space-event-id-${eventId}.html`;
- if (!eventPage || eventPage.includes('连接超时') || force) {
- eventPage = await fetch(eventURL, { headers: htmlRequestHeaders }).then(res => res.text() ?? '');
- await redis.setex(key, 60 * 60 * 10, eventPage)
- }
+ const eventPage = await fetch(eventURL, { headers: htmlRequestHeaders }).then(res => res.text() ?? '');
const $ = cheerio.load(eventPage);
const eventContent = $('.event_content').text().replace(/(\r|\n)/g, ',').split(',').filter(Boolean).join(' ');
const { y, M, D, H, m} = /比赛开始:.*?(?\d{4})年(?\d{2})月(?\d{2})日 \w+ (?\d{2}):(?\d{2})/
@@ -159,14 +149,9 @@ export class KaiqiuService {
}
}
- public static async getMatchDetail(eventId: string, force?: boolean) {
- const key = `my-kaiqiuwang:match-member:${eventId}`;
- let html = await redis.get(key).catch(() => '') || '';
- if (!html || force) {
- const url = `${this.#baseURL}/home/space.php?do=event&id=${eventId}&view=member&status=2`;
- const html = await fetch(url, { headers: htmlRequestHeaders }).then(res => res.text() || '');
- redis.setex(key, 60 * 60 * REDIS_CACHE_HOUR, html);
- }
+ public static async getMatchDetail(eventId: string) {
+ const url = `${this.#baseURL}/home/space.php?do=event&id=${eventId}&view=member&status=2`;
+ const html = await fetch(url, { headers: htmlRequestHeaders }).then(res => res.text() || '');
return parseEventInfo(html);
}
diff --git a/src/services/ScheduleService.ts b/src/services/ScheduleService.ts
new file mode 100644
index 0000000..1e2f59e
--- /dev/null
+++ b/src/services/ScheduleService.ts
@@ -0,0 +1,22 @@
+import * as nodeSchedule from 'node-schedule';
+import { WebSocketService } from './WebsocketService';
+
+export class ScheduleService {
+ constructor() {
+ }
+
+ // startTestSchedule() {
+ // const shedule = nodeSchedule.scheduleJob('test-schedule', {
+ // second: 0,
+ // }, async () => {
+ // console.debug('Test schedule: %s, triggers: %s', Date.now(), shedule.triggeredJobs());
+ // WebSocketService.broadcast('SERVER_PUSH', { title: 'Test', options: {
+ // body: 'This is a test message',
+ // icon: 'https://tt.ksr.la/logo-1dc5sdvb.jpg',
+ // data: {
+ // url: 'https://google.com',
+ // },
+ // } });
+ // });
+ // }
+}
\ No newline at end of file
diff --git a/src/services/WebsocketService.ts b/src/services/WebsocketService.ts
index 1b827c1..2c8ac32 100644
--- a/src/services/WebsocketService.ts
+++ b/src/services/WebsocketService.ts
@@ -45,9 +45,9 @@ export class WebSocketService {
static async #initSubscribe(ws: BunServerWebSocket) {
const user = ws.data.user;
const subEvets = await EventSubscribeService.getEvents(user.sub).then(e => e.map(v => getEventSubKey(v)));
- this.userSub(ws, user.sub, publicTopics);
+ // this.userSub(ws, user.sub, publicTopics);
this.userSub(ws, user.sub, subEvets);
- this.userSub(ws, user.sub, [`MSG:${user.sub}`]);
+ // this.userSub(ws, user.sub, [`MSG:${user.sub}`]);
}
static publish(ws: BunServerWebSocket, topic: string, message: string, withSelf?: boolean) {
diff --git a/src/sw.ts b/src/sw.ts
index e357c47..939e82e 100644
--- a/src/sw.ts
+++ b/src/sw.ts
@@ -1,42 +1 @@
-///
-
-const sw = self as unknown as ServiceWorkerGlobalScope;
-
-sw.addEventListener('install', (event) => {
- console.log('SW Installed');
- sw.skipWaiting();
-});
-
-// 核心:处理通知推送
-sw.addEventListener('push', (event) => {
- const data = event.data?.json() ?? {};
-
- event.waitUntil(
- sw.registration.showNotification(data.title || '新通知', {
- body: data.body || '您有一条新消息',
- icon: '/pwa-192x192.png',
- // 携带自定义数据,方便点击时跳转
- data: { url: data.url || '/' }
- })
- );
-});
-
-// 处理通知点击跳转
-sw.addEventListener('notificationclick', (event) => {
- event.notification.close();
- const urlToOpen = event.notification.data.url;
-
- event.waitUntil(
- sw.clients.matchAll({ type: 'window' }).then((windowClients) => {
- // 如果已经打开了页面,则聚焦;否则打开新窗口
- for (const client of windowClients) {
- if (client.url === urlToOpen && 'focus' in client) {
- return client.focus();
- }
- }
- if (sw.clients.openWindow) {
- return sw.clients.openWindow(urlToOpen);
- }
- })
- );
-});
\ No newline at end of file
+///
\ No newline at end of file
diff --git a/src/utils/common.ts b/src/utils/common.ts
index b86b8ea..c998adb 100644
--- a/src/utils/common.ts
+++ b/src/utils/common.ts
@@ -96,6 +96,14 @@ export type WsServerSendTopicPayload = {
memberNum: number;
};
MSG: { channel: string; name: string; avatar: string; message: string };
+ SERVER_PUSH: {
+ title: string;
+ options?: NotificationOptions & {
+ data?: {
+ url?: string;
+ }
+ },
+ };
}
export type WsServerSendTopics = keyof WsServerSendTopicPayload;
@@ -155,4 +163,4 @@ export function fromServerMessage(message: string): {
data: `${e}`,
};
}
-}
\ No newline at end of file
+}
diff --git a/src/utils/firebase.ts b/src/utils/firebase.ts
new file mode 100644
index 0000000..41cddd5
--- /dev/null
+++ b/src/utils/firebase.ts
@@ -0,0 +1,21 @@
+// Import the functions you need from the SDKs you need
+import { initializeApp } from "firebase/app";
+import { getAnalytics } from "firebase/analytics";
+// TODO: Add SDKs for Firebase products that you want to use
+// https://firebase.google.com/docs/web/setup#available-libraries
+
+// Your web app's Firebase configuration
+// For Firebase JS SDK v7.20.0 and later, measurementId is optional
+const firebaseConfig = {
+ apiKey: "AIzaSyA_ZAXa7edi0Z62purDId7cFFBEvSTQd_Y",
+ authDomain: "my-kaiqiuwang.firebaseapp.com",
+ projectId: "my-kaiqiuwang",
+ storageBucket: "my-kaiqiuwang.firebasestorage.app",
+ messagingSenderId: "373570546459",
+ appId: "1:373570546459:web:f5679b6866b6234b8881be",
+ measurementId: "G-Y027MHX4M7"
+};
+
+// Initialize Firebase
+export const app = initializeApp(firebaseConfig);
+export const analytics = getAnalytics(app);
\ No newline at end of file
diff --git a/src/utils/front.ts b/src/utils/front.ts
index e98d20b..d025aa8 100644
--- a/src/utils/front.ts
+++ b/src/utils/front.ts
@@ -94,4 +94,15 @@ export const openWebMapRaw = (type: MapType, location: MapLocation): void => {
export const AUTH_CALLBACK_URL = `${window.location.origin}/auth/callback`;
-export const USER_CENTER_URL = `${window.location.origin}/user-center`;
\ No newline at end of file
+export const USER_CENTER_URL = `${window.location.origin}/user-center`;
+
+export async function sendNotification(title: string, options?: NotificationOptions) {
+ const registration = await navigator.serviceWorker.getRegistration('/sw.js');
+ const hasPermission = registration && Notification.permission === 'granted';
+ console.log(`sendNotification: hasPermission: ${hasPermission}, title: ${title}, options: ${JSON.stringify(options)}`);
+ if (hasPermission) {
+ registration.showNotification(title, options);
+ } else {
+ new Notification(title, options);
+ }
+}
diff --git a/src/utils/server.ts b/src/utils/server.ts
index a4f7a9d..00aaf33 100644
--- a/src/utils/server.ts
+++ b/src/utils/server.ts
@@ -2,7 +2,7 @@ import type { Player } from "../types";
import * as cheerio from "cheerio";
import { XCXAPI } from "../services/xcxApi";
import { KAIQIU_BASE_URL, LOGTO_DOMAIN } from "./common";
-import { RedisClient, file } from "bun";
+import { RedisClient } from "bun";
import { createRemoteJWKSet, jwtVerify } from 'jose';
import { LOGTO_RESOURCE } from "./constants";
diff --git a/src/utils/swUtils.ts b/src/utils/swUtils.ts
new file mode 100644
index 0000000..e69de29