Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions miniprogram/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,21 @@ App<IAppOption>({
hostUserId: '',
},
onLaunch() {
// 展示本地存储能力
const logs = wx.getStorageSync('logs') || [];
logs.unshift(Date.now());
wx.setStorageSync('logs', logs);

// 登录
wx.login({
success: res => {
logger.log('App', res.code);
// 发送 res.code 到后台换取 openId, sessionKey, unionId
},
});

// 隐私授权拦截器(微信 2023 年 9 月新规)
// chat-room 页面已在弹窗中取得用户明确同意后才调用 wx.requirePrivacyAuthorize,
// 此处直接 resolve('agree') 即可完成 WeChat 侧的授权登记。
wx.onNeedPrivacyAuthorization(resolve => {
resolve({ event: 'agree', buttonId: 'privacy-agree' });
});
},
});
114 changes: 93 additions & 21 deletions miniprogram/packageB/pages/chat-room/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -786,12 +786,15 @@ Page<IChatRoomPageData, IChatRoomCustomOption>({
onMicPermissionGranted(): void {
this.setData({ hasMicPermission: true });

// 启动倒计时
// 授权完成时按钮可能已松开(auth 流中已提前重置 micPressed)
// 仅在仍按住的情况下才启动录音,否则等用户重新按下
if (!this.data.micPressed) {
return;
}

if (!this.timerId) {
this.startTimer();
}

// 震动反馈并开始录音
void wx.vibrateShort({ type: 'light' });
this.startRecording();
},
Expand Down Expand Up @@ -1089,43 +1092,111 @@ Page<IChatRoomPageData, IChatRoomCustomOption>({

/**
* 麦克风按下
* 首次按下时在用户手势上下文中直接请求权限,避免经过 getSetting 异步链。
* 正式版中 wx.authorize 必须在手势上下文内发起,否则授权弹窗不会出现。
* 首次按下时先完成隐私授权,再申请 scope.record 权限。
*/
onMicTouchStart(): void {
if (!this.data.canSpeak) {
return;
}

// 手指按下:立即进入"连接中"状态
this.setData({ micPressed: true });

if (this.data.hasMicPermission) {
// 已有权限,直接开始录音
if (!this.timerId) {
this.startTimer();
}
void wx.vibrateShort({ type: 'light' });
this.startRecording();
} else if (this.micPermissionDenied) {
// 已知被拒绝,直接引导去设置(无需再调 wx.authorize)
this.showMicPermissionDeniedModal();
} else {
// 未请求过:在用户手势上下文中直接调用 wx.authorize
// 不经过 wx.getSetting 的异步链,确保正式版授权弹窗正常弹出
wx.authorize({
scope: 'scope.record',
success: () => {
this.onMicPermissionGranted();
},
fail: () => {
this.micPermissionDenied = true;
this.showMicPermissionDeniedModal();
},
});
this.setData({ micPressed: false });
this.requestRecordPermission();
}
},

/**
* 四步授权流程:
* ① 检查是否需要隐私授权(wx.getPrivacySetting)
* ② 需要则弹出隐私协议弹窗,用户点同意后继续
* ③ 调用 wx.requirePrivacyAuthorize 完成 WeChat 侧授权登记
* ④ 调用 wx.authorize({scope:'scope.record'}) 申请麦克风权限
*/
requestRecordPermission(): void {
wx.getPrivacySetting({
success: res => {
if (res.needAuthorization) {
void wx.showModal({
title: '隐私保护提示',
content: `本小程序需要使用麦克风进行语音识别,请阅读并同意《${res.privacyContractName}》。`,
confirmText: '同意',
cancelText: '拒绝',
success: modalRes => {
if (!modalRes.confirm) {
return;
}
wx.requirePrivacyAuthorize({
success: () => {
this.doAuthorizeRecord();
},
fail: () => {
void wx.showToast({
title: '隐私授权失败,请再次按住麦克风以授权录音',
icon: 'none',
duration: 2000,
});
},
});
},
});
} else {
this.doAuthorizeRecord();
}
},
fail: () => {
void wx.showToast({
title: '请再次按住麦克风以授权录音',
icon: 'none',
duration: 2000,
});
},
});
},

/**
* 申请 scope.record 并处理结果
* scope.record === false → 用户明确拒绝 → 引导去小程序设置
* scope.record === undefined → 用户划掉弹窗 → 提示重试,不锁定状态
*/
doAuthorizeRecord(): void {
wx.authorize({
scope: 'scope.record',
success: () => {
this.onMicPermissionGranted();
},
fail: () => {
wx.getSetting({
success: settingRes => {
if (settingRes.authSetting['scope.record'] === false) {
this.micPermissionDenied = true;
this.showMicPermissionDeniedModal();
} else {
void wx.showToast({
title: '请再次按住麦克风以授权录音',
icon: 'none',
duration: 2000,
});
}
},
fail: () => {
this.micPermissionDenied = true;
this.showMicPermissionDeniedModal();
},
});
},
});
},

/**
* 麦克风松开
*/
Expand Down Expand Up @@ -1227,7 +1298,8 @@ Page<IChatRoomPageData, IChatRoomCustomOption>({
logger.log('ChatRoom', 'Got STS credentials');

// 使用临时凭证启动 ASR
if (this.asrManager) {
// STS 请求是异步的,期间用户可能已松开按钮,此时不再建立连接
if (this.asrManager && this.data.micPressed) {
this.asrManager.start({
secretkey: credentials.TmpSecretKey,
secretid: credentials.TmpSecretId,
Expand Down
30 changes: 30 additions & 0 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,34 @@ interface IAppOption {
hostUserId: string;
}
userInfoReadyCallback?: WechatMiniprogram.GetUserInfoSuccessCallback,
}

// 微信隐私保护 API(基础库 2.32.3+,miniprogram-api-typings@2.8.3 暂未收录)
declare namespace WechatMiniprogram {
interface IPrivacyResolveOption {
event: 'agree' | 'disagree';
buttonId?: string;
}
interface IGetPrivacySettingSuccessResult {
needAuthorization: boolean;
privacyContractName: string;
}
interface Wx {
onNeedPrivacyAuthorization(
callback: (
resolve: (opts: IPrivacyResolveOption) => void,
eventInfo: { privacyContractName: string },
) => void,
): void;
getPrivacySetting(opts: {
success?: (res: IGetPrivacySettingSuccessResult) => void;
fail?: (err: GeneralCallbackResult) => void;
complete?: (res: GeneralCallbackResult) => void;
}): void;
requirePrivacyAuthorize(opts?: {
success?: () => void;
fail?: (err: GeneralCallbackResult) => void;
complete?: (res: GeneralCallbackResult) => void;
}): void;
}
}
Loading