129 lines
3.4 KiB
TypeScript
129 lines
3.4 KiB
TypeScript
import { twilioConfig } from '../config/twilio';
|
||
|
||
export interface TokenRequest {
|
||
identity: string;
|
||
roomName: string;
|
||
apiKey?: string;
|
||
apiSecret?: string;
|
||
}
|
||
|
||
export interface TokenResponse {
|
||
token: string;
|
||
identity: string;
|
||
roomName: string;
|
||
}
|
||
|
||
// 模拟Token生成服务
|
||
// 在实际生产环境中,这应该是一个后端API服务
|
||
export class TokenService {
|
||
// 生成访问令牌
|
||
async generateAccessToken(request: TokenRequest): Promise<TokenResponse> {
|
||
try {
|
||
// 在实际应用中,这里应该调用后端API
|
||
// 这里我们创建一个模拟的token
|
||
const mockToken = this.generateMockToken(request.identity, request.roomName);
|
||
|
||
return {
|
||
token: mockToken,
|
||
identity: request.identity,
|
||
roomName: request.roomName,
|
||
};
|
||
} catch (error) {
|
||
console.error('Error generating access token:', error);
|
||
throw new Error('Failed to generate access token');
|
||
}
|
||
}
|
||
|
||
// 生成模拟Token(仅用于开发测试)
|
||
private generateMockToken(identity: string, roomName: string): string {
|
||
// 这是一个简化的JWT模拟
|
||
// 实际应用中应该使用Twilio SDK在后端生成真实的token
|
||
const header = {
|
||
typ: 'JWT',
|
||
alg: 'HS256',
|
||
cty: 'twilio-fpa;v=1'
|
||
};
|
||
|
||
const payload = {
|
||
iss: twilioConfig.apiKey,
|
||
sub: twilioConfig.accountSid,
|
||
nbf: Math.floor(Date.now() / 1000),
|
||
exp: Math.floor(Date.now() / 1000) + 3600, // 1小时有效期
|
||
jti: `${twilioConfig.apiKey}-${Date.now()}`,
|
||
grants: {
|
||
identity: identity,
|
||
video: {
|
||
room: roomName
|
||
}
|
||
}
|
||
};
|
||
|
||
// 在实际应用中,这里应该用正确的签名算法
|
||
const encodedHeader = btoa(JSON.stringify(header));
|
||
const encodedPayload = btoa(JSON.stringify(payload));
|
||
const signature = btoa(`${twilioConfig.apiSecret}-signature`);
|
||
|
||
return `${encodedHeader}.${encodedPayload}.${signature}`;
|
||
}
|
||
|
||
// 验证Token(模拟)
|
||
validateToken(token: string): boolean {
|
||
try {
|
||
const parts = token.split('.');
|
||
if (parts.length !== 3) return false;
|
||
|
||
const payload = JSON.parse(atob(parts[1]));
|
||
const now = Math.floor(Date.now() / 1000);
|
||
|
||
return payload.exp > now;
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 解析Token信息
|
||
parseToken(token: string): { identity: string; roomName: string } | null {
|
||
try {
|
||
const parts = token.split('.');
|
||
if (parts.length !== 3) return null;
|
||
|
||
const payload = JSON.parse(atob(parts[1]));
|
||
|
||
return {
|
||
identity: payload.grants?.identity || '',
|
||
roomName: payload.grants?.video?.room || '',
|
||
};
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
export const tokenService = new TokenService();
|
||
|
||
// Express.js API端点示例(如果需要真实的后端服务)
|
||
export const createTokenEndpoint = () => {
|
||
return async (req: any, res: any) => {
|
||
try {
|
||
const { identity, roomName } = req.body;
|
||
|
||
if (!identity || !roomName) {
|
||
return res.status(400).json({
|
||
error: 'Identity and roomName are required'
|
||
});
|
||
}
|
||
|
||
const tokenResponse = await tokenService.generateAccessToken({
|
||
identity,
|
||
roomName,
|
||
});
|
||
|
||
res.json(tokenResponse);
|
||
} catch (error) {
|
||
console.error('Token generation error:', error);
|
||
res.status(500).json({
|
||
error: 'Failed to generate token'
|
||
});
|
||
}
|
||
};
|
||
};
|