Skip to content

Make sure Scratch API calls use renewed tokens#1441

Open
zetter-rpf wants to merge 5 commits intomainfrom
better-token-renewal
Open

Make sure Scratch API calls use renewed tokens#1441
zetter-rpf wants to merge 5 commits intomainfrom
better-token-renewal

Conversation

@zetter-rpf
Copy link
Copy Markdown
Contributor

@zetter-rpf zetter-rpf commented Apr 20, 2026

Closes: https://github.com/RaspberryPiFoundation/digital-editor-issues/issues/1197

We periodically renew tokens (every hour in production). We should make sure Scratch has access to the latest token otherwise someone who has kept the window open working on their project may loose their data.

This PR address a few related parts:

  • Don't reload projects when token refreshes. Previously the effect that loaded projects ran each time the token renewed, causing the page to flash and progress to be lost. This affected non-scratch projects too.
  • Make sure we store the up to date token in Redux even if there are no dispatches. Previously we were using a middleware to update the token, but this didn't work in Scratch where actions were performed in a different iframe.
  • Make sure we send updated tokens to the Scratch iframe

See commits for more.

A note on reproducing locally:

By default oidc-client-ts renews the token when it has 60s left, and our tokens last for an hour. To avoid waiting an hour, you can update TTL_ACCESS_TOKEN and TTL_REFRESH_TOKEN in the profile docker-compose.yml. I recommend setting them to 90s and 120s which will cause a refresh every 30s.

I also had to temporally comment out "Cross-Origin-Embedder-Policy": "require-corp", from editor standalone's craco.config.js in order for token refresh to work locally.

@zetter-rpf zetter-rpf temporarily deployed to previews/1441/merge April 20, 2026 11:08 — with GitHub Actions Inactive
@zetter-rpf zetter-rpf force-pushed the better-token-renewal branch from e0107e5 to 25f91cd Compare April 21, 2026 15:40
@zetter-rpf zetter-rpf temporarily deployed to previews/1441/merge April 21, 2026 15:41 — with GitHub Actions Inactive
We expect the access access token to change when it's periodically renewed. We should not reload the whole project when this happens as it will cause the loading state to flash and  the project to be loaded again. This also has might loose any unsaved work.

I'm not sure if it's needed, but i've made sure the effect still runs if the access token goes from unset to set or set to unset.
@zetter-rpf zetter-rpf force-pushed the better-token-renewal branch from 25f91cd to 26aacd0 Compare April 22, 2026 09:27
@zetter-rpf zetter-rpf temporarily deployed to previews/1441/merge April 22, 2026 09:27 — with GitHub Actions Inactive
@zetter-rpf zetter-rpf force-pushed the better-token-renewal branch from 26aacd0 to 6f956c1 Compare April 22, 2026 09:33
@zetter-rpf zetter-rpf temporarily deployed to previews/1441/merge April 22, 2026 09:33 — with GitHub Actions Inactive
@zetter-rpf zetter-rpf changed the title Don't reload project when access token changes Make sure Scratch API calls use renewed tokens Apr 22, 2026
@zetter-rpf zetter-rpf marked this pull request as ready for review April 22, 2026 09:53
Copilot AI review requested due to automatic review settings April 22, 2026 09:53
@zetter-rpf zetter-rpf force-pushed the better-token-renewal branch from 6f956c1 to 77c2ff9 Compare April 22, 2026 09:58
@zetter-rpf zetter-rpf temporarily deployed to previews/1441/merge April 22, 2026 09:58 — with GitHub Actions Inactive
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR ensures long-lived editor sessions (especially Scratch-in-iframe) keep using the latest renewed access token, while avoiding unintended project reloads when tokens refresh.

Changes:

  • Prevent project reloads on token refresh by making project-loading effects depend on “token present” rather than token value.
  • Replace the localStorage user-sync middleware with explicit initial user load + a polling hook to keep Redux auth state updated.
  • Add a new scratch-gui-update-token message flow so the parent can push refreshed tokens into the Scratch iframe.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/web-component.js Dispatches initial auth user into Redux from localStorage at mount time.
src/scratch.jsx Centralizes/standardizes parent origin validation via allowedParentOrigin().
src/redux/stores/WebComponentStore.js Removes the prior localStorage user middleware from the store setup.
src/redux/middlewares/localStorageUserMiddleware.js Removes middleware previously used to sync user from localStorage on editor actions.
src/redux/middlewares/localStorageUserMiddleware.test.js Removes tests for the deleted middleware.
src/hooks/useSyncUserFromLocalStorage.js Adds polling hook to keep Redux auth user in sync with localStorage changes.
src/hooks/useProject.js Stops reloading project data on token refresh by depending on hasAccessToken instead of accessToken.
src/hooks/useProject.test.js Adds tests for “load when token becomes available” and “don’t reload on token change.”
src/containers/WebComponentLoader.jsx Switches to Redux-only user source + starts localStorage polling hook.
src/containers/WebComponentLoader.test.js Updates tests to reflect new auth sync behavior.
src/components/ScratchEditor/events.js Exposes allowedParentOrigin() and uses it in postScratchGuiEvent.
src/components/ScratchEditor/ScratchIntegrationHOC.jsx Silences warnings for the new scratch-gui-update-token message type.
src/components/ScratchEditor/ScratchEditor.jsx Listens for scratch-gui-update-token and updates Scratch fetch metadata.
src/components/ScratchEditor/ScratchEditor.test.jsx Adds test asserting Scratch metadata updates on token update message.
src/components/Editor/Project/ScratchContainer.jsx Posts scratch-gui-update-token to the iframe when auth token changes.
src/components/Editor/Project/ScratchContainer.test.js Adds coverage for scratch-gui-update-token posting behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/web-component.js
Comment thread src/containers/WebComponentLoader.test.js
Comment thread src/components/Editor/Project/ScratchContainer.test.js
Comment thread src/components/Editor/Project/ScratchContainer.jsx
Comment thread src/components/ScratchEditor/ScratchEditor.jsx Outdated
Comment thread src/containers/WebComponentLoader.jsx
Comment thread src/redux/stores/WebComponentStore.js
Comment thread src/hooks/useSyncUserFromLocalStorage.js
The user of the web component is responsible for managing the auth token. Currently we have use a custom middleware which runs before certain actions to sync the user data.

The problem with this approach is that if there are no actions dispatched then the user data will become out of date and requests will fail. This is a problem for Scratch which runs in a separate iframe and does not trigger actions in the root window before saving.

Instead, set syncing that runs every 45 seconds to check that the user data is in sync. I've chosen to key the changes off the access token as that's the most important object and I found that the  whole user data object always failed comparisons.

I've chosen 45 seconds as by default our oidc client renews tokens 60 seconds before they expire.

As part of this I've moved the initial setUser call into web-component.js so we don't need to re-render the component the first time the user loads.

Note more will need to be done in the next commit to pass this updated token to Scratch.

An alternative to this would be to pass the user data as props. This would be a breaking change for users of the web component which I why I've preferred this approach.
In the next commit I'm going to introduce another message so make sure we're checking the correct ones
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.

2 participants