Browse Source

✨ feat(agora): add dynamic token fetching for agora services

- add agoraClient in api.ts to handle agora-related API requests
- implement fetchDynamicToken function to get RTC/RTM tokens from server
- modify useAgoraSTT hook to use dynamic tokens instead of static ones
- remove token requirement from AgoraSTTConfig validation

✨ refactor(agora): update agora configuration requirements

- remove token from required agora configuration fields
- add currentToken ref to track dynamically fetched tokens
- update authorization header to use dynamic tokens
- add error handling for token API responses
yourname 4 months ago
parent
commit
9a09a364d7
3 changed files with 42 additions and 4 deletions
  1. 5 1
      src/client/api.ts
  2. 36 2
      src/client/hooks/useAgoraSTT.ts
  3. 1 1
      src/client/utils/agora-stt.ts

+ 5 - 1
src/client/api.ts

@@ -2,7 +2,7 @@ import axios, { isAxiosError } from 'axios';
 import { hc } from 'hono/client'
 import type {
   AuthRoutes, UserRoutes, RoleRoutes,
-  FileRoutes
+  FileRoutes, AgoraRoutes
 } from '@/server/api';
 
 // 创建 axios 适配器
@@ -75,3 +75,7 @@ export const roleClient = hc<RoleRoutes>('/', {
 export const fileClient = hc<FileRoutes>('/', {
   fetch: axiosFetch,
 }).api.v1.files;
+
+export const agoraClient = hc<AgoraRoutes>('/', {
+  fetch: axiosFetch,
+}).api.v1.agora;

+ 36 - 2
src/client/hooks/useAgoraSTT.ts

@@ -1,6 +1,7 @@
 import { useState, useCallback, useRef, useEffect } from 'react';
 import { AgoraSTTConfig, TranscriptionResult, AgoraSTTState, UseAgoraSTTResult } from '@/client/types/agora-stt';
 import { getAgoraConfig, validateAgoraConfig, isBrowserSupported, getBrowserSupportError } from '@/client/utils/agora-stt';
+import { agoraClient } from '@/client/api';
 
 export const useAgoraSTT = (): UseAgoraSTTResult => {
   const [state, setState] = useState<AgoraSTTState>({
@@ -19,6 +20,7 @@ export const useAgoraSTT = (): UseAgoraSTTResult => {
   const mediaRecorder = useRef<MediaRecorder | null>(null);
   const audioChunks = useRef<Blob[]>([]);
   const config = useRef<AgoraSTTConfig | null>(null);
+  const currentToken = useRef<string | null>(null);
 
   const updateState = useCallback((updates: Partial<AgoraSTTState>) => {
     setState(prev => ({ ...prev, ...updates }));
@@ -28,6 +30,34 @@ export const useAgoraSTT = (): UseAgoraSTTResult => {
     updateState({ error });
   }, [updateState]);
 
+  const fetchDynamicToken = useCallback(async (type: 'rtc' | 'rtm', channel?: string, userId?: string): Promise<string> => {
+    try {
+      const query: any = { type };
+
+      if (type === 'rtc' && channel) {
+        query.channel = channel;
+      } else if (type === 'rtm' && userId) {
+        query.userId = userId;
+      }
+
+      const response = await agoraClient.token.$get({ query });
+
+      if (!response.ok) {
+        throw new Error(`Token API returned ${response.status}: ${response.statusText}`);
+      }
+
+      const data = await response.json();
+
+      if (!data.token) {
+        throw new Error('Invalid token response format');
+      }
+
+      return data.token;
+    } catch (error) {
+      throw new Error(`Failed to fetch dynamic token: ${error instanceof Error ? error.message : 'Unknown error'}`);
+    }
+  }, []);
+
   const initializeConfig = useCallback((): boolean => {
     try {
       if (!isBrowserSupported()) {
@@ -59,12 +89,16 @@ export const useAgoraSTT = (): UseAgoraSTTResult => {
     try {
       updateState({ error: null, isConnecting: true });
 
+      // 动态获取RTC Token
+      const dynamicToken = await fetchDynamicToken('rtc', config.current!.channel);
+      currentToken.current = dynamicToken;
+
       // 模拟Agora STT加入频道API调用
       const joinResponse = await fetch(config.current!.sttJoinUrl, {
         method: 'POST',
         headers: {
           'Content-Type': 'application/json',
-          'Authorization': `Bearer ${config.current!.token}`
+          'Authorization': `Bearer ${dynamicToken}`
         },
         body: JSON.stringify({
           appId: config.current!.appId,
@@ -133,7 +167,7 @@ export const useAgoraSTT = (): UseAgoraSTTResult => {
       setError('Failed to join Agora channel: ' + (error instanceof Error ? error.message : 'Unknown error'));
       updateState({ isConnecting: false });
     }
-  }, [initializeConfig, updateState, setError]);
+  }, [initializeConfig, updateState, setError, fetchDynamicToken]);
 
   const leaveChannel = useCallback((): void => {
     if (wsConnection.current) {

+ 1 - 1
src/client/utils/agora-stt.ts

@@ -15,7 +15,7 @@ export const getAgoraConfig = (): AgoraSTTConfig => {
 };
 
 export const validateAgoraConfig = (config: AgoraSTTConfig): string | null => {
-  const requiredFields = ['appId', 'primaryCert', 'token'];
+  const requiredFields = ['appId', 'primaryCert'];
   for (const field of requiredFields) {
     if (!config[field as keyof AgoraSTTConfig]) {
       return `Missing required Agora configuration: ${field}`;