增加浏览器离线通知功能
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getStorage } from '@/lib/db';
|
||||
import {
|
||||
getUserDevices,
|
||||
revokeAllRefreshTokens,
|
||||
@@ -51,6 +52,8 @@ export async function DELETE(request: NextRequest) {
|
||||
}
|
||||
|
||||
await revokeRefreshToken(authInfo.username, tokenId);
|
||||
const storage = getStorage();
|
||||
await storage.deletePushSubscriptionsByTokenId?.(authInfo.username, tokenId);
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
} catch (error) {
|
||||
@@ -69,6 +72,8 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
try {
|
||||
await revokeAllRefreshTokens(authInfo.username);
|
||||
const storage = getStorage();
|
||||
await storage.deleteAllPushSubscriptions?.(authInfo.username);
|
||||
|
||||
const response = NextResponse.json({ ok: true });
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getStorage } from '@/lib/db';
|
||||
import { db } from '@/lib/db';
|
||||
import { getUserDevices, revokeRefreshToken } from '@/lib/refresh-token';
|
||||
|
||||
@@ -53,11 +54,13 @@ export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const currentTokenId = authInfo.tokenId;
|
||||
const devices = await getUserDevices(username);
|
||||
const storage = getStorage();
|
||||
|
||||
// 撤销所有非当前设备的 token
|
||||
for (const device of devices) {
|
||||
if (device.tokenId !== currentTokenId) {
|
||||
await revokeRefreshToken(username, device.tokenId);
|
||||
await storage.deletePushSubscriptionsByTokenId?.(username, device.tokenId);
|
||||
console.log(`Revoked token ${device.tokenId} for ${username} after password change`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getStorage } from '@/lib/db';
|
||||
import { revokeRefreshToken } from '@/lib/refresh-token';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
@@ -12,6 +13,8 @@ export async function POST(request: NextRequest) {
|
||||
if (authInfo && authInfo.username && authInfo.tokenId) {
|
||||
try {
|
||||
await revokeRefreshToken(authInfo.username, authInfo.tokenId);
|
||||
const storage = getStorage();
|
||||
await storage.deletePushSubscriptionsByTokenId?.(authInfo.username, authInfo.tokenId);
|
||||
} catch (error) {
|
||||
console.error('Failed to revoke refresh token:', error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getStorage } from '@/lib/db';
|
||||
import {
|
||||
createPushSubscriptionRecord,
|
||||
getVapidPublicKey,
|
||||
isWebPushConfigured,
|
||||
} from '@/lib/web-push';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
function extractSubscriptionKeys(subscription: any) {
|
||||
const p256dh = subscription?.keys?.p256dh || subscription?.toJSON?.()?.keys?.p256dh;
|
||||
const auth = subscription?.keys?.auth || subscription?.toJSON?.()?.keys?.auth;
|
||||
return { p256dh, auth };
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const authInfo = getAuthInfoFromCookie(request);
|
||||
if (!authInfo?.username) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const storage = getStorage();
|
||||
|
||||
const [configured, publicKey, subscriptions] = await Promise.all([
|
||||
isWebPushConfigured(storage),
|
||||
getVapidPublicKey(storage),
|
||||
storage.getEnabledPushSubscriptions
|
||||
? storage.getEnabledPushSubscriptions(authInfo.username)
|
||||
: Promise.resolve([]),
|
||||
]);
|
||||
|
||||
const currentDeviceSubscriptions = authInfo.tokenId
|
||||
? subscriptions.filter((item) => item.tokenId === authInfo.tokenId)
|
||||
: [];
|
||||
|
||||
return NextResponse.json({
|
||||
configured,
|
||||
publicKey,
|
||||
pushNotifications: currentDeviceSubscriptions.length > 0,
|
||||
hasDeviceToken: Boolean(authInfo.tokenId),
|
||||
subscriptionCount: subscriptions.length,
|
||||
currentDeviceSubscriptionCount: currentDeviceSubscriptions.length,
|
||||
});
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const authInfo = getAuthInfoFromCookie(request);
|
||||
if (!authInfo?.username) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { subscription, enabled } = body;
|
||||
const storage = getStorage();
|
||||
|
||||
if (enabled === false) {
|
||||
if (authInfo.tokenId) {
|
||||
await storage.deletePushSubscriptionsByTokenId?.(authInfo.username, authInfo.tokenId);
|
||||
}
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
|
||||
if (subscription) {
|
||||
const { p256dh, auth } = extractSubscriptionKeys(subscription);
|
||||
if (!subscription.endpoint || !p256dh || !auth) {
|
||||
return NextResponse.json({ error: 'Invalid push subscription' }, { status: 400 });
|
||||
}
|
||||
|
||||
if (!authInfo.tokenId) {
|
||||
return NextResponse.json(
|
||||
{ error: '当前登录模式不支持设备级浏览器通知订阅' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
await storage.upsertPushSubscription?.(
|
||||
authInfo.username,
|
||||
createPushSubscriptionRecord({
|
||||
username: authInfo.username,
|
||||
tokenId: authInfo.tokenId,
|
||||
endpoint: subscription.endpoint,
|
||||
p256dh,
|
||||
auth,
|
||||
userAgent: request.headers.get('user-agent'),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
|
||||
export async function DELETE(request: NextRequest) {
|
||||
const authInfo = getAuthInfoFromCookie(request);
|
||||
if (!authInfo?.username) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const storage = getStorage();
|
||||
|
||||
if (body?.endpoint) {
|
||||
await storage.deletePushSubscriptionByEndpoint?.(authInfo.username, body.endpoint);
|
||||
} else if (authInfo.tokenId) {
|
||||
await storage.deletePushSubscriptionsByTokenId?.(authInfo.username, authInfo.tokenId);
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { getStorage } from '@/lib/db';
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
/**
|
||||
* GET - 获取用户邮箱设置
|
||||
* GET - 获取用户通知设置
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
const authInfo = getAuthInfoFromCookie(request);
|
||||
@@ -31,7 +31,7 @@ export async function GET(request: NextRequest) {
|
||||
emailNotifications,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取用户邮箱设置失败:', error);
|
||||
console.error('获取用户通知设置失败:', error);
|
||||
return NextResponse.json(
|
||||
{ error: (error as Error).message },
|
||||
{ status: 500 }
|
||||
@@ -40,7 +40,7 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
|
||||
/**
|
||||
* POST - 保存用户邮箱设置
|
||||
* POST - 保存用户通知设置
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
const authInfo = getAuthInfoFromCookie(request);
|
||||
@@ -76,12 +76,14 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '邮箱设置保存成功',
|
||||
message: '通知设置保存成功',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('保存用户邮箱设置失败:', error);
|
||||
console.error('保存用户通知设置失败:', error);
|
||||
return NextResponse.json(
|
||||
{ error: (error as Error).message },
|
||||
{ status: 500 }
|
||||
|
||||
Reference in New Issue
Block a user