# macOS Notification Test Guide Last verified: 2026-06-02 This guide fixes the preflight contract for macOS notification work before Apple signing, a macOS test device, or real OS notification UI evidence is available. Do not commit real Apple, APNs, Firebase, service-account, device-token, auth token, private endpoint, Team ID, signing identity, provisioning, notarization, or private host values into tracked docs. Use placeholders in examples and keep credential files in approved local or secret-store locations. ## Current Repository State - The macOS plugin is still a scaffold. `packages/messaging_flutter/macos/Classes/NexoMessagingPlugin.swift` currently registers only the legacy `nexo_messaging` method channel and implements `getPlatformVersion`. - Dart expects native notification and action channels: `com.tokilabs.nexo.messaging/notifications` and `com.tokilabs.nexo.messaging/notification_actions`. - The minimum macOS bridge is therefore not implemented yet. Until it exists, local routing can only be tested through Dart unit tests or a future debug injection bridge. - `apps/flutter-test/macos` is a host scaffold. It carries sandbox entitlements, but no notification entitlement, signing team, provisioning profile, hardened runtime, or notarization value is tracked in this repo. - Local routing fixtures live under `apps/flutter-test/macos/Fixtures/*.json`. The first useful macOS smoke target is: 1. Register the expected EventChannel and MethodChannel in the macOS plugin. 2. Implement a debug-only local injection path compatible with `debugSendNativeEvent`. 3. Map notification `userInfo` fields into the Dart `NotificationOpenedEvent` contract. 4. Build and run the test host with local placeholder signing. 5. Collect permission, display, click/open routing, lifecycle, and known ACK, dismiss, and inline reply gap evidence. ## Minimum Bridge Contract The Dart SDK expects the macOS native side to preserve these channel names: | Channel | Name | Direction | Minimum macOS behavior | |---|---|---|---| | EventChannel | `com.tokilabs.nexo.messaging/notifications` | native to Dart | Emit codec-safe maps for notification, open, token, clear, session, and future action-failure events. | | MethodChannel | `com.tokilabs.nexo.messaging/notification_actions` | Dart to native | Accept SDK action methods used by Dart without crashing the host app. | | Legacy MethodChannel | `nexo_messaging` | Dart/test compatibility | Keep `getPlatformVersion` until the generated example compatibility path is intentionally removed. | Minimum methods for the action channel: | Method | Minimum preflight result | Production expectation | |---|---|---| | `debugSendNativeEvent` | Validate the argument is a map and emit it on the EventChannel. | Debug/test-only injection path. | | `saveDeviceToken` | Accept and store or no-op a formatted token string without logging secrets. | Store the macOS server-registration token once the platform token source is chosen. | | `getDeviceToken` | Return the last stored formatted token or null. | Return the current formatted macOS server-registration token. | | `setAuthToken` | Validate required string arguments and no-op. | Persist token for ACK/reply work only after storage rules are designed. | | `clearAuthToken` | Validate `serverUrl` and no-op. | Clear persisted token for the server. | | `setSigningKey` | Validate required string arguments and no-op without persistence. | Persist the signing key only after secure storage rules are designed. | Fallback boundaries: - Missing bridge methods should return `FlutterMethodNotImplemented`, not a successful placeholder. - Token, auth, signing-key, ACK, dismiss, and inline reply persistence are not implied by this preflight milestone. - Real notification permission prompts and visible notification display belong to `macOS 알림 테스트` unless a small debug bridge task explicitly implements them first. ## Host Placeholder Criteria Tracked macOS host files must stay generic: - Keep real signing Team ID, identity, provisioning profile, notarization profile, Apple ID, and private host values out of tracked files. - Keep `apps/flutter-test/macos/Runner/DebugProfile.entitlements` and `Release.entitlements` as placeholder entitlements until a smoke run requires a specific capability. - Add notification capability through Xcode or local build settings for a smoke run, then record sanitized evidence instead of committing private signing state. - Do not add real credential files under `apps/flutter-test/macos`. Expected local placeholder checks: ```sh codesign -d --entitlements :- build/macos/Build/Products/Debug/flutter_test.app ``` Look for sandbox values and any locally added notification-related capability. Record private identifiers as placeholders in evidence. ## Payload Mapping macOS notification `userInfo` maps into Dart using the same stable keys as the Android and iOS contracts: | `userInfo` key | Dart event field | Notes | |---|---|---| | `type` | `type` | Use `message` for visible message notifications and `opened` for direct open events. | | `server_url` | `server_url` | Required before host navigation callbacks can run. | | `server_id` | `server_id` | Diagnostic and future server URL fallback input. | | `channel_id` | `channel_id` | Drives `onNavigateToChannel` when thread routing is not selected. | | `root_id` | `root_id` | Drives `onNavigateToThread` when CRT is enabled. | | `is_crt_enabled` | `is_crt_enabled` | Accept boolean `true`, string `"true"`, or string `"1"`. | | `ack_id` | `ack_id` | Preserve for future ACK evidence; do not invent ACK behavior. | | `post_id` | `post_id` | Preserve for notification identity and future ACK/display evidence. | | `userInteraction` | `userInteraction` | Set to `true` when a click/open path caused the event. | Routing expectation: - Channel fixture: `server_url` and `channel_id` are present, `root_id` is empty and `is_crt_enabled` is false, so Dart calls `onNavigateToChannel`. - Thread fixture: `server_url` and `root_id` are present and `is_crt_enabled` is true, so Dart calls `onNavigateToThread`. ## Local Fixtures Use the fixture files as sanitized payloads for a future debug injection method or for manual `userInfo` construction during smoke testing: - `apps/flutter-test/macos/Fixtures/open_channel.json` - `apps/flutter-test/macos/Fixtures/open_thread.json` The fixtures intentionally use `https://example.invalid` and smoke IDs. Replace private runtime values only in local run notes or sanitized evidence. ## Evidence Use [`macos-notification-smoke-evidence-template.md`](macos-notification-smoke-evidence-template.md) for macOS runs. Preflight evidence should explicitly separate: - permission state - visible display state - click/open routing result - app lifecycle state - ACK, dismiss, and inline reply implementation gaps Until the bridge and real OS smoke exist, a valid preflight result may be "not implemented yet" with a concrete follow-up plan, rather than a failed runtime claim.