@trtc/calls-uikit-react 4.4.3-beta.1 → 4.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/package.json +4 -3
  2. package/src/Components/components/common/UIKitModal/UIKitModal.tsx +250 -0
  3. package/src/Components/components/common/UIKitModal/UIKitModalState.ts +177 -0
  4. package/src/Components/components/common/UIKitModal/index.module.scss +176 -0
  5. package/src/Components/components/common/UIKitModal/index.ts +3 -0
  6. package/src/TUICallService/CallService/UIKitModal.ts +72 -6
  7. package/src/TUICallService/CallService/bellContext.ts +25 -2
  8. package/src/TUICallService/CallService/engineEventHandler.ts +6 -1
  9. package/src/TUICallService/CallService/index.ts +56 -33
  10. package/src/TUICallService/CallService/miniProgram.ts +0 -12
  11. package/src/TUICallService/TUIStore/callStore.ts +1 -1
  12. package/src/TUICallService/UIKitModal/UIKitModal.ts +117 -0
  13. package/src/TUICallService/UIKitModal/index.ts +2 -0
  14. package/src/TUICallService/UIKitModal/type.ts +15 -0
  15. package/src/TUICallService/locales/en.ts +2 -1
  16. package/src/TUICallService/locales/ja_JP.ts +2 -1
  17. package/src/TUICallService/locales/zh-cn.ts +2 -1
  18. package/src/TUICallService/utils/common-utils.ts +1 -1
  19. package/src/index.ts +1 -1
  20. package/tuicall-uikit-react.es.js +4040 -4533
  21. package/tuicall-uikit-react.umd.js +3 -33
  22. package/types/Components/components/common/UIKitModal/UIKitModal.d.ts +21 -0
  23. package/types/Components/components/common/UIKitModal/UIKitModalState.d.ts +27 -0
  24. package/types/Components/components/common/UIKitModal/index.d.ts +3 -0
  25. package/types/TUICallService/CallService/bellContext.d.ts +3 -0
  26. package/types/TUICallService/CallService/index.d.ts +2 -2
  27. package/types/TUICallService/CallService/miniProgram.d.ts +0 -1
  28. package/types/TUICallService/UIKitModal/UIKitModal.d.ts +4 -0
  29. package/types/TUICallService/UIKitModal/index.d.ts +2 -0
  30. package/types/TUICallService/UIKitModal/type.d.ts +13 -0
  31. package/types/TUICallService/locales/en.d.ts +1 -0
  32. package/types/TUICallService/locales/ja_JP.d.ts +1 -0
  33. package/types/TUICallService/locales/zh-cn.d.ts +1 -0
  34. package/src/TUICallService/utils/validate/validateStatus.ts +0 -26
  35. package/types/TUICallService/utils/validate/validateStatus.d.ts +0 -5
@@ -1,10 +1,17 @@
1
+
2
+ // @if process.env.BUILD_TARGET ='MINI'
3
+ import { UIKitModal } from '../UIKitModal';
4
+ // @endif
5
+ // @if process.env.BUILD_TARGET!='MINI'
1
6
  import TuiStore from '../TUIStore/tuiStore';
2
7
  import { StoreName } from '../const/call';
3
8
  import { NAME } from '../const/index';
4
- import { t } from '../locales'
9
+ import { t } from '../locales';
5
10
  const TUIStore = TuiStore.getInstance();
11
+ // @endif
6
12
 
7
- const MODAL_ERROR_CODES = [
13
+ // @if process.env.BUILD_TARGET!='MINI'
14
+ const WEB_MODAL_ERROR_CODES = [
8
15
  -1001,
9
16
  -1002,
10
17
  -1101,
@@ -14,9 +21,10 @@ const MODAL_ERROR_CODES = [
14
21
  -1201,
15
22
  30000,
16
23
  20007,
24
+ 101002,
17
25
  ];
18
26
 
19
- const MODAL_ERROR_MAP = {
27
+ const WEB_MODAL_ERROR_MAP = {
20
28
  '-1001': {
21
29
  id: 10001,
22
30
  key: 'error.10001'
@@ -52,19 +60,58 @@ const MODAL_ERROR_MAP = {
52
60
  '20007': {
53
61
  id: 10013,
54
62
  key: 'error.10013'
63
+ },
64
+ '101002': {
65
+ id: 10014,
66
+ key: 'error.10014'
55
67
  }
56
68
  };
69
+ // @endif
70
+
71
+ // @if process.env.BUILD_TARGET ='MINI'
72
+ const MINI_MODAL_ERROR_CODES = [
73
+ -1001,
74
+ -1002,
75
+ 101002,
76
+ ];
77
+
78
+ const MINI_MODAL_ERROR_MAP = {
79
+ '-1001': {
80
+ id: 10001,
81
+ content: '您的应用还未开通音视频通话能力'
82
+ },
83
+ '-1002': {
84
+ id: 10002,
85
+ content: '您暂不支持使用该能力,请前往购买页购买开通'
86
+ },
87
+ '101002': {
88
+ id: 10014,
89
+ content: '发起通话失败,用户 ID 无效,请确认该用户已注册'
90
+ }
91
+ }
92
+ // @endif
57
93
 
58
94
  export function handleModalError(error) {
59
95
  if (!error || !error?.code) {
60
96
  return;
61
97
  }
62
98
 
63
- if (!MODAL_ERROR_CODES.includes(error.code)) {
99
+ // @if process.env.BUILD_TARGET!='MINI'
100
+ handleWebModalError(error);
101
+ // @endif
102
+
103
+ // @if process.env.BUILD_TARGET='MINI'
104
+ handleMiniModalError(error);
105
+ // @endif
106
+ }
107
+
108
+ // @if process.env.BUILD_TARGET!='MINI'
109
+ function handleWebModalError(error) {
110
+ if (!WEB_MODAL_ERROR_CODES.includes(error.code)) {
64
111
  return;
65
112
  }
66
113
 
67
- const errorInfo = MODAL_ERROR_MAP[error.code.toString()];
114
+ const errorInfo = WEB_MODAL_ERROR_MAP[error.code.toString()];
68
115
  if (errorInfo) {
69
116
  let content = t(errorInfo.key);
70
117
  TUIStore.update(StoreName.CALL, NAME.MODAL_ERROR, {
@@ -73,4 +120,23 @@ export function handleModalError(error) {
73
120
  title: t('error')
74
121
  });
75
122
  }
76
- }
123
+ }
124
+ // @endif
125
+
126
+ // @if process.env.BUILD_TARGET='MINI'
127
+ function handleMiniModalError(error) {
128
+ if (!MINI_MODAL_ERROR_CODES.includes(error.code)) {
129
+ return;
130
+ }
131
+
132
+ const errorInfo = MINI_MODAL_ERROR_MAP[error.code.toString()];
133
+ if (errorInfo) {
134
+ UIKitModal.openModal({
135
+ id: errorInfo.id,
136
+ content: errorInfo.content,
137
+ title: '错误',
138
+ type: 'error'
139
+ });
140
+ }
141
+ }
142
+ // @endif
@@ -10,6 +10,7 @@ export class BellContext {
10
10
  private _calleeBellFilePath: string = DEFAULT_CALLEE_BELL_FILEPATH;
11
11
  private _callRole: string = CallRole.UNKNOWN;
12
12
  private _callStatus: string = CallStatus.IDLE;
13
+ private _isPlaying: boolean = false;
13
14
 
14
15
  constructor() {
15
16
  this._bellContext = new Audio();
@@ -39,14 +40,26 @@ export class BellContext {
39
40
  async play() {
40
41
  try {
41
42
  if (this._callStatus !== CallStatus.CALLING) {
42
- return ;
43
+ return;
43
44
  }
45
+ // iOS 优化:增加更严格的状态检查
46
+ if (this._callStatus !== CallStatus.CALLING || this._isPlaying) {
47
+ console.warn(`${NAME.PREFIX}play skipped, callStatus: ${this._callStatus}, isPlaying: ${this._isPlaying}`);
48
+ return;
49
+ }
50
+ this._isPlaying = true;
44
51
  this.setBellSrc();
45
52
  if (this._callRole === CallRole.CALLEE && !this._isMuteBell) {
53
+ // 再次检查状态,避免在设置过程中状态已变更
54
+ if (!this._isPlaying || this._callStatus !== CallStatus.CALLING) return;
46
55
  await this._bellContext.play();
56
+ return;
47
57
  }
48
58
  if (this._callRole === CallRole.CALLER) {
59
+ // 再次检查状态,避免在设置过程中状态已变更
60
+ if (!this._isPlaying || this._callStatus !== CallStatus.CALLING) return;
49
61
  await this._bellContext.play();
62
+ return;
50
63
  }
51
64
  } catch (error) {
52
65
  console.warn(`${NAME.PREFIX}Failed to play audio file, ${error}`);
@@ -55,7 +68,8 @@ export class BellContext {
55
68
 
56
69
  async stop() {
57
70
  try {
58
- await this._bellContext.pause();
71
+ this._isPlaying = false;
72
+ await this._bellContext?.pause();
59
73
  } catch (error) {
60
74
  console.warn(`${NAME.PREFIX}Failed to stop audio file, ${error}`);
61
75
  }
@@ -78,11 +92,20 @@ export class BellContext {
78
92
  this._calleeBellFilePath = '';
79
93
  this._callRole = CallRole.UNKNOWN;
80
94
  this._callStatus = CallStatus.IDLE;
95
+ this._isPlaying = false;
81
96
  this._bellContext.pause();
82
97
  this._bellContext = null;
83
98
  } catch (error) {
84
99
  console.warn(`${NAME.PREFIX}Failed to destroy, ${error}`);
85
100
  }
86
101
  }
102
+
103
+ private _delay(ms: number): any {
104
+ return new Promise((resolve: any) => setTimeout(resolve, ms));
105
+ }
106
+
107
+ private _delayCallback(callback: () => void, ms: number) {
108
+ setTimeout(callback, ms);
109
+ }
87
110
 
88
111
  }
@@ -90,6 +90,9 @@ export default class EngineEventHandler {
90
90
  const callStatus = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS);
91
91
 
92
92
  if (callStatus === CallStatus.CALLING && callRole === CallRole.CALLER) {
93
+ // iOS 优化:主叫状态变更时立即停止铃声
94
+ this._callService?._bellContext?.stop();
95
+
93
96
  TUIStore.update(StoreName.CALL, NAME.CALL_STATUS, CallStatus.CONNECTED);
94
97
  this._callService?.startTimer();
95
98
  }
@@ -183,14 +186,16 @@ export default class EngineEventHandler {
183
186
  }, StoreName.CALL);
184
187
  }
185
188
  private async _handleOnCallBegin(event: any): Promise<void> {
189
+ // iOS 优化:ON_CALL_BEGIN 事件时立即停止铃声
190
+ this._callService?._bellContext?.stop();
186
191
  this._callerChangeToConnected();
187
192
  TUIStore.update(StoreName.CALL, NAME.CALL_TIPS, { text: 'answered', duration: 2000 });
188
193
  await this._callService.openMicrophone();
189
194
  console.log(`${NAME.PREFIX}accept event data: ${JSON.stringify(event)}.`);
190
195
  }
191
196
  private async _handleUserEnter(event: any): Promise<void> {
192
- this._callerChangeToConnected();
193
197
  const { userID: userId, data } = analyzeEventData(event);
198
+ this._callerChangeToConnected();
194
199
  await this._addUserToRemoteUserInfoList(userId);
195
200
  let remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
196
201
  remoteUserInfoList = remoteUserInfoList.map((obj: IUserInfo) => {
@@ -22,14 +22,14 @@ import TuiGlobal from '../TUIGlobal/tuiGlobal';
22
22
  import TuiStore from '../TUIStore/tuiStore';
23
23
  import { UIDesign } from './UIDesign';
24
24
  import ChatCombine from './chatCombine';
25
- import { AIAssistant } from './AIAssistant';
26
25
  import EngineEventHandler from './engineEventHandler';
27
26
  const TUIGlobal: ITUIGlobal = TuiGlobal.getInstance();
28
27
  const TUIStore: ITUIStore = TuiStore.getInstance();
29
28
  const uiDesign = UIDesign.getInstance();
30
29
  uiDesign.setTUIStore(TUIStore);
30
+ const version = '4.4.3';
31
+ import { AIAssistant } from './AIAssistant'; // 仅 web 支持 AI 实时字幕
31
32
  const aiAssistant = AIAssistant.getInstance();
32
- const version = '4.4.3-beta.1';
33
33
  const frameWork = 'react';
34
34
  export { TUIGlobal, TUIStore, uiDesign };
35
35
 
@@ -115,15 +115,17 @@ export default class TUICallService {
115
115
  });
116
116
  uiDesign.setEngineInstance(this._tuiCallEngine);
117
117
  this._addListenTuiCallEngineEvent();
118
- this._bellContext = new BellContext();
118
+ if (this._bellContext) {
119
+ this._bellContext?.destroy();
120
+ }
121
+ this._bellContext = new BellContext();
119
122
  TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, { userId: userID });
120
123
  TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, { userId: userID });
121
124
  uiDesign.updateViewBackgroundUserId('local');
122
-
123
125
  aiAssistant.setEngineInstance(this._tuiCallEngine);
124
126
  aiAssistant.setImInstance(this.getTim());
125
-
126
127
  this.enableAISubtitle(true);
128
+
127
129
  await this._tuiCallEngine.login({ userID, userSig, assetsPath: '' }); // web && mini
128
130
  const uiConfig = TUIStore.getData(StoreName.CALL, NAME.CUSTOM_UI_CONFIG);
129
131
  // close auto play dialog
@@ -187,6 +189,7 @@ export default class TUICallService {
187
189
  public async calls(callsParams: ICallsParams) {
188
190
  if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) !== CallStatus.IDLE) return; // avoid double click when application stuck
189
191
  try {
192
+ TUIStore.update(StoreName.CALL, NAME.PUSHER_ID, NAME.NEW_PUSHER);
190
193
  const { userIDList, type, chatGroupID, offlinePushInfo } = callsParams;
191
194
  if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) !== CallStatus.IDLE) return;
192
195
  const remoteUserInfoList = userIDList.map(userId => ({ userId }));
@@ -204,10 +207,7 @@ export default class TUICallService {
204
207
  public async join(params) {
205
208
  if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) === CallStatus.CONNECTED) return; // avoid double click when application stuck
206
209
  try {
207
- const response = await this._tuiCallEngine.join(params);
208
- TUIStore.update(StoreName.CALL, NAME.IS_CLICKABLE, true);
209
- this.startTimer();
210
-
210
+ TUIStore.update(StoreName.CALL, NAME.PUSHER_ID, NAME.NEW_PUSHER);
211
211
  const updateStoreParams = {
212
212
  [NAME.CALL_ROLE]: CallRole.CALLEE,
213
213
  [NAME.IS_GROUP]: true,
@@ -216,6 +216,10 @@ export default class TUICallService {
216
216
  };
217
217
  TUIStore.updateStore(updateStoreParams, StoreName.CALL);
218
218
 
219
+ const response = await this._tuiCallEngine.join(params);
220
+ TUIStore.update(StoreName.CALL, NAME.IS_CLICKABLE, true);
221
+ this.startTimer();
222
+
219
223
  updateDeviceList(this._tuiCallEngine);;
220
224
  await this._tuiCallEngine.setVideoQuality(TUIStore.getData(StoreName.CALL, NAME.VIDEO_RESOLUTION));
221
225
  const localUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
@@ -343,19 +347,7 @@ export default class TUICallService {
343
347
  TUIStore.update(StoreName.CALL, NAME.CALL_STATUS, CallStatus.CONNECTED);
344
348
  updateDeviceList(this._tuiCallEngine);;
345
349
  const response = await this._tuiCallEngine.accept();
346
- if (response) {
347
- this._chatCombine?.callTUIService({ message: response?.data?.message });
348
- TUIStore.update(StoreName.CALL, NAME.IS_CLICKABLE, true);
349
- this.startTimer();
350
- const callMediaType = TUIStore.getData(StoreName.CALL, NAME.CALL_MEDIA_TYPE);
351
- const isCameraDefaultStateClose = this._getFeatureButtonDefaultState(FeatureButton.Camera) === ButtonState.Close;
352
- (callMediaType === CallMediaType.VIDEO) && !isCameraDefaultStateClose && await this.openCamera(NAME.LOCAL_VIDEO);
353
- await this._tuiCallEngine.setVideoQuality(TUIStore.getData(StoreName.CALL, NAME.VIDEO_RESOLUTION));
354
- const localUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
355
- TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, { ...localUserInfo, isEnter: true });
356
- TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, { ...localUserInfo, isEnter: true });
357
- setLocalUserInfoAudioVideoAvailable(true, NAME.AUDIO); // web && mini default open audio
358
- }
350
+ await this._handleAcceptResponse(response);
359
351
  } catch (error) {
360
352
  this._tuiCallEngine?.reportLog?.({
361
353
  name: 'TUICallKit.accept.fail',
@@ -368,6 +360,21 @@ export default class TUICallService {
368
360
  this._resetCallStore();
369
361
  }
370
362
  }
363
+ private async _handleAcceptResponse(response) {
364
+ if (response) {
365
+ this._chatCombine?.callTUIService({ message: response?.data?.message });
366
+ TUIStore.update(StoreName.CALL, NAME.IS_CLICKABLE, true);
367
+ this.startTimer();
368
+ const callMediaType = TUIStore.getData(StoreName.CALL, NAME.CALL_MEDIA_TYPE);
369
+ const isCameraDefaultStateClose = this._getFeatureButtonDefaultState(FeatureButton.Camera) === ButtonState.Close;
370
+ (callMediaType === CallMediaType.VIDEO) && !isCameraDefaultStateClose && await this.openCamera(NAME.LOCAL_VIDEO);
371
+ await this._tuiCallEngine.setVideoQuality(TUIStore.getData(StoreName.CALL, NAME.VIDEO_RESOLUTION));
372
+ const localUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
373
+ TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, { ...localUserInfo, isEnter: true });
374
+ TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, { ...localUserInfo, isEnter: true });
375
+ setLocalUserInfoAudioVideoAvailable(true, NAME.AUDIO); // web && mini default open audio
376
+ }
377
+ }
371
378
  @avoidRepeatedCall()
372
379
  public async hangup() {
373
380
  if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) === CallStatus.IDLE) return; // avoid double click when application stuck
@@ -696,21 +703,29 @@ export default class TUICallService {
696
703
  }
697
704
  }
698
705
  // clear all use avoidRepeatCall decorator state
699
- private _cleanupAllAvoidRepeatCallState() {
700
- this._tuiCallEngine?.reportLog?.({ name: 'TUICallkit._cleanupAllAvoidRepeatCallState', data: { } });
701
- const methodsToClean = [
702
- (this as any).calls,
703
- (this as any).accept,
704
- (this as any).hangup,
705
- (this as any).reject,
706
- ];
706
+ private _cleanupAvoidRepeatCallState(methodName?: string) {
707
+ this._tuiCallEngine?.reportLog?.({ name: 'TUICallkit._cleanupAvoidRepeatCallState', data: { methodName } });
708
+ let methodsToClean = [];
709
+ if (methodName) {
710
+ methodsToClean = [(this as any)[methodName]];
711
+ } else {
712
+ methodsToClean = [
713
+ (this as any).calls,
714
+ (this as any).accept,
715
+ (this as any).hangup,
716
+ (this as any).reject,
717
+ (this as any).join,
718
+ (this as any).inviteUser,
719
+ ];
720
+ }
707
721
 
708
- methodsToClean.forEach(method => {
722
+ methodsToClean?.forEach(method => {
709
723
  method?.clearCallState?.(this);
710
724
  });
711
725
  }
712
726
  private _resetCallStore() {
713
- this._cleanupAllAvoidRepeatCallState();
727
+ this._cleanupAvoidRepeatCallState();
728
+ this._bellContext?.stop();
714
729
 
715
730
  const oldStatusStr = generateStatusChangeText();
716
731
  this._stopTimer();
@@ -744,6 +759,7 @@ export default class TUICallService {
744
759
  callStatus !== CallStatus.IDLE && TUIStore.reset(StoreName.CALL, [NAME.CALL_STATUS], true); // callStatus reset need notify
745
760
  TUIStore.reset(StoreName.CALL, [NAME.IS_MINIMIZED], true); // isMinimized reset need notify
746
761
  TUIStore.reset(StoreName.CALL, [NAME.IS_EAR_PHONE], true); // isEarPhone reset need notify
762
+ TUIStore.reset(StoreName.CALL, [NAME.PUSHER_ID], true); // pusher unload reset need notify
747
763
  TUIStore.reset(StoreName.CALL, [NAME.ENABLE_VIRTUAL_BACKGROUND], true); // ENABLE_VIRTUAL_BACKGROUND reset need notify
748
764
  TUIStore.reset(StoreName.CALL, [NAME.IS_MUTE_SPEAKER], true); // IS_MUTE_SPEAKER reset need notify
749
765
  TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, {
@@ -784,10 +800,17 @@ export default class TUICallService {
784
800
  callRole: TUIStore.getData(StoreName.CALL, NAME.CALL_ROLE),
785
801
  callStatus: TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS),
786
802
  };
803
+ this._tuiCallEngine?.reportLog?.({
804
+ name: 'TUICallkit._handleCallStatusChange.start',
805
+ data: { bellParams }
806
+ });
787
807
  this._bellContext.setBellProperties(bellParams);
788
808
  if (value === CallStatus.CALLING) {
789
- await this?._bellContext?.play();
809
+ this?._bellContext?.play();
790
810
  } else {
811
+ this._tuiCallEngine?.reportLog?.({
812
+ name: 'TUICallkit._bellContext.stop',
813
+ });
791
814
  // 状态变更通知
792
815
  if (value === CallStatus.CONNECTED) {
793
816
  const isGroup = TUIStore.getData(StoreName.CALL, NAME.IS_GROUP);
@@ -41,18 +41,6 @@ export async function beforeCall(type: CallMediaType, that: any) {
41
41
  return CallStatus.IDLE;
42
42
  }
43
43
  }
44
- // 套餐问题提示, 小程序最低需要群组通话版, 1v1 通话版本使用 TRTC 就会报错
45
- export function handlePackageError(error) {
46
- if (error?.code === -1002) {
47
- // @ts-ignore
48
- wx.showModal({
49
- icon: 'none',
50
- title: 'error',
51
- content: error?.message || '',
52
- showCancel: false,
53
- });
54
- }
55
- }
56
44
 
57
45
  export function handleNoPusherCapabilityError(){
58
46
  // @ts-ignore
@@ -41,7 +41,7 @@ export default class CallStore {
41
41
  pusher: {},
42
42
  player: [],
43
43
  isEarPhone: false, // 是否是听筒, 默认: false
44
- pusherId: NAME.INITIAL_PUSHER, // 重新渲染 live-Pusher 的标识位
44
+ pusherId: '', // 重新渲染 live-Pusher 的标识位
45
45
  // 是否开启虚拟背景, 目前仅 web 支持
46
46
  isShowEnableVirtualBackground: false, // 是否显示虚拟背景图标, 默认: false
47
47
  enableVirtualBackground: false, // 是否开启虚拟背景, 默认: false
@@ -0,0 +1,117 @@
1
+ // @ts-nocheck
2
+ import type { UIKitModalOptions, UIKitModalResult } from './type';
3
+ import TUICallKitServer from '../CallService';
4
+
5
+ const URL_REGEX = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/i;
6
+ const FULL_URL_REGEX = /(https?:\/\/[^\s]+)/g;
7
+
8
+ function isDevEnvironment(): boolean {
9
+ const accountInfo = uni.getAccountInfoSync?.();
10
+ const envVersion = accountInfo?.miniProgram?.envVersion;
11
+ return envVersion === 'develop' || envVersion === 'trial';
12
+ }
13
+
14
+ function extractUrlFromContent(content: string): string | null {
15
+ if (!content || typeof content !== 'string') {
16
+ return null;
17
+ }
18
+
19
+ if (content.includes('<a ') || content.includes('<a>')) {
20
+ const hrefMatch = content.match(/href=["']([^"']+)["']/);
21
+ return hrefMatch ? hrefMatch[1] : null;
22
+ }
23
+
24
+ if (URL_REGEX.test(content.trim())) {
25
+ return content.trim();
26
+ }
27
+
28
+ const urlMatch = content.match(FULL_URL_REGEX);
29
+ return urlMatch ? urlMatch[0] : null;
30
+ }
31
+
32
+ function processContent(content: string): string {
33
+ if (!content || typeof content !== 'string') {
34
+ return '';
35
+ }
36
+
37
+ let text = content.replace(/<[^>]+>/g, '');
38
+ text = text
39
+ .replace(/&nbsp;/g, ' ')
40
+ .replace(/&lt;/g, '<')
41
+ .replace(/&gt;/g, '>')
42
+ .replace(/&amp;/g, '&')
43
+ .replace(/&quot;/g, '"');
44
+ text = text.replace(/\s+/g, ' ').trim();
45
+
46
+ const url = extractUrlFromContent(content);
47
+ if (url && !text.includes(url)) {
48
+ return text ? `${text}\n${url}` : url;
49
+ }
50
+
51
+ return text;
52
+ }
53
+
54
+ function reportModalView(options: UIKitModalOptions): void {
55
+ try {
56
+ const tim = TUICallKitServer?.getTim?.();
57
+ if (tim && typeof tim.callExperimentalAPI === 'function') {
58
+ const reportData = {
59
+ id: options.id,
60
+ title: options.title,
61
+ type: options.type,
62
+ content: options.content,
63
+ platform: 'miniprogram',
64
+ };
65
+ tim.callExperimentalAPI('reportModalView', JSON.stringify(reportData));
66
+ }
67
+ } catch (error) {
68
+ console.warn('[UIKitModal] reportModalView failed:', error);
69
+ }
70
+ }
71
+
72
+ const createUIKitModal = (options: UIKitModalOptions): Promise<UIKitModalResult> => {
73
+ return new Promise((resolve) => {
74
+ reportModalView(options);
75
+
76
+ if (!isDevEnvironment()) {
77
+ resolve({ action: 'confirm' });
78
+ return;
79
+ }
80
+
81
+ const url = extractUrlFromContent(options.content);
82
+ const processedContent = processContent(options.content);
83
+ const hasLink = !!url;
84
+
85
+ uni.showModal({
86
+ title: options.title || '',
87
+ content: processedContent,
88
+ cancelText: '取消',
89
+ confirmText: hasLink ? '复制链接' : '确认',
90
+ success: (res) => {
91
+ const action = res.confirm ? 'confirm' : 'cancel';
92
+ if (res.confirm) {
93
+ if (hasLink && url) {
94
+ uni.setClipboardData({
95
+ data: url,
96
+ success: () => {
97
+ uni.showToast({ title: '链接已复制', icon: 'success' });
98
+ },
99
+ });
100
+ }
101
+ options.onConfirm?.();
102
+ } else {
103
+ options.onCancel?.();
104
+ }
105
+ resolve({ action, raw: res });
106
+ },
107
+ fail: () => {
108
+ options.onCancel?.();
109
+ resolve({ action: 'cancel' });
110
+ },
111
+ });
112
+ });
113
+ };
114
+
115
+ export const UIKitModal = {
116
+ openModal: (config: UIKitModalOptions) => createUIKitModal(config),
117
+ };
@@ -0,0 +1,2 @@
1
+ export { UIKitModal } from './UIKitModal';
2
+ export type { UIKitModalOptions, UIKitModalResult, ModalType } from './type';
@@ -0,0 +1,15 @@
1
+ export type ModalType = 'info' | 'warning' | 'error' | 'success';
2
+
3
+ export interface UIKitModalOptions {
4
+ id: number;
5
+ title: string;
6
+ content: string;
7
+ type: ModalType;
8
+ onConfirm?: () => void;
9
+ onCancel?: () => void;
10
+ }
11
+
12
+ export interface UIKitModalResult {
13
+ action: 'confirm' | 'cancel';
14
+ raw?: any;
15
+ }
@@ -135,7 +135,7 @@ export const en = {
135
135
  'accept-device-error': 'Accept failed, unable to auth calling device',
136
136
  'call-error': 'Start call failed',
137
137
  // 错误提示
138
- 'error.10001': "Your application has not enabled audio/video call (TUICallKit) capability. You can go to the <a href='https://console.cloud.tencent.com/im/detail'>console</a> to apply for a free trial or purchase call capability packages: <a href='https://buy.cloud.tencent.com/avc?addRavLicense=1&position=1600110000&ravLicenseType=1&regionId=1'>Purchase Page</a>",
138
+ 'error.10001': "Your application has not enabled audio/video call (TUICallKit) capability. For first-time use, you can go to <a href='https://cloud.tencent.com/document/product/647/104662'>Enable Service</a> to apply for a free trial, or purchase call capability packages: <a href='https://console.cloud.tencent.com/trtc'>Purchase Page</a>",
139
139
  'error.10002': "You do not currently support using this capability. Please go to the <a href='https://buy.cloud.tencent.com/avc?addRavLicense=1&position=1600110000&ravLicenseType=1&regionId=1'>purchase page</a> to buy and activate it",
140
140
  'error.10004': "Camera/microphone permission was denied. Please check browser settings to ensure camera and microphone access is allowed. If you have confirmed permission is granted, please go to <a href='https://web.sdk.qcloud.com/trtc/webrtc/demo/detect/index.html'>Audio/Video Capability Detection</a> to check if your device and environment support calls",
141
141
  'error.10005': "No microphone device detected. Please ensure your device has a microphone connected and check if the microphone is available in system or browser settings.",
@@ -144,5 +144,6 @@ export const en = {
144
144
  'error.10008': "TUICallEngine has not completed initialization or login. Please ensure you have successfully executed init or login operations before calling this function. Solution: <a href='https://cloud.tencent.com/document/product/647/78769#3a61f42b-e06f-49af-88bf-362d40025887'>View Documentation</a>",
145
145
  'error.10012': "Detected that the current page is under HTTP protocol. To ensure smooth access and full functionality of TUICallEngine SDK for production environment users, please use HTTPS protocol (or localhost) to access the audio/video application page. For details, please visit: <a href='https://web.sdk.qcloud.com/trtc/webrtc/v5/doc/zh-cn/tutorial-05-info-browser.html#h2-3'>View Documentation</a>",
146
146
  'error.10013': "Call failed: You have been blocked by the other party or you have blocked the other party",
147
+ 'error.10014': "Call initiation failed. User ID is invalid. Please confirm that the user is registered.",
147
148
  'error': 'Error',
148
149
  };
@@ -134,7 +134,7 @@ export const ja_JP = {
134
134
  'accept-device-error': '接続できませんでした。発信側デバイスを認証できません',
135
135
  'call-error': '通話が開始できませんでした',
136
136
  // 错误提示
137
- 'error.10001': "あなたのアプリケーションは音声・ビデオ通話(TUICallKit)機能を有効にしていません。<a href='https://console.cloud.tencent.com/im/detail'>コンソール</a>で無料体験を申請するか、通話機能パッケージを購入してください:<a href='https://buy.cloud.tencent.com/avc?addRavLicense=1&position=1600110000&ravLicenseType=1&regionId=1'>購入ページ</a>",
137
+ 'error.10001': "あなたのアプリケーションは音声・ビデオ通話(TUICallKit)機能を有効にしていません。初めてご利用の場合、<a href='https://cloud.tencent.com/document/product/647/104662'>サービスを有効にする</a>で無料体験を申請するか、通話機能パッケージを購入してください:<a href='https://console.cloud.tencent.com/trtc'>購入ページ</a>",
138
138
  'error.10002': "現在この機能を使用することはできません。<a href='https://buy.cloud.tencent.com/avc?addRavLicense=1&position=1600110000&ravLicenseType=1&regionId=1'>購入ページ</a>で購入して有効にしてください",
139
139
  'error.10004': "カメラ/マイクのアクセス許可が拒否されました。ブラウザ設定を確認し、カメラとマイクの使用が許可されていることを確認してください。許可されている場合は、<a href='https://web.sdk.qcloud.com/trtc/webrtc/demo/detect/index.html'>音声・ビデオ機能検出</a>にアクセスして、お使いのデバイスと環境が通話をサポートしているか確認してください",
140
140
  'error.10005': "マイクデバイスが検出されませんでした。デバイスにマイクが接続されていることを確認し、システムまたはブラウザ設定でマイクが利用可能かどうかを確認してください。",
@@ -143,5 +143,6 @@ export const ja_JP = {
143
143
  'error.10008': "TUICallEngineが初期化またはログインを完了していません。この機能を呼び出す前に、initまたはlogin操作が正常に実行されていることを確認してください。解決策:<a href='https://cloud.tencent.com/document/product/647/78769#3a61f42b-e06f-49af-88bf-362d40025887'>ドキュメントを確認</a>",
144
144
  'error.10012': "現在のページがHTTPプロトコル下にあることが検出されました。本番環境のユーザーがTUICallEngine SDKの全機能をスムーズに利用できるようにするため、音声・ビデオアプリケーションページにはHTTPSプロトコル(またはlocalhost)を使用してアクセスしてください。詳細については、<a href='https://web.sdk.qcloud.com/trtc/webrtc/v5/doc/zh-cn/tutorial-05-info-browser.html#h2-3'>ドキュメントを確認</a>してください",
145
145
  'error.10013': "通話失敗:相手にブロックされているか、または相手をブロックしています",
146
+ 'error.10014': "通話の開始に失敗しました。ユーザーIDが無効です。ユーザーが登録されていることを確認してください。",
146
147
  'error': 'エラー',
147
148
  };
@@ -130,7 +130,7 @@ export const zh = {
130
130
  'accept-device-error': '接通失败,通话设备获取失败',
131
131
  'call-error': '发起通话失败',
132
132
  // 错误提示
133
- 'error.10001': "您的应用还未开通音视频通话(TUICallKit)能力,您可以去<a href='https://console.cloud.tencent.com/im/detail'>控制台</a>申请免费体验,或购买通话能力套餐包:<a href='https://buy.cloud.tencent.com/avc?addRavLicense=1&position=1600110000&ravLicenseType=1&regionId=1'>购买页面</a>",
133
+ 'error.10001': "您的应用还未开通音视频通话(TUICallKit)能力,首次使用,您可以去<a href='https://cloud.tencent.com/document/product/647/104662'>开通服务</a>申请免费体验,或购买通话能力套餐包:<a href='https://console.cloud.tencent.com/trtc'>购买页面</a>",
134
134
  'error.10002': "您暂不支持使用该能力,请前往<a href='https://buy.cloud.tencent.com/avc?addRavLicense=1&position=1600110000&ravLicenseType=1&regionId=1'>购买页</a>购买开通",
135
135
  'error.10004': "摄像头/麦克风权限被拒绝,请检查浏览器设置,确保已允许使用您的摄像头和麦克风。如果您确认已授予权限,请前往<a href='https://web.sdk.qcloud.com/trtc/webrtc/demo/detect/index.html'>音视频能力检测</a>检查您的设备和环境是否支持通话",
136
136
  'error.10005': "未检测到麦克风设备。请确保您的设备已连接麦克风,并检查系统或浏览器设置中麦克风是否可用。",
@@ -139,5 +139,6 @@ export const zh = {
139
139
  'error.10008': "TUICallEngine 尚未完成初始化或登录。请确保在调用此功能前,已成功执行 init 或 login 操作。解决方案:<a href='https://cloud.tencent.com/document/product/647/78769#3a61f42b-e06f-49af-88bf-362d40025887'>查看文档</a>",
140
140
  'error.10012': "检测到当前页面正处于 http 协议下,为确保生产环境用户顺畅接入和体验 TUICallEngine SDK 的全部功能,请使用 https 协议(或 localhost)访问音视频应用页面。详情请前往:<a href='https://web.sdk.qcloud.com/trtc/webrtc/v5/doc/zh-cn/tutorial-05-info-browser.html#h2-3'>查看文档</a>",
141
141
  'error.10013': "呼叫失败:您已被对方拉黑或您拉黑了对方",
142
+ 'error.10014': "发起通话失败,用户 ID 无效,请确认该用户已注册。",
142
143
  'error': '错误',
143
144
  };
@@ -155,7 +155,7 @@ export function handleRepeatedCallError(error: any) {
155
155
  */
156
156
  export function handleNoDevicePermissionError(error: any) {
157
157
  const { message } = error;
158
- if (message.indexOf('NotAllowedError: Permission denied') !== -1) {
158
+ if (message?.indexOf('NotAllowedError: Permission denied') !== -1) {
159
159
  return true;
160
160
  }
161
161
  return false;
package/src/index.ts CHANGED
@@ -35,7 +35,7 @@ const TUICallType = {
35
35
  AUDIO_CALL: 1,
36
36
  VIDEO_CALL: 2,
37
37
  };
38
- const Version = '4.4.3-beta.1'; // basic-demo 原来上报使用
38
+ const Version = '4.4.3'; // basic-demo 原来上报使用
39
39
 
40
40
  // 输出产物
41
41
  export {