@trtc/calls-uikit-vue 4.5.3 → 4.5.4
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.
- package/package.json +1 -1
- package/src/Components/components/common/AutoPlayDialog/AutoPlayDialog.vue +2 -0
- package/src/TUICallService/CallService/engineEventHandler.ts +2 -0
- package/src/TUICallService/CallService/index.ts +5 -2
- package/src/TUICallService/CallService/miniProgram.ts +4 -1
- package/src/TUICallService/UIKitModal/UIKitModal.ts +12 -5
- package/src/TUICallService/utils/permission.ts +294 -0
- package/src/TUICallService/utils/validate/avoidRepeatedCall.ts +5 -1
- package/src/index.ts +1 -1
- package/stats.html +1 -1
- package/tuicall-uikit-vue.es.js +164 -162
- package/tuicall-uikit-vue.umd.js +2 -2
- package/types/TUICallService/utils/permission.d.ts +88 -0
- package/types/tsconfig.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -16,6 +16,7 @@ export default {
|
|
|
16
16
|
<script lang="ts" setup>
|
|
17
17
|
import { ref, onMounted, onUnmounted } from '../../../../adapter-vue';
|
|
18
18
|
import { TUIStore, StoreName, NAME, TUIGlobal } from '../../../../TUICallService/index';
|
|
19
|
+
import { TUICallKitAPI } from '../../../../TUICallService/index';
|
|
19
20
|
import { useTranslate } from '../../../hooks';
|
|
20
21
|
const t = useTranslate();
|
|
21
22
|
// The onAutoPlayFailed event is listened in CallService at initialization time and its result is
|
|
@@ -40,6 +41,7 @@ onUnmounted(() => {
|
|
|
40
41
|
|
|
41
42
|
const handleResumePlaybackClick = () => {
|
|
42
43
|
TUIStore.update(StoreName.CALL, NAME.IS_SHOW_AUTO_PLAY_DIALOG, false);
|
|
44
|
+
TUICallKitAPI?._autoPlayResume?.();
|
|
43
45
|
};
|
|
44
46
|
</script>
|
|
45
47
|
|
|
@@ -48,6 +48,7 @@ export default class EngineEventHandler {
|
|
|
48
48
|
callEngine.on(TUICallEvent.ON_USER_INVITING, this._handleOnUserInviting, this); // 通话存在邀请他人时, 通话里的所有人都会抛出
|
|
49
49
|
callEngine.on(TUICallEvent.SDK_READY, this._handleSDKReady, this); // SDK Ready 回调
|
|
50
50
|
callEngine.on(TUICallEvent.KICKED_OUT, this._handleKickedOut, this); // 未开启多端登录时, 多端登录收到的被踢事件
|
|
51
|
+
// @ts-ignore
|
|
51
52
|
callEngine.on(TUICallEvent.MESSAGE_SENT_BY_ME, this._messageSentByMe, this);
|
|
52
53
|
// @ts-ignore
|
|
53
54
|
TUICallEvent.CALL_MESSAGE && callEngine.on(TUICallEvent.CALL_MESSAGE, this._handleCallMessage, this); // call message card display event
|
|
@@ -75,6 +76,7 @@ export default class EngineEventHandler {
|
|
|
75
76
|
callEngine.off(TUICallEvent.ON_USER_INVITING, this._handleOnUserInviting, this);
|
|
76
77
|
callEngine.off(TUICallEvent.SDK_READY, this._handleSDKReady, this);
|
|
77
78
|
callEngine.off(TUICallEvent.KICKED_OUT, this._handleKickedOut, this);
|
|
79
|
+
// @ts-ignore
|
|
78
80
|
callEngine.off(TUICallEvent.MESSAGE_SENT_BY_ME, this._messageSentByMe, this);
|
|
79
81
|
// @ts-ignore
|
|
80
82
|
TUICallEvent.ON_USER_NETWORK_QUALITY_CHANGED && callEngine.off(TUICallEvent.ON_USER_NETWORK_QUALITY_CHANGED, this._handleNetworkQuality, this);
|
|
@@ -27,7 +27,7 @@ const TUIGlobal: ITUIGlobal = TuiGlobal.getInstance();
|
|
|
27
27
|
const TUIStore: ITUIStore = TuiStore.getInstance();
|
|
28
28
|
const uiDesign = UIDesign.getInstance();
|
|
29
29
|
uiDesign.setTUIStore(TUIStore);
|
|
30
|
-
const version = '4.5.
|
|
30
|
+
const version = '4.5.4';
|
|
31
31
|
import AIAssistant from './AIAssistant'; // 仅 web 支持 AI 实时字幕
|
|
32
32
|
const frameWork = 'vue3';
|
|
33
33
|
export { TUIGlobal, TUIStore, uiDesign };
|
|
@@ -52,10 +52,12 @@ export default class TUICallService {
|
|
|
52
52
|
private _wasmReadyResolve;
|
|
53
53
|
private _isInitialized = false;
|
|
54
54
|
// Use an arrow function to keep the `this` binding when registering/unregistering the listener.
|
|
55
|
-
private _handleAutoPlayFailed = () => {
|
|
55
|
+
private _handleAutoPlayFailed = (event: any) => {
|
|
56
56
|
this._tuiCallEngine?.reportLog?.({
|
|
57
57
|
name: 'TUICallkit.onAutoPlayFailed',
|
|
58
58
|
});
|
|
59
|
+
// @ts-ignore
|
|
60
|
+
this._autoPlayResume = event?.resume; // webrtc 自动恢复播放失败需要自己调用
|
|
59
61
|
TUIStore.update(StoreName.CALL, NAME.IS_SHOW_AUTO_PLAY_DIALOG, true);
|
|
60
62
|
};
|
|
61
63
|
|
|
@@ -116,6 +118,7 @@ export default class TUICallService {
|
|
|
116
118
|
frameWork,
|
|
117
119
|
// @ts-ignore
|
|
118
120
|
language: 6,
|
|
121
|
+
// @ts-ignore
|
|
119
122
|
callkitVersion: version,
|
|
120
123
|
isFromChat: isFromChat || false,
|
|
121
124
|
component,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CallMediaType, CallStatus } from '../const/index';
|
|
2
|
+
import { checkDevicePermission } from '../utils/permission';
|
|
2
3
|
|
|
3
4
|
export function initialUI() {
|
|
4
5
|
// 收起键盘
|
|
@@ -34,7 +35,9 @@ export async function beforeCall(type: CallMediaType, that: any) {
|
|
|
34
35
|
microphone: true,
|
|
35
36
|
camera: type === CallMediaType.VIDEO,
|
|
36
37
|
};
|
|
37
|
-
|
|
38
|
+
// `deviceCheck` is not implemented by @trtc/call-engine-lite-wx, so use the
|
|
39
|
+
// mini-program permission utility to pre-check microphone/camera access.
|
|
40
|
+
const hasDevicePermission = await checkDevicePermission(deviceMap); // miniProgram 检查设备权限
|
|
38
41
|
return hasDevicePermission ? CallStatus.CALLING : CallStatus.IDLE;
|
|
39
42
|
} catch (error) {
|
|
40
43
|
console.debug(error);
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import type { UIKitModalOptions, UIKitModalResult } from './type';
|
|
3
|
-
|
|
3
|
+
// Use the explicit `/index` path so the WeChat mini-program `require`, which
|
|
4
|
+
// only appends `.js` and does not resolve a directory to its `index.js`, can
|
|
5
|
+
// locate the module.
|
|
6
|
+
import TUICallKitServer from '../CallService/index';
|
|
7
|
+
|
|
8
|
+
// Native WeChat mini-program only exposes `wx`, while uniapp exposes `uni`.
|
|
9
|
+
// Fall back to `wx` when `uni` is not defined so the modal works in both.
|
|
10
|
+
const platform: any = typeof uni !== 'undefined' ? uni : wx;
|
|
4
11
|
|
|
5
12
|
const URL_REGEX = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/i;
|
|
6
13
|
const FULL_URL_REGEX = /(https?:\/\/[^\s]+)/g;
|
|
7
14
|
|
|
8
15
|
function isDevEnvironment(): boolean {
|
|
9
|
-
const accountInfo =
|
|
16
|
+
const accountInfo = platform.getAccountInfoSync?.();
|
|
10
17
|
const envVersion = accountInfo?.miniProgram?.envVersion;
|
|
11
18
|
return envVersion === 'develop' || envVersion === 'trial';
|
|
12
19
|
}
|
|
@@ -82,7 +89,7 @@ const createUIKitModal = (options: UIKitModalOptions): Promise<UIKitModalResult>
|
|
|
82
89
|
const processedContent = processContent(options.content);
|
|
83
90
|
const hasLink = !!url;
|
|
84
91
|
|
|
85
|
-
|
|
92
|
+
platform.showModal({
|
|
86
93
|
title: options.title || '',
|
|
87
94
|
content: processedContent,
|
|
88
95
|
cancelText: '取消',
|
|
@@ -91,10 +98,10 @@ const createUIKitModal = (options: UIKitModalOptions): Promise<UIKitModalResult>
|
|
|
91
98
|
const action = res.confirm ? 'confirm' : 'cancel';
|
|
92
99
|
if (res.confirm) {
|
|
93
100
|
if (hasLink && url) {
|
|
94
|
-
|
|
101
|
+
platform.setClipboardData({
|
|
95
102
|
data: url,
|
|
96
103
|
success: () => {
|
|
97
|
-
|
|
104
|
+
platform.showToast({ title: '链接已复制', icon: 'success' });
|
|
98
105
|
},
|
|
99
106
|
});
|
|
100
107
|
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeChat mini-program permission utility.
|
|
3
|
+
*
|
|
4
|
+
* NOTE: This module is only referenced inside `// @if process.env.BUILD_TARGET='MINI'`
|
|
5
|
+
* conditional-compilation blocks, so it will be tree-shaken out of the web builds.
|
|
6
|
+
* `wx` global typings are not available in this package, therefore `// @ts-ignore`
|
|
7
|
+
* is used before each `wx.*` call, following the same convention as `miniProgram.ts`.
|
|
8
|
+
*
|
|
9
|
+
* Distinguishes two levels of permission denial:
|
|
10
|
+
* 1. Mini-program level: the user has rejected the scope in this mini-program.
|
|
11
|
+
* Fix: call wx.openSetting to let the user toggle it.
|
|
12
|
+
* 2. System level: WeChat itself lacks the OS-level permission (Settings >
|
|
13
|
+
* Privacy > Microphone / Camera on iOS, App Permissions on Android).
|
|
14
|
+
* Fix: guide the user to the system settings page (cannot be opened
|
|
15
|
+
* programmatically).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// ── Permission Scope Constants ──
|
|
19
|
+
export const PermissionScope = {
|
|
20
|
+
RECORD: 'scope.record',
|
|
21
|
+
CAMERA: 'scope.camera',
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
// ── Result Types ──
|
|
25
|
+
export interface PermissionCheckResult {
|
|
26
|
+
/** Whether the permission is granted. */
|
|
27
|
+
granted: boolean;
|
|
28
|
+
/** Human-readable tip shown to the user. */
|
|
29
|
+
tip: string;
|
|
30
|
+
/**
|
|
31
|
+
* Denial level:
|
|
32
|
+
* - 'mini-program': rejected within this mini-program → wx.openSetting works.
|
|
33
|
+
* - 'system': WeChat lacks the OS permission → user must go to system settings.
|
|
34
|
+
* - 'granted': already authorized.
|
|
35
|
+
*/
|
|
36
|
+
level: 'granted' | 'mini-program' | 'system';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── Helpers ──
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get the raw authSetting snapshot from WeChat.
|
|
43
|
+
*/
|
|
44
|
+
function getAuthSetting(): Promise<any> {
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
// @ts-ignore
|
|
47
|
+
wx.getSetting({
|
|
48
|
+
success(res: any) {
|
|
49
|
+
resolve(res.authSetting);
|
|
50
|
+
},
|
|
51
|
+
fail() {
|
|
52
|
+
resolve({});
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Attempt to authorize a scope via wx.authorize.
|
|
60
|
+
*/
|
|
61
|
+
function authorizeScope(
|
|
62
|
+
scope: string,
|
|
63
|
+
): Promise<{ success: boolean; errMsg: string }> {
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
// @ts-ignore
|
|
66
|
+
wx.authorize({
|
|
67
|
+
scope,
|
|
68
|
+
success() {
|
|
69
|
+
resolve({ success: true, errMsg: '' });
|
|
70
|
+
},
|
|
71
|
+
fail(err: any) {
|
|
72
|
+
resolve({ success: false, errMsg: err.errMsg || '' });
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check whether a scope has been explicitly denied by the user at the
|
|
80
|
+
* mini-program level (authSetting returns false for that key).
|
|
81
|
+
*/
|
|
82
|
+
function isMiniProgramDenied(authSetting: any, scope: string): boolean {
|
|
83
|
+
return authSetting[scope] === false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Public API ──
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check and request microphone permission.
|
|
90
|
+
*
|
|
91
|
+
* - No camera permission → calls can still proceed (video call falls back to audio).
|
|
92
|
+
* - No microphone permission → calls MUST be blocked; user is guided to settings.
|
|
93
|
+
*
|
|
94
|
+
* The result's `level` field tells you whether the permission is already
|
|
95
|
+
* granted, denied at the mini-program level (wx.openSetting can fix it),
|
|
96
|
+
* or denied at the system level (user must go to OS settings manually).
|
|
97
|
+
*/
|
|
98
|
+
export async function checkMicrophonePermission(): Promise<PermissionCheckResult> {
|
|
99
|
+
const authSetting = await getAuthSetting();
|
|
100
|
+
|
|
101
|
+
// Already granted
|
|
102
|
+
if (authSetting[PermissionScope.RECORD] === true) {
|
|
103
|
+
return {
|
|
104
|
+
granted: true,
|
|
105
|
+
tip: '',
|
|
106
|
+
level: 'granted',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Mini-program level: user has explicitly rejected the scope before.
|
|
111
|
+
if (isMiniProgramDenied(authSetting, PermissionScope.RECORD)) {
|
|
112
|
+
return {
|
|
113
|
+
granted: false,
|
|
114
|
+
tip: '您已拒绝麦克风权限,请前往小程序设置页面开启麦克风权限后重试',
|
|
115
|
+
level: 'mini-program',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Scope is undefined — never requested. Try to authorize.
|
|
120
|
+
const result = await authorizeScope(PermissionScope.RECORD);
|
|
121
|
+
|
|
122
|
+
if (result.success) {
|
|
123
|
+
return { granted: true, tip: '', level: 'granted' };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Authorization failed.
|
|
127
|
+
// If the native permission dialog was never shown (user actively cancelled
|
|
128
|
+
// or system-level block), errMsg typically contains "auth deny".
|
|
129
|
+
// When the OS-level permission is off, wx.authorize fails immediately
|
|
130
|
+
// without showing the dialog — we treat this as a system-level issue.
|
|
131
|
+
const isSystemLevelDenial =
|
|
132
|
+
!result.errMsg.includes('cancel') &&
|
|
133
|
+
!result.errMsg.includes('deny');
|
|
134
|
+
|
|
135
|
+
// Heuristic: if the error message suggests the dialog did NOT appear,
|
|
136
|
+
// it is very likely a system-level block.
|
|
137
|
+
if (isSystemLevelDenial || result.errMsg.includes('system')) {
|
|
138
|
+
return {
|
|
139
|
+
granted: false,
|
|
140
|
+
tip: '您的微信没有麦克风权限,请前往手机系统设置 > 微信,开启麦克风权限后重试',
|
|
141
|
+
level: 'system',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// User dismissed the native dialog or denied it → mini-program level.
|
|
146
|
+
return {
|
|
147
|
+
granted: false,
|
|
148
|
+
tip: '您拒绝了麦克风权限,请前往小程序设置页面开启麦克风权限后重试',
|
|
149
|
+
level: 'mini-program',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Check camera permission.
|
|
155
|
+
*
|
|
156
|
+
* Camera permission is REQUIRED for video calls — a denial MUST block the
|
|
157
|
+
* call (both for initiating and for answering). For audio calls this check
|
|
158
|
+
* is irrelevant and should be skipped by the caller.
|
|
159
|
+
*
|
|
160
|
+
* Tip wording here reflects the blocking policy: it tells the user that
|
|
161
|
+
* video calls cannot proceed without camera permission, and points them to
|
|
162
|
+
* the appropriate settings page (mini-program vs. system level).
|
|
163
|
+
*/
|
|
164
|
+
export async function checkCameraPermission(): Promise<PermissionCheckResult> {
|
|
165
|
+
const authSetting = await getAuthSetting();
|
|
166
|
+
|
|
167
|
+
if (authSetting[PermissionScope.CAMERA] === true) {
|
|
168
|
+
return { granted: true, tip: '', level: 'granted' };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (isMiniProgramDenied(authSetting, PermissionScope.CAMERA)) {
|
|
172
|
+
return {
|
|
173
|
+
granted: false,
|
|
174
|
+
tip: '您已拒绝摄像头权限,无法发起或接听视频通话,请前往小程序设置页面开启摄像头权限后重试',
|
|
175
|
+
level: 'mini-program',
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const result = await authorizeScope(PermissionScope.CAMERA);
|
|
180
|
+
|
|
181
|
+
if (result.success) {
|
|
182
|
+
return { granted: true, tip: '', level: 'granted' };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const isSystemLevelDenial =
|
|
186
|
+
!result.errMsg.includes('cancel') &&
|
|
187
|
+
!result.errMsg.includes('deny');
|
|
188
|
+
|
|
189
|
+
if (isSystemLevelDenial || result.errMsg.includes('system')) {
|
|
190
|
+
return {
|
|
191
|
+
granted: false,
|
|
192
|
+
tip: '您的微信没有摄像头权限,无法发起或接听视频通话,请前往手机系统设置 > 微信,开启摄像头权限后重试',
|
|
193
|
+
level: 'system',
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
granted: false,
|
|
199
|
+
tip: '您拒绝了摄像头权限,无法发起或接听视频通话,请前往小程序设置页面开启摄像头权限后重试',
|
|
200
|
+
level: 'mini-program',
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Handle a failed permission check by showing the appropriate dialog and
|
|
206
|
+
* optionally navigating to the settings page.
|
|
207
|
+
*
|
|
208
|
+
* Resolves with `{ confirm }` describing the user's choice:
|
|
209
|
+
* - confirm === true : the user clicked the primary button (前往设置 for the
|
|
210
|
+
* mini-program level, or 我知道了 for the system level).
|
|
211
|
+
* - confirm === false: the user cancelled the dialog (only possible for the
|
|
212
|
+
* mini-program level, which shows a 取消 button).
|
|
213
|
+
*
|
|
214
|
+
* Callers can use this to decide follow-up behavior, e.g. a callee rejecting
|
|
215
|
+
* the incoming call when the user cancels the mandatory permission dialog.
|
|
216
|
+
*/
|
|
217
|
+
export function handlePermissionDenied(result: PermissionCheckResult): Promise<{ confirm: boolean }> {
|
|
218
|
+
return new Promise((resolve) => {
|
|
219
|
+
if (result.level === 'mini-program') {
|
|
220
|
+
// Mini-program level: open the mini-program settings page.
|
|
221
|
+
// @ts-ignore
|
|
222
|
+
wx.showModal({
|
|
223
|
+
title: '权限提示',
|
|
224
|
+
content: result.tip,
|
|
225
|
+
confirmText: '前往设置',
|
|
226
|
+
cancelText: '取消',
|
|
227
|
+
success(modalRes: any) {
|
|
228
|
+
if (modalRes.confirm) {
|
|
229
|
+
// @ts-ignore
|
|
230
|
+
wx.openSetting({
|
|
231
|
+
complete() {
|
|
232
|
+
resolve({ confirm: true });
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
} else {
|
|
236
|
+
resolve({ confirm: false });
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
fail() {
|
|
240
|
+
resolve({ confirm: false });
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
} else {
|
|
244
|
+
// System level: can only guide the user, cannot open system settings.
|
|
245
|
+
// @ts-ignore
|
|
246
|
+
wx.showModal({
|
|
247
|
+
title: '权限提示',
|
|
248
|
+
content: result.tip,
|
|
249
|
+
showCancel: false,
|
|
250
|
+
confirmText: '我知道了',
|
|
251
|
+
success() {
|
|
252
|
+
resolve({ confirm: true });
|
|
253
|
+
},
|
|
254
|
+
fail() {
|
|
255
|
+
resolve({ confirm: false });
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Combined device-permission pre-check used as the mini-program replacement
|
|
264
|
+
* for `tuiCallEngine.deviceCheck(deviceMap)`.
|
|
265
|
+
*
|
|
266
|
+
* - Microphone is mandatory for every call (audio and video).
|
|
267
|
+
* - Camera is mandatory only for video calls (`deviceMap.camera === true`).
|
|
268
|
+
*
|
|
269
|
+
* When a required permission is missing, the corresponding guidance dialog is
|
|
270
|
+
* shown (via `handlePermissionDenied`) and `false` is returned so the caller
|
|
271
|
+
* can abort / postpone the call. Returns `true` only when all required
|
|
272
|
+
* permissions are granted.
|
|
273
|
+
*/
|
|
274
|
+
export async function checkDevicePermission(
|
|
275
|
+
deviceMap: { microphone?: boolean; camera?: boolean } = {},
|
|
276
|
+
): Promise<boolean> {
|
|
277
|
+
if (deviceMap.microphone) {
|
|
278
|
+
const micResult = await checkMicrophonePermission();
|
|
279
|
+
if (!micResult.granted) {
|
|
280
|
+
await handlePermissionDenied(micResult);
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (deviceMap.camera) {
|
|
286
|
+
const cameraResult = await checkCameraPermission();
|
|
287
|
+
if (!cameraResult.granted) {
|
|
288
|
+
await handlePermissionDenied(cameraResult);
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
@@ -17,6 +17,10 @@ export function avoidRepeatedCall() {
|
|
|
17
17
|
if (isCallingSet.has(this)) {
|
|
18
18
|
console.warn((`${NAME.PREFIX}previous ${name}() is ongoing, please avoid repeated calls`));
|
|
19
19
|
// throw new Error(`previous ${name}() is ongoing, please avoid repeated calls`);
|
|
20
|
+
// Guard the method call itself: some classes decorated with
|
|
21
|
+
// `avoidRepeatedCall` (e.g. CallManager) do not implement
|
|
22
|
+
// `getTUICallEngineInstance`. Using `?.()` short-circuits when the
|
|
23
|
+
// method is absent instead of throwing "is not a function".
|
|
20
24
|
this?.getTUICallEngineInstance()?.reportLog?.({
|
|
21
25
|
name: 'TUICallKit.avoidRepeatedCall.fail',
|
|
22
26
|
data: { name },
|
|
@@ -38,7 +42,7 @@ export function avoidRepeatedCall() {
|
|
|
38
42
|
descriptor.value.clearCallState = function(instance: any) {
|
|
39
43
|
isCallingSet.delete(instance);
|
|
40
44
|
};
|
|
41
|
-
|
|
45
|
+
|
|
42
46
|
return descriptor;
|
|
43
47
|
};
|
|
44
48
|
}
|