Skip to content

Stop proliferation of levelbuilder sourceUrls in level animation JSON#67763

Merged
mikeharv merged 3 commits into
stagingfrom
mike/levelbuilder-sourceUrl
Aug 18, 2025
Merged

Stop proliferation of levelbuilder sourceUrls in level animation JSON#67763
mikeharv merged 3 commits into
stagingfrom
mike/levelbuilder-sourceUrl

Conversation

@mikeharv

@mikeharv mikeharv commented Aug 14, 2025

Copy link
Copy Markdown
Contributor

Last week, @breville shared an issue of a level on test failing to load animations. This coincided with levelbuilder being down. Our p5 levels (Game Lab, Sprite Lab) support a custom animation JSON field. This level, and many others have animations with a levelbuilder URL in their sourceUrl, which causes an undesirable dependency. For example, the level at https://test-studio.code.org/courses/coursee-2019/units/1/lessons/9/levels/6?noautoplay=true&no_redirect=true depends upon the image at https://levelbuilder-studio.code.org/api/v1/animation-library/wzaShBj3E3JC6ynAQTv56K3MIRlpF19i/category_characters/kid_outline.png

Before fixing existing levels with this problem, this branch aims to prevent it from happening more. I looked at the two ways that levelbuilders are known to author this JSON. I also added some validation to explicitly prevent a level from being saved if it contains a levelbuilder URL in its animation JSON.

Extra Links Option

The older of the two methods for authoring animation JSON is also the most flexible. Using the link, the author can view the full animation JSON for any level or project simply by visiting it. They can then copy and paste it into other levels. This is available on all environments to users with sufficient privileges.

Currently, this pop-up shows absolute URLs. If you add animation from the library to a project on levelbuilder, you might see something like:

"name": "kid_outline_1",
      "sourceUrl": "https://levelbuilder-studio.code.org/api/v1/animation-library/spritelab/9j7wtr58woBSBVOZqrWQ66Msi.JqL6vF/category_people/kid_outline.png",

If this gets pasted into a level file which is loaded on another environment, we wrap it our media proxy to make sure it can be loaded without a CORS violation. At that point you might see something like:

"name": "kid_outline_1",
      "sourceUrl": "https://studio.code.org/media?u=https%3A%2F%2Flevelbuilder-studio.code.org%2Fapi%2Fv1%2Fanimation-library%2F9j7wtr58woBSBVOZqrWQ66Msi.JqL6vF%2Fcategory_people%2Fkid_outline.png"

The relative URL we should actually show in both of these cases is just /api/v1/animation-library/spritelab/9j7wtr58woBSBVOZqrWQ66Msi.JqL6vF/category_people/kid_outline.png
However, this is only safe because /api/v1/animation-library/ tells us it's a library animation. These relative links should be available on any environment (prod/test/levelbuilder/localhost), and because they're relative, the lab won't need to proxy them.

If the animation is not from the library, we still need the full absolute URL. This includes images at images.code.org and curriculum.code.org (seldom used, but supported), or studio links that include v3/animations in the path.

The v3/animations path is for animations that were created (drawn or uploaded) or modified on the environment itself, ie. using the Piskel editor in Game Lab or Sprite Lab. A relative path will only work on the environment where the image exists in this case, so we need to save the full URL. This does imply the undesirable dependency still exists, but the only way around that is to update the levels themselves. This is follow-up work.

Changes:

  1. Proxy unwrap
    If we detect that the URL includes our proxy prefix, we'll split the URL and decode it, displaying the original URL.

  2. Preserving relative URLs for animation library assets
    If the path indicates it's a library asset, we'll split it at the domain (e.g. levelbuilder-studio.code.org or studio.code.org) and just return the later part.

  3. Absolute URLs for non-library assets
    If it's not a library asset, we guarantee a properly formatter URL based on the current environment. This is the pre-existing anchor tag logic and is only used when props.sourceUrl doesn't exist and we end up with a raw relative URL. This is the path we'd take for animations that uploaded directly into the project, for example.

Screenshots

The screenshots compare the JSON for two animations, one created by the user and one from the library (a bear).
image

Before (Production)

The user animation has a full production link with version. The bear animation url is also absolute and includes a proxied version of the animation's actual source URL.
image

Before (Levelbuilder)

Same as above, except the absolute URL for includes levelbuilder-studio.
image

After (Localhost)

The user animation still has a full URL, out of necessity. The bear animation has a simple relative URL for the un-proxied original source. If added to a level, this will work equally well on any environment without creating a cross-environment dependency.
image

Lesson Edit Page

The lesson edit page includes a JSON field for specify animations. Even with the changes above, it's possible that we will still need to show a levelbuilder URL - if the user created the image on the levelbuilder environment. It's also possible to type anything into this field, so it makes sense that we should validate against the URLs we want to prevent.

We were already validating for malformed JSON so it was straight-forward to add a check for source URLs that include https://levelbuilder-studio.code.org

image

Asset Management tools

Levelbuilder has a set of tools available (at /sprites) which allow authors to upload/replace animations, create new JSON blobs for levels, and to update Sprite Lab's default costumes and backgrounds. Fortunately, the relevant tool ("Generate Animation JSON for a level" /sprites/select_start_animations) only provides relative URLs already. No functional changes were needed here but I did take the opportunity to add some nicer formatting to the output text.

Before:
image (341)

After:
image (342)

@katiejofr supported these changes:

YES! For sure. I have needed to sift through that raw text before to find something. It would be much better if it were formatted

Links

Testing story

I had to update one test case to reflect that withAbsoluteSourceUrls no longer returns proxied URLs for absolute URLs. This function is only used for the pop-up that provides authors with the JSON to copy. It doesn't change any actual app functionality.

Deployment strategy

Follow-up work

Audit and update all p5 levels that currently have a levelbuilder dependency.

Privacy

Security

Caching

PR Creation Checklist:

  • Tests provide adequate coverage
  • Privacy impacts have been documented
  • Security impacts have been documented
  • Code is well-commented
  • New features are translatable or updates will not break translations
  • Relevant documentation has been added or updated
  • User impact is well-understood and desirable
  • Follow-up work items (including potential tech debt) are tracked and linked

@mikeharv mikeharv changed the title Mike/levelbuilder source url Stop proliferation of levelbuilder sourceUrls in level animation JSON Aug 18, 2025
@mikeharv mikeharv requested review from a team and breville August 18, 2025 19:11

@breville breville left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks very much for investigating and beginning to address this long-standing issue! Looking forward to the follow-up work as well.

@mikeharv mikeharv merged commit 9ad0fb8 into staging Aug 18, 2025
6 checks passed
@mikeharv mikeharv deleted the mike/levelbuilder-sourceUrl branch August 18, 2025 21:27
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