05d278f8f7
移动壳 HostBridge 能力失败日志只记录稳定阶段标签 更新 badge notification haptics network share navigation 失败测试断言 扩展移动壳配置门禁禁止能力日志打印原生 error 对象 补充 Expo 与 Tauri 宿主壳方案中的移动诊断日志边界
121 lines
3.4 KiB
TypeScript
121 lines
3.4 KiB
TypeScript
import * as Notifications from 'expo-notifications';
|
|
import { Platform } from 'react-native';
|
|
|
|
import {
|
|
HOST_BRIDGE_LOCAL_NOTIFICATION_DELIVERED_TO_SYSTEM_RESULT,
|
|
HOST_BRIDGE_MOBILE_LOCAL_NOTIFICATION_CHANNEL_ID,
|
|
type HostBridgeError,
|
|
type HostBridgeRequest,
|
|
type LocalNotificationPayload,
|
|
normalizeHostBridgeLocalNotification,
|
|
} from '../../../../packages/shared/src/contracts/hostBridge';
|
|
import { invalidRequest, ok } from './protocol';
|
|
|
|
Notifications.setNotificationHandler({
|
|
handleNotification: async () => ({
|
|
shouldShowBanner: true,
|
|
shouldShowList: true,
|
|
shouldPlaySound: false,
|
|
shouldSetBadge: false,
|
|
}),
|
|
});
|
|
|
|
function logMobileNotificationFailure(label: string, _error: unknown) {
|
|
console.warn(`mobile notification failed for ${label}`);
|
|
}
|
|
|
|
function hasNotificationPermission(
|
|
permission: Awaited<ReturnType<typeof Notifications.getPermissionsAsync>>,
|
|
) {
|
|
return (
|
|
permission.granted ||
|
|
permission.ios?.status === Notifications.IosAuthorizationStatus.PROVISIONAL
|
|
);
|
|
}
|
|
|
|
async function ensureNotificationPermission() {
|
|
let currentPermission: Awaited<
|
|
ReturnType<typeof Notifications.getPermissionsAsync>
|
|
>;
|
|
try {
|
|
currentPermission = await Notifications.getPermissionsAsync();
|
|
} catch (error) {
|
|
logMobileNotificationFailure('permission.current', error);
|
|
throw {
|
|
code: 'host_error',
|
|
message: 'notification permission unavailable',
|
|
} satisfies HostBridgeError;
|
|
}
|
|
if (hasNotificationPermission(currentPermission)) {
|
|
return;
|
|
}
|
|
|
|
let requestedPermission: Awaited<
|
|
ReturnType<typeof Notifications.requestPermissionsAsync>
|
|
>;
|
|
try {
|
|
requestedPermission = await Notifications.requestPermissionsAsync({
|
|
ios: {
|
|
allowAlert: true,
|
|
allowBadge: false,
|
|
allowSound: false,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
logMobileNotificationFailure('permission.request', error);
|
|
throw {
|
|
code: 'host_error',
|
|
message: 'notification permission unavailable',
|
|
} satisfies HostBridgeError;
|
|
}
|
|
if (!hasNotificationPermission(requestedPermission)) {
|
|
throw {
|
|
code: 'host_error',
|
|
message: 'notification permission denied',
|
|
} satisfies HostBridgeError;
|
|
}
|
|
}
|
|
|
|
export async function showMobileLocalNotification(
|
|
notification: LocalNotificationPayload,
|
|
) {
|
|
await ensureNotificationPermission();
|
|
try {
|
|
if (Platform.OS === 'android') {
|
|
await Notifications.setNotificationChannelAsync(
|
|
HOST_BRIDGE_MOBILE_LOCAL_NOTIFICATION_CHANNEL_ID,
|
|
{
|
|
name: 'Genarrative',
|
|
importance: Notifications.AndroidImportance.DEFAULT,
|
|
},
|
|
);
|
|
}
|
|
|
|
await Notifications.scheduleNotificationAsync({
|
|
content: notification,
|
|
trigger:
|
|
Platform.OS === 'android'
|
|
? { channelId: HOST_BRIDGE_MOBILE_LOCAL_NOTIFICATION_CHANNEL_ID }
|
|
: null,
|
|
});
|
|
} catch (error) {
|
|
logMobileNotificationFailure('delivery.schedule', error);
|
|
throw {
|
|
code: 'host_error',
|
|
message: 'notification delivery unavailable',
|
|
} satisfies HostBridgeError;
|
|
}
|
|
return HOST_BRIDGE_LOCAL_NOTIFICATION_DELIVERED_TO_SYSTEM_RESULT;
|
|
}
|
|
|
|
export async function showMobileHostBridgeLocalNotification(
|
|
request: HostBridgeRequest,
|
|
) {
|
|
const notification = normalizeHostBridgeLocalNotification(request.payload);
|
|
if (!notification) {
|
|
throw invalidRequest('title is required');
|
|
}
|
|
|
|
return ok(request, await showMobileLocalNotification(notification));
|
|
}
|