feat(share): ShareProvider 去SDK化三段式分享 + WeChatApi 仅留登录(分享重构 §4/§5/§7)

- WeChatApi:移除 SendMessageToWXResp/SendMessageToWXReq/WXMediaMessage/WXWebpageObject/WXImageObject/ErrCode/shareCb/sendShare;仅保留登录路径(sendAuth/handleWant/SendAuthResp)
- ShareProvider:完全去 wechat_open_sdk 分享依赖,改「剪贴板+ShareGuideDialog→系统分享」三段式架构;保留 FriendsShare 注册、PHOTO_UPLOAD 链路、captureCanvas 截图、systemShare 兜底、乐观回传 sharesuccess;新增 type3 图片链接下载路径

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
lanterngamescn
2026-06-27 14:04:13 +08:00
co-authored by Claude Sonnet 4.6
parent 15fadd7cdb
commit e93d0627d4
2 changed files with 206 additions and 251 deletions
@@ -1,45 +1,45 @@
/**
* 分享能力(契约 §8.1/§10.1T-M3-13)。对齐原 Android「自定义三按钮分享面板」(微信/QQ/抖音)
* 分享能力(契约 §8.1/§10.1)。三端统一「剪贴板+指引窗 → 系统分享」,**不依赖任何分享 SDK**
*
* H5 调 friendsSharetypeUrlToptitleDescriptdata=sharetypeBean JSON)→ 弹自定义面板(entry 容器渲染)。
* 用户点平台后经一次性 resultEvent 回投本 Provider
* - 微信(@tencent/wechat_open_sdk):
* type=="2" → Canvas 截图图片分享(截当前激活 Web 的 canvas.toDataURL → 落盘 → WXImageObject.uri);
* 其他 type → 网页分享(WXWebpageObject)。
* scenesharefriend=="1" → 好友(WXSceneSession),否则 → 朋友圈(WXSceneTimeline)。
* 分享结果回传 H5callHandler('sharesuccess', {success:2成功/3取消, type:1好友/2朋友圈})
* - QQ / 抖音(不集成对应 SDK,用 HarmonyOS 系统分享 @kit.ShareKit,让用户在系统面板选目标 app):
* type=="1" → 文本(title+description)type=="2" → 截图图片;
* type=="4"(抖音视频) → 视频(webpageUrl 为视频地址)else → 网页链接(webpageUrl)。**不回传 sharesuccess**。
* H5 调 friendsSharetypeUrlToptitleDescript(data=sharetypeBean JSON)
* - sharefriend=="2" → 直接微信指引窗(朋友圈语义,回传 type=2)。
* - sharefriend=="1" → 弹自定义面板(微信/QQ/抖音/取消) → 选定渠道。
* 选定渠道后按内容类型组装并写剪贴板,再弹 ShareGuideDialog
* - 文本(type1/4/其他):剪贴板 PLAIN_TEXT(title\ndescription,无 url)
* - 图片(type2 截图 / type3 图片链接下载,低优先):剪贴板 PIXELMAP(尽力)。
* 指引窗动作:打开App(openLink schemebest-effort) / 用系统分享(systemText|systemImage,可靠) / 取消
* 仅微信乐观回传 sharesuccess:打开/系统分享→{success:2,type};取消→{success:3,type}。QQ/抖音不回传。
*
* ⚠ 微信运行期成功需在微信开放平台注册本 HarmonyOS 应用(bundleName+指纹 绑定 AppID)。
* 截图/系统分享拉起/微信分享 均只能真机验证
* 注:图片"复制剪贴板再去对方App粘贴"能否成功取决于对方是否读 PIXELMAP(鸿蒙不可保证),
* 故指引窗"用系统分享"(systemImage) 为可靠兜底;拉起 App 仅打开、不预填(需用户自行粘贴)
*/
import { SendMessageToWXReq, WXMediaMessage, WXWebpageObject, WXImageObject, ErrCode } from '@tencent/wechat_open_sdk';
import { systemShare } from '@kit.ShareKit';
import { uniformTypeDescriptor as utd } from '@kit.ArkData';
import { fileUri } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common, bundleManager } from '@kit.AbilityKit';
import { BusinessError, pasteboard } from '@kit.BasicServicesKit';
import { image } from '@kit.ImageKit';
import { util } from '@kit.ArkTS';
import { BridgeController } from 'feature_bridge';
import { InboundHandlers, OutboundHandlers, SharetypeBean, ShareSuccessResp } from 'contracts';
import { EventBus, Logger, ShareEvents, SharePanelRequest, SharePanelResult, PhotoUploadPayload } from 'common';
import { FileSystem } from 'platform';
import {
EventBus, Logger, ShareEvents,
SharePanelRequest, SharePanelResult, ShareGuideRequest, ShareGuideResult, PhotoUploadPayload,
} from 'common';
import { FileSystem, Downloader } from 'platform';
import { CapabilityContext, CapabilityProvider } from '../core/CapabilityProvider';
import { WeChatApi } from '../wx/WeChatApi';
import { buildShareText } from '../share/ShareText';
export class ShareProvider implements CapabilityProvider {
/** 进程内实例计数:双 Web 槽各一个 ShareProvider 实例,用它给 resultEvent 命名空间隔离。 */
/** 进程内实例计数:双 Web 槽各一个实例,用它给 resultEvent 命名空间隔离。 */
private static instanceCount: number = 0;
readonly name: string = 'share';
private readonly log: Logger = Logger.tag('ShareProvider');
private readonly instanceId: number;
private bridge: BridgeController | undefined = undefined;
private ctx: CapabilityContext | undefined = undefined;
/** 截图落盘 + resultEvent 命名计数器(实例自增,避免 Math.random 串扰)。 */
/** 落盘 + resultEvent 命名计数器(实例自增,避免 Math.random 串扰)。 */
private seq: number = 0;
/** PHOTO_UPLOAD 订阅退订函数(onDestroy 调用,防双槽实例泄漏/串扰)。 */
private photoUploadCancel: (() => void) | undefined = undefined;
constructor() {
@@ -53,11 +53,10 @@ export class ShareProvider implements CapabilityProvider {
bridge.registerHandler(InboundHandlers.FriendsShare, (data: string, _cb: (resp: string) => void) => {
this.onShare(data);
});
// 第二条链路:H5 主动 POST 截图到 LocalUploadServer → emit PHOTO_UPLOAD → 微信图片分享
// 第二条链路:H5 主动 POST 截图到 LocalUploadServer → emit PHOTO_UPLOAD → 微信图片指引窗
this.photoUploadCancel = EventBus.on(ShareEvents.PHOTO_UPLOAD, (p) => this.onPhotoUpload(p));
}
/** 容器销毁:退订 PHOTO_UPLOAD(双槽各一实例,须 per-subscriber 退订,勿 offAll)。 */
onDestroy(): void {
if (this.photoUploadCancel !== undefined) {
this.photoUploadCancel();
@@ -65,49 +64,10 @@ export class ShareProvider implements CapabilityProvider {
}
}
/**
* H5 POST 截图上传分享:仅当本槽桥激活时处理(非激活槽忽略,避免双实例对同一上传重复分享)。
* type 语义同 sharefriend"1" 好友 → WXSceneSession/回传 type=1;其他 → 朋友圈/回传 type=2。
* 落盘成功走微信图片分享;微信未装 reportResult(3, ...);落盘失败仅 log(无网页可降级)。
*/
private onPhotoUpload(payload?: Object): void {
const ctx = this.ctx;
const bridge = this.bridge;
if (ctx === undefined || bridge === undefined) {
return;
}
if (!bridge.isActive()) {
return; // 非激活槽不处理,保证同一上传仅激活槽分享一次
}
if (payload === undefined) {
return;
}
const up = payload as PhotoUploadPayload;
if (up.imageBase64 === '') {
this.log.w('photo upload empty image');
return;
}
const friendType: number = up.type === '1' ? 1 : 2;
const wx: WeChatApi = WeChatApi.getInstance();
if (!wx.isWXInstalled()) {
this.log.i('WeChat not installed (photo upload); report cancel');
this.reportResult(3, friendType);
return;
}
// saveDataUrlToFile 对无 data: 前缀的纯 base64 也兼容(indexOf(',')<0 → 整串当 base64
const filePath: string = this.saveDataUrlToFile(ctx.uiAbilityContext, up.imageBase64);
if (filePath === '') {
this.log.w('photo upload save failed');
this.reportResult(3, friendType);
return;
}
this.wechatImageByFriend(ctx.uiAbilityContext, wx, filePath, friendType);
}
// —— 入站 ——
/** 入站:校验 bean → 发起面板请求(一对一回投)。 */
private onShare(data: string): void {
const ctx = this.ctx;
if (ctx === undefined) {
if (this.ctx === undefined) {
return;
}
let bean: SharetypeBean;
@@ -121,6 +81,11 @@ export class ShareProvider implements CapabilityProvider {
this.log.w('share bean empty');
return;
}
if (bean.sharefriend === '2') {
// 朋友圈语义:不弹面板,直接微信指引窗(契约 §10.1)。
this.startShare(bean, 'wechat');
return;
}
this.seq += 1;
const resultEvent: string = `share.result.${this.instanceId}.${this.seq}`;
EventBus.once(resultEvent, (p) => this.onPlatformChosen(bean, p));
@@ -128,189 +93,217 @@ export class ShareProvider implements CapabilityProvider {
EventBus.emit(ShareEvents.SHOW_PANEL, req);
}
/** 用户在面板选定平台后回投。 */
private onPlatformChosen(bean: SharetypeBean, payload?: Object): void {
const platform: string = payload !== undefined ? (payload as SharePanelResult).platform : 'cancel';
if (platform === 'wechat') {
this.shareToWeChat(bean);
} else if (platform === 'qq') {
this.shareViaSystem(bean, 'qq');
} else if (platform === 'douyin') {
this.shareViaSystem(bean, 'douyin');
if (platform === 'wechat' || platform === 'qq' || platform === 'douyin') {
this.startShare(bean, platform);
}
// cancel:不分享、不回传(对齐 Android 点空白关闭面板)
// cancel:不分享、不回传。
}
// —— 微信 ——
/** sharefriend=="1" → 好友(回传 type=1);否则 → 朋友圈(回传 type=2)。对齐 Android scene 语义。 */
private wechatReportType(bean: SharetypeBean): number {
return bean.sharefriend === '1' ? 1 : 2;
/** H5 POST 截图上传:仅激活槽处理,落盘后走微信图片指引窗。 */
private onPhotoUpload(payload?: Object): void {
const ctx = this.ctx;
const bridge = this.bridge;
if (ctx === undefined || bridge === undefined || !bridge.isActive() || payload === undefined) {
return;
}
const up = payload as PhotoUploadPayload;
if (up.imageBase64 === '') {
this.log.w('photo upload empty image');
return;
}
const bean: SharetypeBean = {
sharefriend: up.type, type: '2', sharetype: '', webpageUrl: '', title: '', description: '',
};
const filePath: string = this.saveDataUrlToFile(ctx.uiAbilityContext, up.imageBase64);
if (filePath === '') {
this.log.w('photo upload save failed; report cancel');
this.reportResult(3, up.type === '1' ? 1 : 2);
return;
}
this.onImageReady(bean, 'wechat', filePath);
}
private shareToWeChat(bean: SharetypeBean): void {
// —— 组装 + 弹指引窗 ——
private startShare(bean: SharetypeBean, platform: string): void {
if (bean.type === '2') {
this.startCanvasImageShare(bean, platform);
} else if (bean.type === '3') {
this.startLinkImageShare(bean, platform);
} else {
this.startTextShare(bean, platform);
}
}
private startTextShare(bean: SharetypeBean, platform: string): void {
this.writeClipboardText(buildShareText(bean.title, bean.description));
this.guide(bean, platform, 'text', '');
}
/** type2:截当前 Web canvas → 落盘 → 图片指引窗(捕获失败降级文本指引,非"图片转文本"语义)。 */
private startCanvasImageShare(bean: SharetypeBean, platform: string): void {
const ctx = this.ctx;
if (ctx === undefined) {
return;
}
const reportType: number = this.wechatReportType(bean);
const wx: WeChatApi = WeChatApi.getInstance();
if (!wx.isWXInstalled()) {
this.log.i('WeChat not installed; report cancel');
this.reportResult(3, reportType);
const cap = ctx.captureCanvas;
if (cap === undefined) {
this.startTextShare(bean, platform);
return;
}
if (bean.type === '2') {
// Canvas 截图图片分享(失败降级网页)
const cap = ctx.captureCanvas;
if (cap === undefined) {
this.wechatWebPage(ctx.uiAbilityContext, wx, bean, reportType);
return;
cap(bean.sharetype).then((dataUrl: string) => {
const filePath: string = dataUrl !== '' ? this.saveDataUrlToFile(ctx.uiAbilityContext, dataUrl) : '';
if (filePath !== '') {
this.onImageReady(bean, platform, filePath);
} else {
this.log.w('canvas capture empty; fallback to text guide');
this.startTextShare(bean, platform);
}
cap(bean.sharetype).then((dataUrl: string) => {
const filePath: string = dataUrl !== '' ? this.saveDataUrlToFile(ctx.uiAbilityContext, dataUrl) : '';
if (filePath !== '') {
this.wechatImage(ctx.uiAbilityContext, wx, filePath, bean, reportType);
} else {
this.log.w('canvas capture empty; fallback to webpage share');
this.wechatWebPage(ctx.uiAbilityContext, wx, bean, reportType);
}
}).catch((e: Error) => {
this.log.w(`captureCanvas failed: ${e.message}; fallback to webpage share`);
this.wechatWebPage(ctx.uiAbilityContext, wx, bean, reportType);
});
}).catch((e: Error) => {
this.log.w(`captureCanvas failed: ${e.message}; fallback to text guide`);
this.startTextShare(bean, platform);
});
}
/** type3(低优先,需确认 H5 是否使用):从 webpageUrl 下载图片 → 落盘 → 图片指引窗(失败降级文本)。 */
private startLinkImageShare(bean: SharetypeBean, platform: string): void {
const ctx = this.ctx;
if (ctx === undefined) {
return;
}
this.wechatWebPage(ctx.uiAbilityContext, wx, bean, reportType);
}
private wechatScene(bean: SharetypeBean): number {
return this.wechatSceneByFriend(bean.sharefriend === '1' ? 1 : 2);
}
/** friendType: 1 好友(WXSceneSession) / 2 朋友圈(WXSceneTimeline)。 */
private wechatSceneByFriend(friendType: number): number {
return friendType === 1 ? SendMessageToWXReq.WXSceneSession : SendMessageToWXReq.WXSceneTimeline;
}
private wechatWebPage(ctx: common.UIAbilityContext, wx: WeChatApi, bean: SharetypeBean, reportType: number): void {
const req: SendMessageToWXReq = new SendMessageToWXReq();
req.scene = this.wechatScene(bean);
const msg: WXMediaMessage = new WXMediaMessage();
msg.title = bean.title;
msg.description = bean.description;
const web: WXWebpageObject = new WXWebpageObject();
web.webpageUrl = bean.webpageUrl;
msg.mediaObject = web;
req.message = msg;
wx.sendShare(ctx, req, (resp) => {
this.reportResult(resp.errCode === ErrCode.ERR_OK ? 2 : 3, reportType);
const url: string = bean.webpageUrl;
if (url === '' || !(url.startsWith('http://') || url.startsWith('https://'))) {
this.startTextShare(bean, platform);
return;
}
this.seq += 1;
const ext: string = url.toLowerCase().indexOf('.png') >= 0 ? '.png' : '.jpg';
const savePath: string = `${ctx.uiAbilityContext.filesDir}/share/link_${this.seq}${ext}`;
Downloader.download(url, savePath).then(() => {
this.onImageReady(bean, platform, savePath);
}).catch((e: Error) => {
this.log.w(`link image download failed: ${e.message}; fallback to text guide`);
this.startTextShare(bean, platform);
});
}
private wechatImage(ctx: common.UIAbilityContext, wx: WeChatApi, filePath: string,
bean: SharetypeBean, reportType: number): void {
this.wechatImageByFriend(ctx, wx, filePath, reportType);
private onImageReady(bean: SharetypeBean, platform: string, filePath: string): void {
this.writeClipboardImage(filePath);
this.guide(bean, platform, 'image', filePath);
}
/** 微信图片分享(友/圈由 friendType 决定);分享面板与 H5 POST 上传两条链路共用。
* friendType 同时作为 sharesuccess 回传的 type1 好友 / 2 朋友圈)。 */
private wechatImageByFriend(ctx: common.UIAbilityContext, wx: WeChatApi, filePath: string, friendType: number): void {
const req: SendMessageToWXReq = new SendMessageToWXReq();
req.scene = this.wechatSceneByFriend(friendType);
const msg: WXMediaMessage = new WXMediaMessage();
const img: WXImageObject = new WXImageObject();
img.uri = fileUri.getUriFromPath(filePath); // 沙箱文件 file uriSDK 支持 jpeg/png
msg.mediaObject = img;
req.message = msg;
wx.sendShare(ctx, req, (resp) => {
this.reportResult(resp.errCode === ErrCode.ERR_OK ? 2 : 3, friendType);
});
private guide(bean: SharetypeBean, platform: string, kind: string, filePath: string): void {
this.seq += 1;
const resultEvent: string = `share.guide.${this.instanceId}.${this.seq}`;
EventBus.once(resultEvent, (p) => this.onGuideAction(bean, platform, kind, filePath, p));
const req: ShareGuideRequest = { platform, contentKind: kind, resultEvent };
EventBus.emit(ShareEvents.SHOW_GUIDE, req);
}
// —— QQ / 抖音:HarmonyOS 系统分享(不回传 sharesuccess)——
private shareViaSystem(bean: SharetypeBean, platform: string): void {
private onGuideAction(bean: SharetypeBean, platform: string, kind: string, filePath: string, payload?: Object): void {
const ctx = this.ctx;
if (ctx === undefined) {
return;
}
const uiCtx: common.UIAbilityContext = ctx.uiAbilityContext;
if (bean.type === '2') {
// 截图图片
const cap = ctx.captureCanvas;
if (cap === undefined) {
this.systemText(uiCtx, bean, platform);
return;
const action: string = payload !== undefined ? (payload as ShareGuideResult).action : 'cancel';
const reportType: number = bean.sharefriend === '1' ? 1 : 2;
if (action === 'open') {
this.openApp(uiCtx, platform);
if (platform === 'wechat') {
this.reportResult(2, reportType);
}
} else if (action === 'system') {
if (kind === 'image' && filePath !== '') {
this.systemImage(uiCtx, filePath, bean);
} else {
this.systemText(uiCtx, bean);
}
if (platform === 'wechat') {
this.reportResult(2, reportType);
}
} else {
// cancel
if (platform === 'wechat') {
this.reportResult(3, reportType);
}
cap(bean.sharetype).then((dataUrl: string) => {
const filePath: string = dataUrl !== '' ? this.saveDataUrlToFile(uiCtx, dataUrl) : '';
if (filePath !== '') {
this.systemImage(uiCtx, filePath, bean);
} else {
this.log.w(`[${platform}] canvas capture empty; fallback to text`);
this.systemText(uiCtx, bean, platform);
}
}).catch((e: Error) => {
this.log.w(`[${platform}] captureCanvas failed: ${e.message}; fallback to text`);
this.systemText(uiCtx, bean, platform);
});
return;
}
if (bean.type === '1') {
this.systemText(uiCtx, bean, platform);
return;
}
if (bean.type === '4') {
// 抖音视频:webpageUrl 为视频地址
this.systemVideo(uiCtx, bean);
return;
}
// else:网页链接(title + description + webpageUrl
this.systemLink(uiCtx, bean);
}
/** 文本分享(title + description,对齐 Android 文本拼接)。 */
private systemText(uiCtx: common.UIAbilityContext, bean: SharetypeBean, _platform: string): void {
const text: string = this.joinText(bean.title, bean.description);
const record: systemShare.SharedRecord = { utd: utd.UniformDataType.PLAIN_TEXT, content: text !== '' ? text : ' ' };
this.showSystemShare(uiCtx, record);
// —— 拉起 Appbest-effort,仅打开不预填)——
private schemeOf(platform: string): string {
if (platform === 'qq') {
return 'mqqapi://';
}
if (platform === 'douyin') {
return 'snssdk1128://';
}
return 'weixin://';
}
/** 网页链接分享。 */
private systemLink(uiCtx: common.UIAbilityContext, bean: SharetypeBean): void {
const url: string = bean.webpageUrl !== '' ? bean.webpageUrl : ' ';
private openApp(uiCtx: common.UIAbilityContext, platform: string): void {
const link: string = this.schemeOf(platform);
let canOpen: boolean = false;
try {
canOpen = bundleManager.canOpenLink(link);
} catch (e) {
this.log.w(`canOpenLink failed: ${(e as BusinessError).message}`);
return;
}
if (!canOpen) {
this.log.i(`app not openable: ${link}`);
return;
}
uiCtx.openLink(link, { appLinkingOnly: false }).catch((e: BusinessError) => {
this.log.w(`openLink failed: code=${e.code} ${e.message}`);
});
}
// —— 剪贴板 ——
private writeClipboardText(text: string): void {
try {
const data: pasteboard.PasteData =
pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text !== '' ? text : ' ');
pasteboard.getSystemPasteboard().setData(data)
.catch((e: BusinessError) => this.log.w(`clipboard text failed: ${e.code} ${e.message}`));
} catch (e) {
this.log.w(`clipboard text init failed: ${(e as Error).message}`);
}
}
/** 图片写剪贴板(尽力;对方能否粘贴 PIXELMAP 不可保证,systemImage 才是可靠交付)。 */
private writeClipboardImage(filePath: string): void {
const source: image.ImageSource = image.createImageSource(filePath);
source.createPixelMap().then((pm: image.PixelMap) => {
try {
const data: pasteboard.PasteData = pasteboard.createData(pasteboard.MIMETYPE_PIXELMAP, pm);
pasteboard.getSystemPasteboard().setData(data)
.catch((e: BusinessError) => this.log.w(`clipboard image failed: ${e.code} ${e.message}`));
} catch (e) {
this.log.w(`clipboard image init failed: ${(e as Error).message}`);
}
}).catch((e: BusinessError) => {
this.log.w(`createPixelMap failed: ${e.code} ${e.message}`);
});
}
// —— 系统分享(可靠兜底;文本不下发 url)——
private systemText(uiCtx: common.UIAbilityContext, bean: SharetypeBean): void {
const text: string = buildShareText(bean.title, bean.description);
const record: systemShare.SharedRecord = {
utd: utd.UniformDataType.HYPERLINK,
content: url,
title: bean.title !== '' ? bean.title : undefined,
description: bean.description !== '' ? bean.description : undefined,
utd: utd.UniformDataType.PLAIN_TEXT, content: text !== '' ? text : ' ',
};
this.showSystemShare(uiCtx, record);
}
/** 视频分享(webpageUrl 为视频地址;远程地址降级为链接分享)。 */
private systemVideo(uiCtx: common.UIAbilityContext, bean: SharetypeBean): void {
const path: string = bean.webpageUrl;
if (path !== '' && (path.startsWith('/') || path.startsWith('file://')) && FileSystem.exists(this.toLocalPath(path))) {
const local: string = this.toLocalPath(path);
const typeId: string = utd.getUniformDataTypeByFilenameExtension(this.extOf(local), utd.UniformDataType.VIDEO);
const record: systemShare.SharedRecord = {
utd: typeId,
uri: fileUri.getUriFromPath(local),
title: bean.title !== '' ? bean.title : undefined,
description: bean.description !== '' ? bean.description : undefined,
};
this.showSystemShare(uiCtx, record);
return;
}
// 非本地视频文件:以链接形式分享地址(系统分享无法直接推送远程视频)
this.systemLink(uiCtx, bean);
}
/** 图片分享(沙箱文件)。 */
private systemImage(uiCtx: common.UIAbilityContext, filePath: string, bean: SharetypeBean): void {
const typeId: string = utd.getUniformDataTypeByFilenameExtension(this.extOf(filePath), utd.UniformDataType.IMAGE);
const typeId: string =
utd.getUniformDataTypeByFilenameExtension(this.extOf(filePath), utd.UniformDataType.IMAGE);
const record: systemShare.SharedRecord = {
utd: typeId,
uri: fileUri.getUriFromPath(filePath),
@@ -320,7 +313,6 @@ export class ShareProvider implements CapabilityProvider {
this.showSystemShare(uiCtx, record);
}
/** 拉起系统分享面板(失败 log 不崩溃)。 */
private showSystemShare(uiCtx: common.UIAbilityContext, record: systemShare.SharedRecord): void {
try {
const data: systemShare.SharedData = new systemShare.SharedData(record);
@@ -343,10 +335,7 @@ export class ShareProvider implements CapabilityProvider {
this.bridge?.callHandler(OutboundHandlers.ShareSuccess, JSON.stringify(out));
}
/**
* dataURL"data:image/...;base64,XXXX")→ 解码 → 写沙箱 filesDir/share/shot_<seq>.jpg → 返回沙箱路径。
* 失败返回空串(调用方降级)。
*/
/** dataURL/纯base64 → 解码 → 写沙箱 filesDir/share/shot_<seq>.jpg|png → 返回路径;失败返回空串。 */
private saveDataUrlToFile(uiCtx: common.UIAbilityContext, dataUrl: string): string {
try {
const comma: number = dataUrl.indexOf(',');
@@ -366,22 +355,6 @@ export class ShareProvider implements CapabilityProvider {
}
}
private joinText(title: string, description: string): string {
const parts: string[] = [];
if (title !== '') {
parts.push(title);
}
if (description !== '') {
parts.push(description);
}
return parts.join('\n\n');
}
/** file:// 前缀 → 本地路径。 */
private toLocalPath(p: string): string {
return p.startsWith('file://') ? p.substring('file://'.length) : p;
}
private extOf(path: string): string {
const dot: number = path.lastIndexOf('.');
const slash: number = path.lastIndexOf('/');
@@ -1,16 +1,12 @@
/**
* 微信开放平台 SDK 封装(@tencent/wechat_open_sdk,契约 §10.1/§10.2/§13)。单例。
* 微信开放平台 SDK 封装(@tencent/wechat_open_sdk)。单例。**仅用于登录授权**(分享已去 SDK 化)
*
* 职责:持有 WXApi(用 AppID 创建)、发起授权/分享请求、接收微信回调并路由到对应回调
* AppID 为公开标识,可入客户端;**AppSecret 绝不入端**(登录换 profile 须服务端,§13 红线)。
*
* 回调路径:微信处理完拉回宿主 App(默认 EntryAbility),EntryAbility.onNewWant/onCreate
* 把 want 交给 handleWant() → SDK 解析 → onResp 路由到 authCb/shareCb。
* 职责:持有 WXApi(用 AppID 创建)、发起授权请求、接收微信回调并路由到 authCb
* AppID 为公开标识,可入客户端;AppSecret 绝不入端(登录换 profile 须服务端,§13 红线)。
*/
import { common, Want } from '@kit.AbilityKit';
import {
WXAPIFactory, WXApi, WXApiEventHandler, BaseReq, BaseResp,
SendAuthResp, SendMessageToWXResp,
WXAPIFactory, WXApi, WXApiEventHandler, BaseReq, BaseResp, SendAuthResp,
} from '@tencent/wechat_open_sdk';
import { Logger } from 'common';
@@ -18,7 +14,6 @@ import { Logger } from 'common';
export const WX_APP_ID: string = 'wxd2bd650e06bdfe58';
export type AuthRespCallback = (resp: SendAuthResp) => void;
export type ShareRespCallback = (resp: SendMessageToWXResp) => void;
export class WeChatApi {
private static readonly log: Logger = Logger.tag('WeChatApi');
@@ -26,7 +21,6 @@ export class WeChatApi {
private readonly api: WXApi;
private readonly handler: WXApiEventHandler;
private authCb: AuthRespCallback | undefined = undefined;
private shareCb: ShareRespCallback | undefined = undefined;
private constructor() {
this.api = WXAPIFactory.createWXAPI(WX_APP_ID);
@@ -58,12 +52,6 @@ export class WeChatApi {
this.api.sendReq(context, req);
}
/** 发起分享;resp 经 cb 异步回调。 */
sendShare(context: common.UIAbilityContext, req: BaseReq, cb: ShareRespCallback): void {
this.shareCb = cb;
this.api.sendReq(context, req);
}
/** 由 EntryAbility 在 onNewWant/onCreate 调用,处理微信回调 want。 */
handleWant(want: Want): void {
try {
@@ -80,12 +68,6 @@ export class WeChatApi {
if (cb !== undefined) {
cb(resp);
}
} else if (resp instanceof SendMessageToWXResp) {
const cb = this.shareCb;
this.shareCb = undefined;
if (cb !== undefined) {
cb(resp);
}
}
}
}