Documents OneKey push notification system across platforms. Use when implementing notification features, handling notification clicks, configuring backend payloads, or understanding cold start navigation. Notification, push, toast, JPush, WebSocket.
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
skills listSkill Instructions
name: notification-system description: Documents OneKey push notification system across platforms. Use when implementing notification features, handling notification clicks, configuring backend payloads, or understanding cold start navigation. Notification, push, toast, JPush, WebSocket.
Notification System
This skill documents the OneKey push notification implementation across all platforms.
Platform Support Matrix
| Platform | Offline Push | Notification Bar | Click Navigation | In-App Toast |
|---|---|---|---|---|
| iOS | ✅ JPush | ✅ | ✅ | ✅ |
| Android | ✅ JPush | ✅ | ✅ | ✅ |
| Desktop macOS | ❌ | ✅ | ✅ | ✅ |
| Desktop Windows | ❌ | ✅ | ❌ | ✅ |
| Desktop Linux | ❌ | ✅ | ❌ | ✅ |
| Extension | ⚠️ (browser alive) | ✅ | ✅ | ✅ |
| Web | ❌ | ✅ | ✅ | ✅ |
Architecture Overview
┌─────────────────────────────────────────────────────────────────┐
│ Push Sources │
├─────────────────────┬───────────────────────────────────────────┤
│ JPush │ WebSocket │
│ (iOS/Android) │ (All platforms) │
└─────────────────────┴───────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ ServiceNotification │
│ packages/kit-bg/src/services/ServiceNotification/ │
│ - onNotificationReceived │
│ - onNotificationClicked │
│ - handleColdStartByNotification │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ notificationsUtils │
│ packages/shared/src/utils/notificationsUtils.ts │
│ - navigateToNotificationDetail │
│ - parseNotificationPayload │
└─────────────────────────────────────────────────────────────────┘
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
Transaction App Events Direct
Detail (EventBus) Navigation
Key Files Reference
| Purpose | Location |
|---|---|
| Notification service | packages/kit-bg/src/services/ServiceNotification/ServiceNotification.ts |
| Navigation utilities | packages/shared/src/utils/notificationsUtils.ts |
| Notification types | packages/shared/types/notification.ts |
| Cold start (native) | packages/kit/src/provider/Container/NotificationHandlerContainer/hooks.native.ts |
| Cold start (other) | packages/kit/src/provider/Container/NotificationHandlerContainer/hooks.ts |
| Event handlers | packages/kit/src/provider/Container/NotificationHandlerContainer/index.tsx |
| In-app toast | packages/kit/src/provider/Container/InAppNotification/index.tsx |
| Toast component | packages/components/src/actions/Toast/index.tsx |
| Payload test UI | packages/kit/src/views/Setting/pages/Tab/DevSettingsSection/NotificationPayloadTest.tsx |
Dev Settings: Notification Payload Test
Location: packages/kit/src/views/Setting/pages/Tab/DevSettingsSection/NotificationPayloadTest.tsx
A developer tool for testing notification payload parsing and navigation without sending actual push notifications.
Access Path
Settings → Dev Settings → Notification Payload Test
Features
- Mode Selection: Dropdown to select notification mode (1-5)
- Payload Editor: Text area to input/edit JSON or URL payload
- Load Example: Button to load default example payload for selected mode
- Test Button: Calls
parseNotificationPayloaddirectly to test navigation
Default Example Payloads
const payloadExamples = {
// Mode 1: Page Navigation - Navigate to modal
[ENotificationPushMessageMode.page]: {
screen: 'modal',
params: {
screen: 'SettingModal',
params: {
screen: 'SettingPerpUserConfig',
},
},
},
// Alternative: Navigate to main tab
// {
// screen: 'main',
// params: {
// screen: 'Discovery',
// params: {
// screen: 'TabDiscovery',
// },
// },
// },
// Mode 2: Dialog
[ENotificationPushMessageMode.dialog]: {
title: 'Test Dialog',
description: 'This is a test dialog from notification payload.',
confirmButtonProps: { text: 'Confirm' },
cancelButtonProps: { text: 'Cancel' },
onConfirm: {
actionType: 'openInBrowser',
payload: 'https://onekey.so',
},
},
// Mode 3: Open in Browser
[ENotificationPushMessageMode.openInBrowser]: 'https://onekey.so',
// Mode 4: Open in App
[ENotificationPushMessageMode.openInApp]: 'https://onekey.so/support',
// Mode 5: Open in DApp
[ENotificationPushMessageMode.openInDapp]: 'https://app.uniswap.org',
};
Usage
- Open Dev Settings in the app
- Find "Notification Payload Test" section
- Select the notification mode you want to test
- Edit the payload JSON/URL as needed
- Click "Test parseNotificationPayload" to trigger the navigation
This is useful for:
- Testing new navigation routes before backend integration
- Debugging notification payload formats
- Verifying dialog configurations
Notification Click Flow
onNotificationClicked (ServiceNotification.ts:243-288)
When a notification is clicked:
onNotificationClicked = async ({
notificationId,
params,
webEvent,
eventSource,
}: INotificationClickParams) => {
// 1. Skip if notificationId is empty (Huawei HarmonyOS edge case)
if (!notificationId) return;
// 2. Mark as shown to prevent duplicates
this.addShowedNotificationId(notificationId);
// 3. Acknowledge notification (for analytics/server sync)
void this.ackNotificationMessage({
msgId: notificationId,
action: ENotificationPushMessageAckAction.clicked,
remotePushMessageInfo: params?.remotePushMessageInfo,
});
// 4. Show and focus the app
await (await this.getNotificationProvider()).showAndFocusApp();
// 5. Wait for app to open, then navigate
await timerUtils.wait(400);
await notificationsUtils.navigateToNotificationDetail({
message: params?.remotePushMessageInfo,
isFromNotificationClick: true,
notificationId: notificationId || '',
notificationAccountId: params?.remotePushMessageInfo?.extras?.params?.accountId,
mode: params?.remotePushMessageInfo?.extras?.mode,
payload: params?.remotePushMessageInfo?.extras?.payload,
});
// 6. Remove notification from notification center
void this.removeNotification({ notificationId, desktopNotification });
};
navigateToNotificationDetail Logic
Location: packages/shared/src/utils/notificationsUtils.ts:175-315
Function Signature
async function navigateToNotificationDetail({
notificationId,
notificationAccountId,
message,
isFromNotificationClick,
navigation,
mode,
payload,
topicType,
isRead = false,
}: INavigateToNotificationDetailParams)
Navigation Decision Tree
┌─────────────────┐
│ Has mode set? │
└────────┬────────┘
│
┌──────────────┴──────────────┐
│ Yes │ No
▼ ▼
┌─────────────────┐ ┌─────────────────────┐
│parseNotification│ │ Has transactionHash │
│ Payload │ │ in extras? │
└─────────────────┘ └──────────┬──────────┘
│
┌──────────┴──────────┐
│ Yes │ No
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Navigate to │ │ Navigate to │
│HistoryDetails │ │ NotificationList│
│ modal │ │ (default) │
└─────────────────┘ └─────────────────┘
Key Logic
- Log analytics (if not already read)
- Check current route - If already in NotificationsModal, just update
- Transaction notifications: Navigate to
HistoryDetailsmodal with transaction params - Mode-based navigation: Call
parseNotificationPayloadif mode is set - Default behavior: Navigate to
NotificationList
parseNotificationPayload Logic
Location: packages/shared/src/utils/notificationsUtils.ts:127-173
Notification Modes
export enum ENotificationPushMessageMode {
page = 1, // Navigate to a specific page
dialog = 2, // Show a dialog
openInBrowser = 3, // Open URL in external browser
openInApp = 4, // Open URL in in-app browser
openInDapp = 5, // Open URL in DApp browser
}
Mode Handlers
| Mode | Event/Action | Handler Location |
|---|---|---|
page (1) | EAppEventBusNames.ShowNotificationPageNavigation | NotificationHandlerContainer/index.tsx:104-121 |
dialog (2) | EAppEventBusNames.ShowNotificationViewDialog | NotificationHandlerContainer/index.tsx:64-99 |
openInBrowser (3) | openUrlExternal(payload) | Direct call |
openInApp (4) | openUrlInApp(payload) | Direct call |
openInDapp (5) | EAppEventBusNames.ShowNotificationInDappPage | NotificationHandlerContainer/index.tsx:122-140 |
Implementation
export function parseNotificationPayload(
mode: ENotificationPushMessageMode,
payload: string | undefined,
fallbackHandler: () => void,
) {
switch (mode) {
case ENotificationPushMessageMode.page:
// Parse JSON payload and emit navigation event
const payloadObj = JSON.parse(payload || '');
appEventBus.emit(EAppEventBusNames.ShowNotificationPageNavigation, {
payload: payloadObj,
});
break;
case ENotificationPushMessageMode.dialog:
// Parse JSON payload and emit dialog event
const payloadObj = JSON.parse(payload || '');
appEventBus.emit(EAppEventBusNames.ShowNotificationViewDialog, {
payload: payloadObj,
});
break;
case ENotificationPushMessageMode.openInBrowser:
openUrlExternal(payload);
break;
case ENotificationPushMessageMode.openInApp:
openUrlInApp(payload);
break;
case ENotificationPushMessageMode.openInDapp:
appEventBus.emit(EAppEventBusNames.ShowNotificationInDappPage, payload);
break;
}
}
Backend Configuration Guide
Notification Message Structure
interface INotificationPushMessageExtras {
msgId: string;
miniBundlerVersion?: string; // Minimum app version required
mode?: ENotificationPushMessageMode; // 1-5
payload?: string; // JSON string or URL
topic: ENotificationPushTopicTypes;
image?: string; // Image URL for notification
params: {
msgId: string;
accountAddress: string;
accountId: string;
networkId: string;
transactionHash: string;
};
}
Mode Configuration Examples
Mode 1: Page Navigation
{
"mode": 1,
"payload": "{\"screen\":\"Modal\",\"params\":{\"screen\":\"SettingsModal\",\"params\":{\"screen\":\"SettingsPage\"}}}"
}
Payload supports local param replacement:
{
"screen": "Modal",
"params": {
"screen": "EarnModal",
"params": {
"screen": "EarnDetail",
"params": {
"networkId": "evm--1",
"accountId": "{local_accountId}"
}
}
}
}
Available local params:
{local_accountId}- Current account ID{local_indexedAccountId}- Current indexed account ID{local_networkId}- Current network ID{local_walletId}- Current wallet ID
Generate All Available Routes
Run the following command to generate a complete list of all navigable routes with ready-to-use Mode 1 JSON payloads:
npx tsx development/scripts/extract-routes.ts
This will generate:
build/routes/ROUTES.md- Markdown documentation with all routes and their parametersbuild/routes/routes.json- JSON format for programmatic access
Each route entry includes:
- Required and optional parameters
- Pre-filled
{local_*}template variables for common params - Complete Mode 1 JSON payload ready to copy
Mode 2: Dialog
{
"mode": 2,
"payload": "{\"title\":\"Update Available\",\"description\":\"A new version is available.\",\"onConfirm\":{\"actionType\":\"openInBrowser\",\"payload\":\"https://onekey.so\"}}"
}
Dialog payload structure:
interface INotificationViewDialogPayload {
title?: string;
description?: string;
icon?: IKeyOfIcons;
tone?: 'default' | 'destructive';
confirmButtonProps?: { text: string };
cancelButtonProps?: { text: string };
onConfirm: {
actionType: 'navigate' | 'openInApp' | 'openInBrowser';
payload: string | NavigationPayload;
};
}
Mode 3: Open in External Browser
{
"mode": 3,
"payload": "https://onekey.so/blog/announcement"
}
Mode 4: Open in In-App Browser
{
"mode": 4,
"payload": "https://onekey.so/support"
}
Mode 5: Open in DApp Browser
{
"mode": 5,
"payload": "https://app.uniswap.org"
}
Cold Start Notification Handling
Native Platforms (iOS/Android)
Location: packages/kit/src/provider/Container/NotificationHandlerContainer/hooks.native.ts
The useInitialNotification hook handles app cold start via notification:
export const useInitialNotification = () => {
const coldStartRef = useRef(true);
useEffect(() => {
setTimeout(async () => {
if (coldStartRef.current) {
coldStartRef.current = false;
// 1. Check ColdStartByNotification (JPush)
const options = ColdStartByNotification.launchNotification;
if (options) {
// Handle JPush launch notification
void backgroundApiProxy.serviceNotification.handleColdStartByNotification({
notificationId: options.msgId,
params: { /* notification details */ },
});
return;
}
// 2. Check LaunchOptionsManager (local/remote notifications)
const launchOptions = await launchOptionsManager.getLaunchOptions();
if (launchOptions?.localNotification || launchOptions?.remoteNotification) {
const userInfo = launchOptions.localNotification?.userInfo
|| launchOptions.remoteNotification?.userInfo;
await handleShowNotificationDetail({
message: userInfo,
notificationId: userInfo?.extras?.params?.msgId,
mode: userInfo?.extras?.mode,
payload: userInfo?.extras?.payload,
});
}
}
}, 350);
}, []);
};
Non-Native Platforms
Location: packages/kit/src/provider/Container/NotificationHandlerContainer/hooks.ts
export const useInitialNotification = () => {}; // No-op
Cold start handling is not needed for web/desktop/extension as:
- Web: Notifications don't persist across page reloads
- Desktop: App opens fresh, WebSocket reconnects
- Extension: Background script maintains state
In-App Notification Toast
Location: packages/kit/src/provider/Container/InAppNotification/index.tsx:410-461
When It Triggers
In-app notifications show when:
- WebSocket notification is received (
pushSource === 'websocket') - Platform is NOT iOS native (iOS uses native notification center)
Implementation
useEffect(() => {
const callback = ({
notificationId,
title,
description,
icon,
remotePushMessageInfo,
}) => {
const topicType = remotePushMessageInfo?.extras?.topic;
const isSystemTopic = topicType === ENotificationPushTopicTypes.system;
const toast = Toast.notification({
title,
message: description,
icon: isSystemTopic ? undefined : (icon as IKeyOfIcons),
iconImageUri: isSystemTopic ? undefined : remotePushMessageInfo?.extras?.image,
duration: 10 * 1000,
imageUri: remotePushMessageInfo?.extras?.image,
onPress: async () => {
await whenAppUnlocked();
await notificationsUtils.navigateToNotificationDetail({
message: remotePushMessageInfo,
isFromNotificationClick: true,
notificationId: notificationId || '',
mode: remotePushMessageInfo?.extras?.mode,
payload: remotePushMessageInfo?.extras?.payload,
});
toast.close();
},
});
};
appEventBus.on(EAppEventBusNames.ShowInAppPushNotification, callback);
return () => {
appEventBus.off(EAppEventBusNames.ShowInAppPushNotification, callback);
};
}, [navigation]);
Toast.notification Props
interface IToastNotificationProps {
title: string;
message?: string;
icon?: IKeyOfIcons;
iconImageUri?: string; // Custom icon image
imageUri?: string; // Large image on right
duration?: number; // Default: 5000ms
onPress?: () => void; // Click handler
onClose?: () => void; // Close callback
}
Customizing Toast Appearance
The Toast.notification component is defined in:
packages/components/src/actions/Toast/index.tsx:272-375
Key styling elements:
- Icon container:
bg="$bgStrong",borderRadius="$full", 28x28px - Title:
size="$headingSm", max 2 lines - Message:
size="$bodyMd",color="$textSubdued", max 3 lines - Image:
borderRadius="$1",size="$12"(48px)
Other parseNotificationPayload Usages
1. Hardware Device Get Started (DeviceGetStarted.tsx)
Location: packages/kit/src/views/DeviceManagement/pages/DeviceDetailsModal/DeviceGetStarted.tsx:38-39
const handleOpen = (item: { mode: number; payload: string }) => {
parseNotificationPayload(item.mode, item.payload, () => {});
};
Used for hardware wallet tutorial and FAQ links fetched from server.
2. Wallet Banner Clicks (useWalletBanner.ts)
Location: packages/kit/src/hooks/useWalletBanner.ts:69-72
if (item.mode) {
parseNotificationPayload(item.mode, item.payload, () => {});
return;
}
Used for promotional banners in the wallet home screen.
Event Bus Events
| Event Name | Trigger | Handler |
|---|---|---|
ShowInAppPushNotification | WebSocket notification received | InAppNotification/index.tsx |
ShowNotificationPageNavigation | Mode 1 payload parsed | NotificationHandlerContainer/index.tsx |
ShowNotificationViewDialog | Mode 2 payload parsed | NotificationHandlerContainer/index.tsx |
ShowNotificationInDappPage | Mode 5 payload parsed | NotificationHandlerContainer/index.tsx |
ShowFallbackUpdateDialog | Version mismatch | NotificationHandlerContainer/index.tsx |
UpdateNotificationBadge | Badge count change | Various UI components |
More by OneKeyHQ
View allFilters specific errors from Sentry reporting in this OneKey monorepo. Use when needing to ignore/suppress/filter Sentry errors, add error exclusions, or stop certain errors from being reported. Handles platform-specific filtering (desktop/mobile/web/extension).
Helps fix ESLint errors and warnings in the OneKey codebase. Use when running yarn lint and encountering warnings, cleaning up code before committing, or fixing spellcheck, unused variable, or other ESLint warnings.
Guide for adding new blockchain chains to OneKey. Use when implementing new chain support, adding blockchain protocols, or understanding chain architecture. Triggers on chain, blockchain, protocol, network, coin, token, add chain, new chain.
Creates test version branches for testing app upgrade functionality. Use when preparing upgrade test builds, testing version migration, or when the user mentions test version, 9005.x.x version numbers, upgrade testing, or version upgrade QA. Automates branch creation, version bumping, and build number hardcoding for upgrade flow verification.
