Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Handle server side redirects #586

Merged
merged 1 commit into from
Nov 17, 2023
Merged

Conversation

Viicos
Copy link
Contributor

@Viicos Viicos commented Nov 2, 2023

Copy link

codecov bot commented Nov 2, 2023

Codecov Report

Attention: 4 lines in your changes are missing coverage. Please review.

Comparison is base (dd342fd) 71.27% compared to head (54d1198) 73.29%.

Files Patch % Lines
src/components/routingActions.js 71.42% 1 Missing and 1 partial ⚠️
src/sdk.js 92.59% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #586      +/-   ##
==========================================
+ Coverage   71.27%   73.29%   +2.01%     
==========================================
  Files         211      212       +1     
  Lines        4286     4314      +28     
  Branches     1161     1168       +7     
==========================================
+ Hits         3055     3162     +107     
+ Misses       1196     1108      -88     
- Partials       35       44       +9     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

src/index.js Outdated Show resolved Hide resolved
src/index.js Outdated Show resolved Hide resolved
@Viicos
Copy link
Contributor Author

Viicos commented Nov 8, 2023

Maybe we should add an action for form steps as well: action: form, action_args: <form_step>

Edit: this is already the case 🤦

@sergei-maertens sergei-maertens force-pushed the issue/3362-hash-redirects branch from 1724c36 to 1521d31 Compare November 13, 2023 08:15
Copy link
Member

@sergei-maertens sergei-maertens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found my unpublished comment 😬

src/sdk.js Outdated Show resolved Hide resolved
src/sdk.js Outdated
Comment on lines 128 to 130
newUrl = `${window.location.origin}${window.location.pathname}/${redirectPath}${
query.size ? '?' + query : ''
}`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if redirectPath has query params and there's an uncontrolled query param like foo=bar from the CMS, you will end up with:

https://example.com/base-path/stap/step-1?submissionId=123?foo=bar

which is of course not the correct form. I think getRedirectPathname should return an object with the URL parts:

{
  "path": relativePath,
  "query": queryObj,
}

and then depending on the useHashRouting compose the final URL in this method.

Note that this (probably) works correctly with hash-based routing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if redirectPath has query params and there's an uncontrolled query param like foo=bar from the CMS, you will end up with:

But shouldn't we keep unrelated query params? If the CMS needs foo to work correctly and we strip it off, it can break things, can it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes we need to keep it, but now the implementation is naive in that there will only be before-hash query params or after-hash query params while both can be present at the same time - the problem is the double ? in the resulting URL if you don't use hash based routing.

So you need to:

  1. Server loads URL with query params, possibly with CMS-required params
  2. SDK initializes
  3. SDK does pick<query, '_of_action' | '_of_action_args'>
  4. SDK processes action & action args into the (relative) path & SDK-specific query params
  5. SDK builds a new URL:
    • Join basePath + relative path
    • Merge (original) params with SDK-specific query params from 4.
    • Delete _of_action and `_of_action_args params
  6. SDK does pushState with newly constructed URL
    • it retains the "unrelated" query params
    • it drops the private _action routing params
    • it merges in any query params required by the SDK (like submission_uuid and ?productId=... for appointments)

The current implementation fails at 5.2 - the merging is not done properly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the new implementation should cover this

src/components/routingActions.js Outdated Show resolved Hide resolved
src/sdk.spec.js Outdated Show resolved Hide resolved
src/sdk.spec.js Outdated Show resolved Hide resolved
src/sdk.js Outdated Show resolved Hide resolved
src/sdk.spec.js Outdated Show resolved Hide resolved
src/sdk.spec.js Outdated Show resolved Hide resolved
src/sdk.spec.js Outdated
Comment on lines 189 to 230
'/?_of_action=stap&_of_action_params=next_step%3Dstep-1&unrelated_q=1',
'http://localhost/?unrelated_q=1#/startpagina', // SDK redirects to start page
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's odd, shouldn't it go to ...#/stap/step-1?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect the api mocks to not cover an api call, so it goes back to the start page as it doesn't know about step-1 but I'm not sure about that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit, figured out while adding tests:

it.each([
// With a base path:
[
'/base-path/?_of_action=afspraak-annuleren&unrelated_q=1',
'http://localhost/base-path?unrelated_q=1#/afspraak-annuleren',
],
[
'/base-path/?_of_action=afspraak-maken&unrelated_q=1',
'http://localhost/base-path?unrelated_q=1#/afspraak-maken',
],
[
'/base-path/?_of_action=cosign&_of_action_params=submission_uuid%3Dabc&unrelated_q=1',
'http://localhost/base-path?unrelated_q=1#/cosign?submission_uuid=abc',
],
[
'/base-path/?_of_action=resume&_of_action_params=next_step%3Dstep-1&unrelated_q=1',
'http://localhost/base-path?unrelated_q=1#/stap/step-1',
],
// Without a base path:
[
'/?_of_action=afspraak-annuleren&unrelated_q=1',
'http://localhost/?unrelated_q=1#/afspraak-annuleren',
],
[
'/?_of_action=afspraak-maken&unrelated_q=1',
'http://localhost/?unrelated_q=1#/afspraak-maken/producten', // SDK redirects to producten
],
[
'/?_of_action=cosign&_of_action_params=submission_uuid%3Dabc&unrelated_q=1',
'http://localhost/?unrelated_q=1#/cosign?submission_uuid=abc',
],
[
'/?_of_action=resume&_of_action_params=next_step%3Dstep-1&unrelated_q=1',
'http://localhost/?unrelated_q=1#/startpagina', // SDK redirects to start page
],
])(

The behavior is different depending on having a basepath or not with hash routing, something might be going wrong

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please investigate further what's causing this 😬

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested locally:

When accessing localhost:3000/?_of_action=resume&_of_action_params=%7B%22next_step%22%3A%22test-step%22%7D ($\Leftrightarrow$ localhost:3000/?_of_action=resume&_of_action_params=encodeURIComponent(JSON.stringify({next_step: 'test-step'})), I am redirected to startpagina as expected, because I haven't started the form (it works and I get redirected to http://localhost:3000/#/stap/test-step otherwise).

So I think having a basepath breaks for now. Looking into it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so the resume flow relies on the submission_id (query) param, as that gets loaded from the backend first before the actual step is rendered. Many of these components are wrapped in a <RequiredSubmission> component/decorator.

Probably best to manually test the behaviour with a real submission, it doesn't make sense to redirect to a form/submission that's not in your session anyway.

Copy link
Contributor Author

@Viicos Viicos Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ended up being a bit more complicated:
We have to differentiate between the react router base path and the "real" browser base path with hash based routing. In hash based routing, router base path will always be "", but we need the extra browserBasePath to be able to correctly set this.clientBaseUrl, as the CMS basepath needs to be taken into account

@Viicos Viicos force-pushed the issue/3362-hash-redirects branch from 19a8de1 to c87f004 Compare November 15, 2023 11:29
@Viicos
Copy link
Contributor Author

Viicos commented Nov 15, 2023

Note for later: URLSearchParams is treating %2B the wrong way: %2B is +, and + is being replaced by once decoded. It should stay as +

The backend is now agnostic to URL routing/hash based routing - it will always
redirect back to the URL that was used to start the form and provide action &
action params query arguments that specify the target for the frontend.

The frontend parses this action & the params and maps it to the intended client
side routes, effectively decoupling implementation details between backend and
frontend.

Note that this requires the SDK to operate correctly in two of the tree main
steps in this flow:

1. SDK must correctly derive the 'base URL' for the form, irrespective of
   whether hash based routing is used or not. Fragments should *not* be sent
   to the backend, since they are ignored anyway.
2. The backend uses the URL supplied from 1. and append the action/action
   params from the context of the backend action/validation that was
   performed.
3. The SDK must correctly interpret the action and its params and route to
   the appropriate part of the application.

TODO: using this pattern, we can probably refactor the _start=1 flow from the
backend too, this can likely be converted to _of_action=startSubmission.
@sergei-maertens sergei-maertens force-pushed the issue/3362-hash-redirects branch from a939e9d to 54d1198 Compare November 17, 2023 12:12
src/components/routingActions.js Outdated Show resolved Hide resolved
@sergei-maertens sergei-maertens merged commit 59447c0 into main Nov 17, 2023
15 checks passed
@sergei-maertens sergei-maertens deleted the issue/3362-hash-redirects branch November 17, 2023 12:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Server side redirects to the frontend break when the SDK uses hash based routing
2 participants