import { OBSERVATION_CATEGORY_SYSTEM, unsupportedScopes, type RecordsConnection, type ReplacementOutcome } from "../../../services/RecordsConnectionsService.tsx"; import { categoryWords } from "./retrievalWords.ts"; /** Renewal state comes from the server, never from the browser's clock. */ export const connectionStateLabel = (connection: RecordsConnection, now = Date.now()): string => { if (connection.status === "disconnected") return connection.replacedBy ? "Replaced" : "Switched off"; if (connection.status === "needs-reconnect") return "Needs connecting again"; if (connection.status === "pending") return "Not confirmed yet, so not in use"; if (connection.status === "adoption-failed") return "Could not be saved, so not in use"; if (connection.renewing) return "Renewing access"; if (connection.standing === "unknown") return "Not known just now"; if (connection.expiresAt && Date.parse(connection.expiresAt) <= now) return "Expired"; return "Connected"; }; export const connectionRecoveryWords = (connection: RecordsConnection): string | undefined => { if (connection.status === "connected" && connection.standing === "unknown") return "Whether this connection can still be used could not be checked just now. Look again shortly."; if (connection.status !== "needs-reconnect") return undefined; if (connection.reconnectReason === "renewal-stopped") return "The renewal stopped. Connect to this system again before reading records."; if (connection.reconnectReason === "renewal-unknown") return "The renewal could not be confirmed. Connect to this system again before reading records."; if (connection.reconnectReason === "credentials-unusable") return "Its stored access can no longer be used. Connect to this system again before reading records."; if (connection.reconnectReason === "registration-changed") return "How Akashic is registered with this system changed since it was connected, so its access is not used. Connect again before reading records."; if (connection.reconnectReason === "client-config-unavailable") return "Akashic is not set up to renew access to this system just now, so nothing was sent. Connect again before reading records."; return "The connection was made but needs connecting again before it can be used."; }; export const systemName = (named: { vendorDisplay: string; environmentLabel: string }): string => named.vendorDisplay + " ยท " + named.environmentLabel; /** * The record types a grant reads, as the scopes name them; scopes that read nothing are not listed. A type read only in * some categories is listed with them, never as the whole type. */ export const readableTypes = (scopes: readonly string[]): string[] => { const types: string[] = []; const limited = new Map>(); for (const scope of scopes) { if (unsupportedScopes([scope]).length) continue; const found = /^(?:patient|user)\/([A-Za-z*]+)\.(?:read|r|rs|\*)$/.exec(scope); if (found) { const type = found[1] === "*" ? "Everything granted" : found[1]; if (!types.includes(type)) types.push(type); continue; } const category = /^(?:patient|user)\/(Observation)\.(?:read|r|rs|\*)\?category=(.+)$/.exec(scope); const code = category ? safeDecode(category[2]) : undefined; if (category && code?.startsWith(OBSERVATION_CATEGORY_SYSTEM + "|")) { limited.set(category[1], (limited.get(category[1]) ?? new Set()).add(code.slice(OBSERVATION_CATEGORY_SYSTEM.length + 1))); } } for (const [type, codes] of limited) { if (!types.includes(type)) types.push(type + " (" + categoryWords([...codes].sort()) + ")"); } return types; }; const safeDecode = (value: string): string | undefined => { try { return decodeURIComponent(value); } catch { return undefined; } }; /** Access granted in a form Akashic cannot use yet, named so its absence is not mistaken for a refusal. */ export const unusableGrantLine = (scopes: readonly string[]): string | undefined => { const kinds = unsupportedScopes(scopes); return kinds.length ? "Granted in a form Akashic cannot use yet, so not used: " + kinds.join(", ") : undefined; }; export const REVOCATION_WORDS: Record, string> = { pending: "Switched off here. Whether the other system ended access is not known yet.", revoked: "Switched off. The other system ended access.", "not-supported": "Switched off here. That system offers no way to end the grant from here.", "could-not-confirm": "Switched off here. The other system did not confirm it ended access.", }; export const TOKENS_NOT_DELETED = "Akashic could not delete its stored access. It is not used."; export const TOKENS_DELETED = "Akashic deleted its stored access."; /** Said of a switch-off whose result the server could not store: the list it reads next says what was kept. */ export const SWITCH_OFF_UNCONFIRMED = "Switched off here. What came of it after that could not be saved, so this is not a record of it. Check again to see what was kept."; /** * Off with its stored access not known deleted: switching off again carries the cleanup on and turns nothing back on. * Once deleted, nothing is owed: with no access left to send, the other system cannot be asked again. */ export const withdrawalOwed = (connection: RecordsConnection): boolean => connection.status === "disconnected" && connection.cleanup !== "tokens-deleted"; /** Replaced by one that is listed and connected: that one is in use, and switching this one off is still owed. */ export const replacedInUse = (connection: RecordsConnection, listed: readonly RecordsConnection[]): boolean => Boolean(connection.replacing) && listed.some((other) => other.connectionId === connection.replacing && other.status === "connected"); export const REPLACED_IN_USE = "A new connection to this system is in use. Switch this one off to finish replacing it."; export const REPLACES_EARLIER = "Made in place of an earlier connection to this system."; /** What a completed Connect again says of the connection it replaces. */ export const REPLACEMENT_WORDS: Record = { retired: "The connection it replaces was switched off. It stays listed as history.", pending: "The connection it replaces is not switched off yet. Switch it off from Connections to finish.", withdrawn: "The connection it was to replace had already been switched off.", "not-replaced": "The connection it was to replace was left as it was and is still listed.", };