export const statsActionFilters = [ ['action-ping', 'ping'], ['action-delete', 'delete'], ['action-reset', 'reset'], ['action-set', 'set'], ['action-get', 'get'], ['action-set-fail', 'set_fail'], ['action-update-fail', 'update_fail'], ['action-update-check-error', 'update_check_error'], ['action-download-fail', 'download_fail'], ['action-windows-path-fail', 'windows_path_fail'], ['action-canonical-path-fail', 'canonical_path_fail'], ['action-directory-path-fail', 'directory_path_fail'], ['action-unzip-fail', 'unzip_fail'], ['action-low-mem-fail', 'low_mem_fail'], ['action-download-0', 'download_0'], ['action-download-10', 'download_10'], ['action-download-20', 'download_20'], ['action-download-30', 'download_30'], ['action-download-40', 'download_40'], ['action-download-50', 'download_50'], ['action-download-60', 'download_60'], ['action-download-70', 'download_70'], ['action-download-80', 'download_80'], ['action-download-90', 'download_90'], ['action-download-complete', 'download_complete'], ['action-download-manifest-start', 'download_manifest_start'], ['action-download-manifest-complete', 'download_manifest_complete'], ['action-download-zip-start', 'download_zip_start'], ['action-download-zip-complete', 'download_zip_complete'], ['action-download-manifest-file-fail', 'download_manifest_file_fail'], ['action-download-manifest-checksum-fail', 'download_manifest_checksum_fail'], ['action-download-manifest-brotli-fail', 'download_manifest_brotli_fail'], ['action-finish-download-fail', 'finish_download_fail'], ['action-manifest-path-fail', 'manifest_path_fail'], ['action-decrypt-fail', 'decrypt_fail'], ['action-checksum-required', 'checksum_required'], ['action-insufficient-disk-space', 'insufficient_disk_space'], ['action-app-moved-to-foreground', 'app_moved_to_foreground'], ['action-app-moved-to-background', 'app_moved_to_background'], ['action-app-launch-start', 'app_launch_start'], ['action-app-launch-ready', 'app_launch_ready'], ['action-app-launch-timeout', 'app_launch_timeout'], ['action-app-crash', 'app_crash'], ['action-app-crash-native', 'app_crash_native'], ['action-app-anr', 'app_anr'], ['action-app-killed-low-memory', 'app_killed_low_memory'], ['action-app-killed-excessive-resource-usage', 'app_killed_excessive_resource_usage'], ['action-app-initialization-failure', 'app_initialization_failure'], ['action-app-memory-warning', 'app_memory_warning'], ['action-webview-javascript-error', 'webview_javascript_error'], ['action-webview-unhandled-rejection', 'webview_unhandled_rejection'], ['action-webview-resource-error', 'webview_resource_error'], ['action-webview-security-policy-violation', 'webview_security_policy_violation'], ['action-webview-unclean-restart', 'webview_unclean_restart'], ['action-webview-render-process-gone', 'webview_render_process_gone'], ['action-webview-content-process-terminated', 'webview_content_process_terminated'], ['action-webview-dom-content-loaded', 'webview_dom_content_loaded'], ['action-webview-page-loaded', 'webview_page_loaded'], ['action-app-nav', 'app_nav'], ['action-os-version-changed', 'os_version_changed'], ['action-native-app-version-changed', 'native_app_version_changed'], ['action-uninstall', 'uninstall'], ['action-need-plan-upgrade', 'needPlanUpgrade'], ['action-missing-bundle', 'missingBundle'], ['action-no-new', 'noNew'], ['action-disable-platform-ios', 'disablePlatformIos'], ['action-disable-platform-android', 'disablePlatformAndroid'], ['action-disable-platform-electron', 'disablePlatformElectron'], ['action-channel-paused', 'channelPaused'], ['action-disable-auto-update-to-major', 'disableAutoUpdateToMajor'], ['action-cannot-update-via-private-channel', 'cannotUpdateViaPrivateChannel'], ['action-disable-auto-update-to-minor', 'disableAutoUpdateToMinor'], ['action-disable-auto-update-to-patch', 'disableAutoUpdateToPatch'], ['action-channel-misconfigured', 'channelMisconfigured'], ['action-disable-auto-update-metadata', 'disableAutoUpdateMetadata'], ['action-disable-auto-update-under-native', 'disableAutoUpdateUnderNative'], ['action-disable-dev-build', 'disableDevBuild'], ['action-disable-prod-build', 'disableProdBuild'], ['action-disable-emulator', 'disableEmulator'], ['action-disable-device', 'disableDevice'], ['action-cannot-get-bundle', 'cannotGetBundle'], ['action-checksum-fail', 'checksum_fail'], ['action-key-mismatch', 'keyMismatch'], ['action-no-channel-or-override', 'NoChannelOrOverride'], ['action-set-channel', 'setChannel'], ['action-get-channel', 'getChannel'], ['action-rate-limited', 'rateLimited'], ['action-rate-limit-reached', 'rate_limit_reached'], ['action-disable-auto-update', 'disableAutoUpdate'], ['action-invalid-ip', 'InvalidIp'], ['action-blocked-by-server-url', 'blocked_by_server_url'], ['action-backend-refusal', 'backend_refusal'], ['action-custom-id-blocked', 'customIdBlocked'], ['action-set-next', 'set_next'], ] as const export const filterToAction: Record = Object.fromEntries(statsActionFilters) export const actionToFilter: Record = Object.fromEntries( statsActionFilters.map(([filterKey, actionValue]) => [actionValue, filterKey]), ) const failureActions = new Set([ 'set_fail', 'update_fail', 'download_fail', 'windows_path_fail', 'canonical_path_fail', 'directory_path_fail', 'unzip_fail', 'low_mem_fail', 'download_manifest_file_fail', 'download_manifest_checksum_fail', 'download_manifest_brotli_fail', 'decrypt_fail', 'app_crash', 'app_crash_native', 'app_anr', 'app_killed_low_memory', 'app_killed_excessive_resource_usage', 'app_initialization_failure', 'webview_javascript_error', 'webview_unhandled_rejection', 'webview_resource_error', 'webview_security_policy_violation', 'webview_unclean_restart', 'webview_render_process_gone', 'webview_content_process_terminated', 'cannotGetBundle', 'checksum_fail', 'blocked_by_server_url', 'backend_refusal', ]) export const failureActionFilterKeys = statsActionFilters .filter(([, actionValue]) => failureActions.has(actionValue)) .map(([filterKey]) => filterKey) /** Observe / health events (webview, crashes, launches, native version changes). */ const observeActions = new Set([ 'app_moved_to_foreground', 'app_moved_to_background', 'app_launch_start', 'app_launch_ready', 'app_launch_timeout', 'app_crash', 'app_crash_native', 'app_anr', 'app_killed_low_memory', 'app_killed_excessive_resource_usage', 'app_initialization_failure', 'app_memory_warning', 'webview_javascript_error', 'webview_unhandled_rejection', 'webview_resource_error', 'webview_security_policy_violation', 'webview_unclean_restart', 'webview_render_process_gone', 'webview_content_process_terminated', 'webview_dom_content_loaded', 'webview_page_loaded', 'app_nav', 'os_version_changed', 'native_app_version_changed', ]) export const observeActionFilterKeys = statsActionFilters .filter(([, actionValue]) => observeActions.has(actionValue)) .map(([filterKey]) => filterKey) /** Live-update / OTA process events (everything that is not observe). */ export const updateActionFilterKeys = statsActionFilters .filter(([, actionValue]) => !observeActions.has(actionValue)) .map(([filterKey]) => filterKey) export function createActionFilterState(): Record { return Object.fromEntries(statsActionFilters.map(([filterKey]) => [filterKey, false])) } /** * Observe signal categories. None of these come from Capgo itself: the updater * plugin only relays what the OS and the WebView report about the host app. * - crash: the app process failed (crash, ANR, startup failure, launch timeout). * - web: an error thrown or triggered by the app's own web code. * - system: the OS reclaimed memory or restarted the WebView. Often expected, * especially while the app sits in the background. * - context: informational events (launches, page loads, navigation). */ export type ObserveSignalCategory = 'crash' | 'web' | 'system' | 'context' const observeSignals: Record = { app_crash: { category: 'crash', helpKey: 'observe-signal-help-app-crash' }, app_crash_native: { category: 'crash', helpKey: 'observe-signal-help-app-crash-native' }, app_anr: { category: 'crash', helpKey: 'observe-signal-help-app-anr' }, app_initialization_failure: { category: 'crash', helpKey: 'observe-signal-help-app-initialization-failure' }, app_launch_timeout: { category: 'crash', helpKey: 'observe-signal-help-app-launch-timeout' }, webview_javascript_error: { category: 'web', helpKey: 'observe-signal-help-webview-javascript-error' }, webview_unhandled_rejection: { category: 'web', helpKey: 'observe-signal-help-webview-unhandled-rejection' }, webview_resource_error: { category: 'web', helpKey: 'observe-signal-help-webview-resource-error' }, webview_security_policy_violation: { category: 'web', helpKey: 'observe-signal-help-webview-security-policy-violation' }, app_killed_low_memory: { category: 'system', helpKey: 'observe-signal-help-app-killed-low-memory' }, app_killed_excessive_resource_usage: { category: 'system', helpKey: 'observe-signal-help-app-killed-excessive-resource-usage' }, app_memory_warning: { category: 'system', helpKey: 'observe-signal-help-app-memory-warning' }, webview_unclean_restart: { category: 'system', helpKey: 'observe-signal-help-webview-unclean-restart' }, webview_render_process_gone: { category: 'system', helpKey: 'observe-signal-help-webview-render-process-gone' }, webview_content_process_terminated: { category: 'system', helpKey: 'observe-signal-help-webview-content-process-terminated' }, } export function observeSignalCategory(action: string): ObserveSignalCategory { return observeSignals[action]?.category ?? 'context' } export function observeSignalHelpKey(action: string): string | null { return observeSignals[action]?.helpKey ?? null } /** * Updater failure categories. These are the live update failures shown on the * Observe > Updater page. App crashes and WebView errors belong to the Native * page and are intentionally left out. * - rollback: the new bundle did not call notifyAppReady() in time, so the * plugin restored the previous bundle. * - bundle: the bundle was rejected or could not be served (checksum, * encryption, invalid paths, install step). * - device: the device could not finish the download (network, storage, * memory). The device keeps its current bundle and retries later. * - setup: the plugin setup prevents updates (server.url, old plugin). */ export type UpdaterFailureCategory = 'rollback' | 'bundle' | 'device' | 'setup' const updaterFailures: Record = { update_fail: { category: 'rollback', helpKey: 'updater-failure-help-update-fail' }, set_fail: { category: 'bundle', helpKey: 'updater-failure-help-set-fail' }, checksum_fail: { category: 'bundle', helpKey: 'updater-failure-help-checksum-fail' }, decrypt_fail: { category: 'bundle', helpKey: 'updater-failure-help-decrypt-fail' }, cannotGetBundle: { category: 'bundle', helpKey: 'updater-failure-help-cannot-get-bundle' }, download_manifest_checksum_fail: { category: 'bundle', helpKey: 'updater-failure-help-checksum-fail' }, download_manifest_brotli_fail: { category: 'bundle', helpKey: 'updater-failure-help-brotli-fail' }, manifest_path_fail: { category: 'bundle', helpKey: 'updater-failure-help-path-fail' }, windows_path_fail: { category: 'bundle', helpKey: 'updater-failure-help-path-fail' }, canonical_path_fail: { category: 'bundle', helpKey: 'updater-failure-help-path-fail' }, directory_path_fail: { category: 'bundle', helpKey: 'updater-failure-help-path-fail' }, download_fail: { category: 'device', helpKey: 'updater-failure-help-download-fail' }, download_manifest_file_fail: { category: 'device', helpKey: 'updater-failure-help-download-fail' }, finish_download_fail: { category: 'device', helpKey: 'updater-failure-help-finish-download-fail' }, unzip_fail: { category: 'device', helpKey: 'updater-failure-help-unzip-fail' }, low_mem_fail: { category: 'device', helpKey: 'updater-failure-help-low-mem-fail' }, insufficient_disk_space: { category: 'device', helpKey: 'updater-failure-help-insufficient-disk-space' }, blocked_by_server_url: { category: 'setup', helpKey: 'updater-failure-help-blocked-by-server-url' }, backend_refusal: { category: 'setup', helpKey: 'updater-failure-help-backend-refusal' }, } /** Actions requested by the Observe > Updater insights. */ export const updaterInsightActions = Object.keys(updaterFailures) export function updaterFailureCategory(action: string): UpdaterFailureCategory { return updaterFailures[action]?.category ?? 'bundle' } export function updaterFailureHelpKey(action: string): string | null { return updaterFailures[action]?.helpKey ?? null }