05d278f8f7
移动壳 HostBridge 能力失败日志只记录稳定阶段标签 更新 badge notification haptics network share navigation 失败测试断言 扩展移动壳配置门禁禁止能力日志打印原生 error 对象 补充 Expo 与 Tauri 宿主壳方案中的移动诊断日志边界
104 lines
2.8 KiB
TypeScript
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);
|
|
}
|