Files
Genarrative/apps/mobile-shell/src/host-bridge/badge.ts
T
kdletters 05d278f8f7 收口移动壳能力诊断日志
移动壳 HostBridge 能力失败日志只记录稳定阶段标签

更新 badge notification haptics network share navigation 失败测试断言

扩展移动壳配置门禁禁止能力日志打印原生 error 对象

补充 Expo 与 Tauri 宿主壳方案中的移动诊断日志边界
2026-06-20 23:09:49 +08:00

104 lines
2.8 KiB
TypeScript

import * as Notifications from 'expo-notifications';
import { Platform } from 'react-native';
import {
HOST_BRIDGE_BADGE_COUNT_MAX,
type HostBridgeError,
type HostBridgeRequest,
normalizeHostBridgeBadgeCount,
type SetBadgeCountPayload,
} from '../../../../packages/shared/src/contracts/hostBridge';
import { invalidRequest, ok } from './protocol';
type NotificationPermissionStatus = Awaited<
ReturnType<typeof Notifications.getPermissionsAsync>
>;
function hasBadgePermission(permission: NotificationPermissionStatus) {
return permission.ios?.allowsBadge === true;
}
function logMobileBadgeFailure(label: string, _error: unknown) {
console.warn(`mobile app badge failed for ${label}`);
}
async function ensureMobileBadgePermission() {
let currentPermission: NotificationPermissionStatus;
try {
currentPermission = await Notifications.getPermissionsAsync();
} catch (error) {
logMobileBadgeFailure('permission.current', error);
throw {
code: 'host_error',
message: 'app badge permission unavailable',
} satisfies HostBridgeError;
}
if (hasBadgePermission(currentPermission)) {
return;
}
let requestedPermission: NotificationPermissionStatus;
try {
requestedPermission = await Notifications.requestPermissionsAsync({
ios: {
allowAlert: false,
allowBadge: true,
allowSound: false,
},
});
} catch (error) {
logMobileBadgeFailure('permission.request', error);
throw {
code: 'host_error',
message: 'app badge permission unavailable',
} satisfies HostBridgeError;
}
if (!hasBadgePermission(requestedPermission)) {
throw {
code: 'host_error',
message: 'app badge permission denied',
} satisfies HostBridgeError;
}
}
export async function setMobileAppBadgeCount(request: HostBridgeRequest) {
if (Platform.OS !== 'ios') {
throw {
code: 'unsupported_capability',
message: 'app badge count is only supported on iOS mobile shell',
} satisfies HostBridgeError;
}
const count = normalizeHostBridgeBadgeCount(
(request.payload as SetBadgeCountPayload | undefined)?.count,
);
if (count === null) {
throw invalidRequest(
`count must be an integer between 0 and ${HOST_BRIDGE_BADGE_COUNT_MAX}`,
);
}
await ensureMobileBadgePermission();
let updated = false;
try {
updated = await Notifications.setBadgeCountAsync(count);
} catch (error) {
logMobileBadgeFailure('update.set_count', error);
throw {
code: 'host_error',
message: 'app badge update unavailable',
} satisfies HostBridgeError;
}
if (!updated) {
logMobileBadgeFailure(
'update.rejected',
new Error('setBadgeCountAsync returned false'),
);
throw {
code: 'host_error',
message: 'app badge update unavailable',
} satisfies HostBridgeError;
}
return ok(request, true);
}