import { sessionStorageKeyPrefix, stackStorageKeyPrefix } from './stackStorageKey'; import { DEMO_APP_ORIGIN, DEMO_COGNITO_CLIENTS, DEMO_LANDING_ORIGINS, DEMO_TURNSTILE_SITE_KEY } from './demoStacks'; import { bareHttpsOrigin } from './demoOrigin'; import { portalStorageKeyPrefix, sessionPortalForContext } from './portalStorageKey'; import { readSandboxMode } from './sandboxMode'; /** * - WHICH STACK THIS BUILD TALKS TO, AND WHERE ITS SESSION IS STORED. * `localStoragePrefix` IS DERIVED. Do not hand-write it in any block below. */ const STACKS = { staging: { region: 'us-east-1', cognitoDomain: 'https://akashichealthrecords-staging.auth.us-east-1.amazoncognito.com', cognitoUserPoolId: 'us-east-1_iy93VU33D', cognitoPractitionerClient: '1ofneolro5t20qqcmvvosn1jur', cognitoPatientClient: 'k8m3ot6rci271ap1nnepvlaja', cognitoRelatedPersonClient: 'kdiscfem24u81lrc9ps83uk9m', apiUrl: 'https://api.staging.akashichealthrecords.com/R4', fhirFoundationApi: 'https://fhir-foundation.staging.akashichealthrecords.com/R4', fhirBaseApi: 'https://fhir-base.staging.akashichealthrecords.com/R4', fhirIdentityApi: 'https://fhir-identity.staging.akashichealthrecords.com/R4', fhirClinicalApi: 'https://fhir-clinical.staging.akashichealthrecords.com/R4', orgAuthorizationApi: 'https://fhir-org-authorization.staging.akashichealthrecords.com/R4', fhirGuardianshipApi: 'https://fhir-guardianship.staging.akashichealthrecords.com/R4', websocketApi: 'wss://wss.staging.akashichealthrecords.com', terminologyApi: 'https://terminology.staging.akashichealthrecords.com/fhir', stripePublicKey: 'pk_test_qMmH3ls33DaCJm3RuU4BfpFV', contractorsConsoleUrl: 'https://contractors.staging.akashic.health/', production: false, }, sandbox: { region: 'us-east-1', cognitoDomain: 'https://akashichealthrecords-staging.auth.us-east-1.amazoncognito.com', cognitoUserPoolId: 'us-east-1_iy93VU33D', cognitoPractitionerClient: '1ofneolro5t20qqcmvvosn1jur', cognitoPatientClient: 'k8m3ot6rci271ap1nnepvlaja', cognitoRelatedPersonClient: 'kdiscfem24u81lrc9ps83uk9m', // API hosts stay the sandbox's own; only identity is shared with staging. apiUrl: 'https://api.sandbox.akashichealthrecords.com/R4', fhirFoundationApi: 'https://fhir-foundation.sandbox.akashichealthrecords.com/R4', fhirBaseApi: 'https://fhir-base.sandbox.akashichealthrecords.com/R4', fhirIdentityApi: 'https://fhir-identity.sandbox.akashichealthrecords.com/R4', fhirClinicalApi: 'https://fhir-clinical.sandbox.akashichealthrecords.com/R4', orgAuthorizationApi: 'https://fhir-org-authorization.sandbox.akashichealthrecords.com/R4', fhirGuardianshipApi: 'https://fhir-guardianship.sandbox.akashichealthrecords.com/R4', websocketApi: 'wss://wss.sandbox.akashichealthrecords.com', terminologyApi: 'https://terminology.staging.akashichealthrecords.com/fhir', stripePublicKey: 'pk_test_51UIt014D6BUnYSR1HyP9dJ8RQg6PaXckxkXKvD7MnwAQmyfbE2JPRE5Fj9LsZu5ANOJCw8cvmRND7jCW9RBlnUUV009Joos6zw', // Agencies are staging's: the sandbox shares staging's identity pool. contractorsConsoleUrl: 'https://contractors.staging.akashic.health/', production: false, }, // HIS-Prod as rebuilt from main on 2026-10-05. The pool and client ids are the AkashicAuthStack-prod // exports; a rebuild of that stack mints new ones, so read them again then. prod: { region: 'us-east-1', cognitoDomain: 'https://akashichealthrecords.auth.us-east-1.amazoncognito.com', cognitoUserPoolId: 'us-east-1_cVFmwTHXq', cognitoPractitionerClient: '2e6svnr57ngc767l6kmblmcn5b', cognitoPatientClient: '4nh2416e81lgvvnk2o71t091n0', cognitoRelatedPersonClient: '20rmrsjjs4fs3gehkhndbonhen', apiUrl: 'https://api.akashichealthrecords.com/R4', fhirFoundationApi: 'https://fhir-foundation.akashichealthrecords.com/R4', fhirBaseApi: 'https://fhir-base.akashichealthrecords.com/R4', fhirIdentityApi: 'https://fhir-identity.akashichealthrecords.com/R4', fhirClinicalApi: 'https://fhir-clinical.akashichealthrecords.com/R4', orgAuthorizationApi: 'https://fhir-org-authorization.akashichealthrecords.com/R4', fhirGuardianshipApi: 'https://fhir-guardianship.akashichealthrecords.com/R4', websocketApi: 'wss://wss.akashichealthrecords.com', terminologyApi: 'https://terminology.akashichealthrecords.com/fhir', stripePublicKey: 'pk_live_sshZXbUUD4BmYRDT6kAEepQF', contractorsConsoleUrl: 'https://contractors.akashic.health/', production: true, }, } as const; type StackName = keyof typeof STACKS; declare const AKASHIC_STACK: string | undefined; // Which stack a live stack's Sandbox Mode switches to. Prod staff share staging's sandbox (release gate G2). const SANDBOX_OF: Partial> = { staging: 'sandbox', prod: 'sandbox' }; const requested = typeof AKASHIC_STACK === 'string' ? AKASHIC_STACK : ''; if (!(requested in STACKS)) { // Thrown at module load, so a mis-built bundle fails on the first page that imports this rather // than on whichever call happens to reach the wrong stack first. throw new Error( `AKASHIC_STACK is ${requested ? `"${requested}", which is not a known stack` : 'not set'}. ` + `Expected one of: ${Object.keys(STACKS).join(', ')}. ` + 'Build through the npm scripts, which set it. Defaulting here would ship a bundle pointed ' + 'at the wrong stack, which is the failure this file is shaped to prevent.', ); } const requestedStack = requested as StackName; const sandboxSibling = SANDBOX_OF[requestedStack]; const wpData = (window as any).akasherePatientPortalData; /* * STAFF ONLY - mirrors `utils/mountContext.ts`'s `getMountContext()` / `isStaffExperience()`, inlined * rather than imported: that module imports THIS file for `environment.wpData`, so importing it back * here would cycle. * * A WordPress mount states its own context and is trusted outright. A HOSTED mount - no WordPress * payload at all - has nothing to trust, so the path decides between the patient portal and the staff * portal, exactly as `hostedMountContext()` does. Those are NOT the same case as a WordPress payload * that is merely silent on context, which falls to 'frontend' rather than reading the path - the old * code collapsed the two, so a patient on a hosted mount (`wpData` entirely absent) read as staff. */ const mountContext = wpData?.context ?? (!wpData && /^\/staff-portal(\/|$)/.test(window.location.pathname) ? 'staff' : 'frontend'); const staffContext = mountContext === 'admin' || mountContext === 'staff'; // Every page on the configured demo origin - entering, holding, declined or ended - always uses the stack it // was built for and leaves ordinary storage alone: the sandbox switch and the legacy session moves are ordinary // settings, and that origin offers no ordinary sign-in. const configuredDemoOrigin = STACKS[requestedStack].production ? undefined : bareHttpsOrigin(DEMO_APP_ORIGIN[requestedStack]); const demoTab = !!configuredDemoOrigin && typeof window !== 'undefined' && window.location.origin === configuredDemoOrigin; const inSandboxMode = staffContext && !demoTab && sandboxSibling !== undefined && readSandboxMode(localStorage) === 'sandbox'; const stack = inSandboxMode ? STACKS[sandboxSibling as StackName] : STACKS[requestedStack]; // The physical portal comes from the same effective context the mount renders, so a hosted patient page never reads // or writes the staff namespace. Legacy and stack-prefixed token keys are left where they are and never moved. const sessionPortal = sessionPortalForContext(mountContext === 'frontend' ? 'frontend' : 'staff'); const credentialStack = sessionStorageKeyPrefix(stack); export const environment = { ...stack, pluginSlot: 'akashic-health-records-patient-portal', wpData, demoAppOrigin: DEMO_APP_ORIGIN[requestedStack], demoLandingOrigins: DEMO_LANDING_ORIGINS[requestedStack] ?? [], demoCognitoClients: DEMO_COGNITO_CLIENTS[requestedStack], demoTurnstileSiteKey: STACKS[requestedStack].production ? undefined : DEMO_TURNSTILE_SITE_KEY[requestedStack], localStoragePrefix: portalStorageKeyPrefix(credentialStack, sessionPortal), /** The identity pool and clients a credential record belongs to, and the portal it is held for. */ credentialStack, sessionPortal, /** The stack namespace on its own - what the legacy keys used, and what a migration reads. */ stackStoragePrefix: stackStorageKeyPrefix(stack), sandboxAvailable: staffContext && sandboxSibling !== undefined, inSandboxMode, /** * Where a hosted address is resolved, in every mode. An address names the live practice and the * sandbox holds none, so a lookup sent there answers "nobody holds this" and the mount leaves for * the root site with the mode still set. */ liveFhirBaseApi: STACKS[requestedStack].fhirBaseApi, /** This stack's own FHIR foundation host, where sandbox-export is asked for. */ liveFhirFoundationApi: STACKS[requestedStack].fhirFoundationApi, /** The sibling sandbox's FHIR foundation base, known while still on live so Enter can ask it whether the practice has arrived. */ sandboxFhirFoundationApi: sandboxSibling === undefined ? undefined : STACKS[sandboxSibling].fhirFoundationApi, };