From 9f44a530470e030b7204b2c1504aa233d6efcf81 Mon Sep 17 00:00:00 2001 From: Jose Olarte III Date: Fri, 27 Mar 2026 21:33:46 +0800 Subject: [PATCH] feat(notifications): mint long-lived JWT for native New Activity prefetch (Phase A) Add BACKGROUND_JWT_EXPIRY_DAYS/SECONDS and accessTokenForBackgroundNotifications via createEndorserJwtForDid; configureNativeFetcher uses it instead of getHeaders so WorkManager prefetch is not stuck with a 60s access token. Interactive API calls unchanged. --- package-lock.json | 4 ++-- src/constants/backgroundJwt.ts | 9 +++++++++ src/libs/crypto/index.ts | 18 ++++++++++++++++++ .../notifications/nativeFetcherConfig.ts | 9 ++------- 4 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 src/constants/backgroundJwt.ts diff --git a/package-lock.json b/package-lock.json index 87ef13ed32..22698a3e9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8685,8 +8685,8 @@ } }, "node_modules/@timesafari/daily-notification-plugin": { - "version": "2.1.5", - "resolved": "git+https://gitea.anomalistdesign.com/trent_larson/daily-notification-plugin.git#469167a55fbebb91b3e61d6c8b3aec6fc873a13c", + "version": "2.2.0", + "resolved": "git+https://gitea.anomalistdesign.com/trent_larson/daily-notification-plugin.git#9121b1e0f7e1be50d00eb3e78d52e06816196697", "license": "MIT", "workspaces": [ "packages/*" diff --git a/src/constants/backgroundJwt.ts b/src/constants/backgroundJwt.ts new file mode 100644 index 0000000000..49cc1cec4d --- /dev/null +++ b/src/constants/backgroundJwt.ts @@ -0,0 +1,9 @@ +/** + * JWT lifetime for native New Activity background prefetch (`configureNativeFetcher`). + * Phase A: single long-lived token minted in TS; see doc/plan-background-jwt-pool-and-expiry.md. + * Confirm max `exp` with Endorser before raising. + */ +export const BACKGROUND_JWT_EXPIRY_DAYS = 90; + +export const BACKGROUND_JWT_EXPIRY_SECONDS = + BACKGROUND_JWT_EXPIRY_DAYS * 24 * 60 * 60; diff --git a/src/libs/crypto/index.ts b/src/libs/crypto/index.ts index b8ff2d57a8..82ba0678d5 100644 --- a/src/libs/crypto/index.ts +++ b/src/libs/crypto/index.ts @@ -4,6 +4,7 @@ import { entropyToMnemonic } from "ethereum-cryptography/bip39"; import { wordlist } from "ethereum-cryptography/bip39/wordlists/english"; import { HDNode } from "@ethersproject/hdnode"; +import { BACKGROUND_JWT_EXPIRY_SECONDS } from "@/constants/backgroundJwt"; import { CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI, createEndorserJwtForDid, @@ -104,6 +105,23 @@ export const accessToken = async (did?: string) => { } }; +/** + * JWT for native New Activity prefetch (`configureNativeFetcher` / WorkManager). + * Uses a long `exp` (`BACKGROUND_JWT_EXPIRY_SECONDS`); do not use for ordinary + * in-app API calls — use `getHeaders` / `accessToken` instead. + */ +export const accessTokenForBackgroundNotifications = async ( + did?: string, +): Promise => { + if (!did) { + return ""; + } + const nowEpoch = Math.floor(Date.now() / 1000); + const endEpoch = nowEpoch + BACKGROUND_JWT_EXPIRY_SECONDS; + const tokenPayload = { exp: endEpoch, iat: nowEpoch, iss: did }; + return createEndorserJwtForDid(did, tokenPayload); +}; + /** * Extract JWT from various URL formats * @param jwtUrlText The URL containing the JWT diff --git a/src/services/notifications/nativeFetcherConfig.ts b/src/services/notifications/nativeFetcherConfig.ts index ebefe8000d..8e2823073a 100644 --- a/src/services/notifications/nativeFetcherConfig.ts +++ b/src/services/notifications/nativeFetcherConfig.ts @@ -8,7 +8,7 @@ import { Capacitor } from "@capacitor/core"; import { DailyNotification } from "@/plugins/DailyNotificationPlugin"; -import { getHeaders } from "@/libs/endorserServer"; +import { accessTokenForBackgroundNotifications } from "@/libs/crypto"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { logger } from "@/utils/logger"; import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app"; @@ -63,12 +63,7 @@ export async function configureNativeFetcherIfReady( : DEFAULT_ENDORSER_API_SERVER; } - const headers = await getHeaders(did); - const auth = headers?.Authorization; - const jwtToken = - typeof auth === "string" && auth.startsWith("Bearer ") - ? auth.slice(7) - : ""; + const jwtToken = await accessTokenForBackgroundNotifications(did); if (!jwtToken) { logger.warn( "[nativeFetcherConfig] No JWT for native fetcher; API-driven notifications may fail",