锁定移动通知权限不可用边界

移动通知桥接把权限查询失败转为稳定 host_error

移动通知桥接把权限请求失败转为稳定 host_error

移动通知单测覆盖权限不可用时不调度系统通知

移动壳配置检查反查通知权限不可用边界
This commit is contained in:
2026-06-20 12:09:08 +08:00
parent 97588de941
commit 1670741a00
3 changed files with 82 additions and 8 deletions
@@ -104,6 +104,11 @@ const hostBridgeNetworkTestSource = fs.readFileSync(
);
const notificationsPath = new URL('../src/host-bridge/notifications.ts', import.meta.url);
const notificationsSource = fs.readFileSync(notificationsPath, 'utf8');
const notificationsTestPath = new URL(
'../src/host-bridge/notifications.test.ts',
import.meta.url,
);
const notificationsTestSource = fs.readFileSync(notificationsTestPath, 'utf8');
const protocolPath = new URL('../src/host-bridge/protocol.ts', import.meta.url);
const protocolSource = fs.readFileSync(protocolPath, 'utf8');
const protocolTestPath = new URL(
@@ -1936,6 +1941,7 @@ for (const notificationSnippet of [
'request: HostBridgeRequest',
'normalizeHostBridgeLocalNotification(request.payload)',
"invalidRequest('title is required')",
"message: 'notification permission unavailable'",
'showMobileLocalNotification(notification)',
'ok(request, await showMobileLocalNotification(notification))',
'HOST_BRIDGE_LOCAL_NOTIFICATION_DELIVERED_TO_SYSTEM_RESULT',
@@ -1945,6 +1951,18 @@ for (const notificationSnippet of [
}
}
for (const snippet of [
'rejects delivery when current permission lookup is unavailable',
'rejects delivery when permission request is unavailable',
"message: 'notification permission unavailable'",
'expect(Notifications.setNotificationChannelAsync).not.toHaveBeenCalled()',
'expect(Notifications.scheduleNotificationAsync).not.toHaveBeenCalled()',
]) {
if (!notificationsTestSource.includes(snippet)) {
throw new Error(`mobile shell notification tests missing ${snippet}`);
}
}
for (const snippet of [
'file.exportText',
'file.importText',
@@ -152,6 +152,42 @@ describe('mobile local notification helpers', () => {
expect(Notifications.scheduleNotificationAsync).not.toHaveBeenCalled();
});
test('rejects delivery when current permission lookup is unavailable', async () => {
vi.mocked(Notifications.getPermissionsAsync).mockRejectedValueOnce(
new Error('native permission failed'),
);
await expect(
showMobileLocalNotification({ title: '生成完成' }),
).rejects.toMatchObject({
code: 'host_error',
message: 'notification permission unavailable',
});
expect(Notifications.requestPermissionsAsync).not.toHaveBeenCalled();
expect(Notifications.setNotificationChannelAsync).not.toHaveBeenCalled();
expect(Notifications.scheduleNotificationAsync).not.toHaveBeenCalled();
});
test('rejects delivery when permission request is unavailable', async () => {
vi.mocked(Notifications.getPermissionsAsync).mockResolvedValue(
DENIED_NOTIFICATION_PERMISSION,
);
vi.mocked(Notifications.requestPermissionsAsync).mockRejectedValueOnce(
new Error('native permission failed'),
);
await expect(
showMobileLocalNotification({ title: '生成完成' }),
).rejects.toMatchObject({
code: 'host_error',
message: 'notification permission unavailable',
});
expect(Notifications.setNotificationChannelAsync).not.toHaveBeenCalled();
expect(Notifications.scheduleNotificationAsync).not.toHaveBeenCalled();
});
test('schedules iOS notification without channel trigger', async () => {
await showMobileLocalNotification({
title: '生成完成',
@@ -30,18 +30,38 @@ function hasNotificationPermission(
}
async function ensureNotificationPermission() {
const currentPermission = await Notifications.getPermissionsAsync();
let currentPermission: Awaited<
ReturnType<typeof Notifications.getPermissionsAsync>
>;
try {
currentPermission = await Notifications.getPermissionsAsync();
} catch {
throw {
code: 'host_error',
message: 'notification permission unavailable',
} satisfies HostBridgeError;
}
if (hasNotificationPermission(currentPermission)) {
return;
}
const requestedPermission = await Notifications.requestPermissionsAsync({
ios: {
allowAlert: true,
allowBadge: false,
allowSound: false,
},
});
let requestedPermission: Awaited<
ReturnType<typeof Notifications.requestPermissionsAsync>
>;
try {
requestedPermission = await Notifications.requestPermissionsAsync({
ios: {
allowAlert: true,
allowBadge: false,
allowSound: false,
},
});
} catch {
throw {
code: 'host_error',
message: 'notification permission unavailable',
} satisfies HostBridgeError;
}
if (!hasNotificationPermission(requestedPermission)) {
throw {
code: 'host_error',