Client/Server: Initial session interactions - #574
Conversation
# Please enter a commit message to explain why this merge is necessary, # especially if it merges an updated upstream into a topic branch. # # Lines starting with '#' will be ignored, and an empty message aborts # the commit.
Test Results1 554 tests ±0 1 548 ✅ ±0 2m 25s ⏱️ +35s Results for commit b970395. ± Comparison against base commit 0557a24. This pull request removes 16 and adds 16 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
| let launchSession (env: AppEnv) (token: SessionLaunchToken) : Async<Result<SessionRedeemToken, string>> = | ||
| // TODO: implement proper session launch logic, | ||
| // which checks the validity of the token and returns a redeem token if valid, or an error if invalid. | ||
| let (SessionLaunchToken token) = token | ||
| async { return Ok(SessionRedeemToken "test-redeem-token") } | ||
|
|
||
| let redeemSession (env: AppEnv) (token: SessionRedeemToken) : Async<Result<SessionContent, string>> = | ||
| async { | ||
| let (SessionRedeemToken token) = token | ||
|
|
||
| if token = "demo-error" then |
There was a problem hiding this comment.
Session Tokens Are Unvalidated
These remotely exposed operations accept effectively arbitrary tokens. launchSession ignores the supplied launch token and always returns test-redeem-token, while redeemSession accepts every token except demo-error and returns session identity and patient content. Because the remoting API has no separate authentication gate, any remote caller can create and redeem a session without proving identity or permissions. These endpoints must fail closed until redeem tokens are validated, bound to a session, and protected against replay.
How this was verified: The public remoting API routes caller-supplied tokens directly to functions that issue a fixed token and return session content without an authentication check.
Knowledge Base Used:
There was a problem hiding this comment.
This is a stub, not the final production code. The real production code will follow: docs/scenarios/integration/GenPRES-MainEHR-Integration-V8.md
There was a problem hiding this comment.
Understood. Since this is explicitly a scaffold and the token derivation/validation is deferred to docs/scenarios/integration/GenPRES-MainEHR-Integration-V8.md, I would treat this as a release-risk reminder rather than a defect in the intended production flow. The key constraint is that these stubs must not be exposed as production authentication: until the documented implementation lands, the endpoints should remain clearly non-production or fail closed.
| let state = | ||
| { state with | ||
| SessionContext = SessionContext.Content(Resolved content) | ||
| Patient = Some content.Patient | ||
| } | ||
|
|
||
| state, Cmd.none |
There was a problem hiding this comment.
Redeeming Leaves Stale Results
A successful redemption assigns content.Patient directly instead of using the existing UpdatePatient lifecycle. When the redeemed patient differs from the patient currently in state, the order context, order plan, nutrition plan, formulary, and parenteralia remain associated with the previous patient because none of them are reset or reloaded. This can display clinically stale results beside the newly redeemed patient.
Knowledge Base Used:
There was a problem hiding this comment.
This will be part of a different pathway. The existing pathway only is meant for anonymous use and will be separate from the pathway this applies to.
There was a problem hiding this comment.
That distinction addresses the concern. If the redeemed-session flow always starts without the anonymous patient/order state, assigning content.Patient directly cannot leave anonymous results associated with it. The stale-state risk would only apply if a session could be redeemed after an existing patient had already been loaded in the same app instance. No change is needed for the separate pathways described here.
| | SessionContextMsg(SessionContextMsg.Launched(SessionRedeemToken token)) -> | ||
| state, Cmd.navigate ("session", [ "redeem", token ]) |
There was a problem hiding this comment.
Launch and redemption completions are not tied to the session request that is still current. If the user changes the URL or starts another session while an earlier request is delayed or awaiting the server, the older Launched result still navigates to its redeem URL and the older Redeemed result still overwrites the active session and patient. Carry the initiating token or another request identity through completion and ignore results that no longer match the current session.
Knowledge Base Used: Prescribing client workflows
There was a problem hiding this comment.
Work in progress but good to note. Now just a stub and not real working code.
| Cmd.batch | ||
| [ | ||
| Cmd.ofMsg (UpdatePatient pat) | ||
| match sessionInfo with | ||
| | SessionContext.Launching token -> Cmd.ofMsg (SessionContextMsg(SessionContextMsg.Launch token)) | ||
| | SessionContext.Redeeming token -> Cmd.ofMsg (SessionContextMsg(SessionContextMsg.Redeem token)) | ||
| | _ -> Cmd.none |
There was a problem hiding this comment.
Anonymous Routes Retain Identity
An anonymous URL change does not reset state.SessionContext. After leaving a redeemed session, the title bar can therefore continue showing the previous user's name even though the new route was parsed as anonymous. Reset the session context to SessionContext.Anonymous during this transition so the displayed identity matches the active route.
Knowledge Base Used: Prescribing client workflows
There was a problem hiding this comment.
@Zaid-Ajaj this is something that needs to fixed, I think.
halcwb
left a comment
There was a problem hiding this comment.
I think the comment on the problem of session context still showing user if switching to anonymous should work in this PR.
|
@Zaid-Ajaj I think we should first agree on the implementation plan and then fix this pr. @7sharp9 if you can also have a look? Thanks! |
| | Interactions | ||
| | Settings | ||
|
|
||
| [<RequireQualifiedAccess>] |
There was a problem hiding this comment.
SessionContext is doing two jobs. parseSessionInfo : string list -> SessionContext can only ever return Anonymous | Launching | Redeeming, but the type also carries Error of string and Content of Deferred<SessionContent>.
This could benefit from splitting the responsibilities
|
|
||
| | UrlChanged sl -> | ||
| let pat, page, lang, discl, med = sl |> parseUrl | ||
| | UrlChanged newUrl -> |
There was a problem hiding this comment.
UrlChanged overwrites Patient unconditionally with parsePatient newUrl.
After a redeem sets Patient = Some content.Patient, the next navigation to any non-patient route parses None and wipes it.
Shouldn't the redeemed patient win? or the redeem flow should put the patient into the URL like the anonymous flow does?
| let parsePatient (urlParts: string list) = | ||
| urlParts | ||
| |> List.pairwise | ||
| |> List.tryFind (fun (a, b) -> a = "patient") |
Description
This PR contains the initial session interactions implementation between client and server. It scaffolds a skeleton for the launch sequence of GenPRES from an external application via the
/session?launch={token}. Received on the client initially, it is transferred to the server from which the redeem token is derived and sent back to the client. Afterwards, the client uses the redeem token to acquire session information.The idea is that subsequent operations on the client will carry a redeem token to validate who can do what and when.
The actual derivation, name and validation of the redeem token is stubbed out for now. As well as the functionality to retrieve the patient information.
When there is no launch token, the session is anonymous and continues to work the way it used to be; being able to parse the patient info still from the initial URL.
Finally, this PR fixes an issue in
Feliz.Routerwhere it never triggeredUrlChangedbecause the dispatch function it acquires was not stable. This is now resolved (with a bit of help from Claude)