Skip to content

Commit 4b51fab

Browse files
dalkiaclaude
andcommitted
fix: rewrite {version}/assets/ requests to the sidecar's per-entity JIT lane
The flat /assets/{file} lane only serves bundles already in the sidecar's index: anything rebuilt or not yet built after a scene edit 404s, since only the legacy {version}/{entity}/{file} lane resolves digests tolerantly and JIT-builds on miss. Rewrite there instead, deriving the entity id the same way the preview content server does and dropping the deps digest from the file name (bare b64 hashes never contain '_'). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 6602368 commit 4b51fab

3 files changed

Lines changed: 33 additions & 12 deletions

File tree

packages/@dcl/sdk-commands/src/commands/start/server/asset-bundles-proxy.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Router } from '@well-known-components/http-server'
22
import { Readable } from 'stream'
33

44
import { CliComponents } from '../../../components'
5+
import { Workspace } from '../../../logic/workspace-validations'
6+
import { b64UrlHashingFunction } from '../../../logic/project-files'
57
import { PreviewComponents } from '../types'
68

79
/**
@@ -18,8 +20,13 @@ import { PreviewComponents } from '../types'
1820
export function setupAssetBundlesProxy(
1921
components: Pick<CliComponents, 'fetch'>,
2022
router: Router<PreviewComponents>,
21-
getSidecarUrl: () => string | undefined
23+
getSidecarUrl: () => string | undefined,
24+
workspace: Workspace
2225
) {
26+
// Same derivation the preview content server uses for the scene's entity id
27+
// (endpoints.ts getSceneJson). Multi-project workspaces keep the first project's
28+
// id: local scene development previews a single scene.
29+
const sceneId = b64UrlHashingFunction(workspace.projects[0].workingDirectory)
2330
router.all('/optimized-assets/:path+', async (ctx) => {
2431
const sidecarUrl = getSidecarUrl()
2532
if (!sidecarUrl) {
@@ -43,11 +50,15 @@ export function setupAssetBundlesProxy(
4350
const rawPath = Array.isArray(ctx.params.path) ? ctx.params.path.join('/') : ctx.params.path
4451

4552
// Explorer requests v49+ scene bundles by their digest-bearing file name under the
46-
// CDN's shared {version}/assets/ prefix (unity-explorer#9442). The sidecar serves
47-
// those same files through its flat /assets/{file} lane (bundle-index lookup) but
48-
// has no version-prefixed route, so strip the version segment on the way through.
53+
// CDN's shared {version}/assets/ prefix (unity-explorer#9442). The sidecar has no
54+
// such route — its digest-tolerant, JIT-building lane is the legacy per-entity one —
55+
// so rewrite to {version}/{sceneId}/{hash}_{platform}, dropping the deps digest
56+
// (bare b64 hashes never contain '_', so the digest segment is unambiguous).
4957
// TODO: drop once abgen serves GET /{version}/assets/{file} natively.
50-
const path = rawPath.replace(/^v\d+\/assets\//, 'assets/')
58+
const path = rawPath.replace(
59+
/^(v\d+)\/assets\/(.+?)(?:_[0-9a-f]{32})?(_(?:windows|mac|linux)(?:\.br)?)$/,
60+
(_match, version, hash, platform) => `${version}/${sceneId}/${hash}${platform}`
61+
)
5162

5263
const response = await components.fetch.fetch(`${sidecarUrl}/${path}${ctx.url.search}`, {
5364
headers: requestHeaders,

packages/@dcl/sdk-commands/src/commands/start/server/routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export async function wireRouter(
2121
const router = new Router<PreviewComponents>()
2222

2323
if (getAssetBundlesSidecarUrl) {
24-
setupAssetBundlesProxy(components, router, getAssetBundlesSidecarUrl)
24+
setupAssetBundlesProxy(components, router, getAssetBundlesSidecarUrl, workspace)
2525
}
2626

2727
if (dataLayer) {

test/sdk-commands/commands/start/asset-bundles-proxy.spec.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { setupAssetBundlesProxy } from '../../../../packages/@dcl/sdk-commands/src/commands/start/server/asset-bundles-proxy'
2+
import { b64UrlHashingFunction } from '../../../../packages/@dcl/sdk-commands/src/logic/project-files'
3+
4+
const workspace = { projects: [{ workingDirectory: '/tmp/scene' }] } as any
5+
const sceneId = b64UrlHashingFunction('/tmp/scene')
26

37
function makeProxy(getSidecarUrl: () => string | undefined) {
48
const fetch = jest.fn()
59
let handler: (ctx: any) => Promise<any>
610
const router = { all: jest.fn((_path: string, h: any) => (handler = h)) }
7-
setupAssetBundlesProxy({ fetch: { fetch } } as any, router as any, getSidecarUrl)
11+
setupAssetBundlesProxy({ fetch: { fetch } } as any, router as any, getSidecarUrl, workspace)
812
// requests reach the handler with the /optimized-assets prefix already
913
// consumed by the route pattern: ctx.params.path carries the rest
1014
const dispatch = (method: string, path: string, search = '', body?: any, headers: Record<string, string> = {}) =>
@@ -68,16 +72,22 @@ describe('start/server/asset-bundles-proxy', () => {
6872
await expect(readBody(response.body)).resolves.toBe('bundle-bytes')
6973
})
7074

71-
it('rewrites the version-prefixed assets/ shape to the flat assets/ lane the sidecar serves', async () => {
75+
it('rewrites digest-bearing {version}/assets/ requests to the legacy per-entity lane, dropping the digest', async () => {
7276
const { fetch, dispatch } = makeProxy(() => 'http://127.0.0.1:53211')
7377
fetch.mockResolvedValue(new Response('bundle-bytes', { status: 200 }))
7478

7579
await dispatch('GET', 'v49/assets/b64-abc_7580fefaf1c77b8b771687a8a4f86063_mac')
7680

77-
expect(fetch).toHaveBeenCalledWith(
78-
'http://127.0.0.1:53211/assets/b64-abc_7580fefaf1c77b8b771687a8a4f86063_mac',
79-
expect.anything()
80-
)
81+
expect(fetch).toHaveBeenCalledWith(`http://127.0.0.1:53211/v49/${sceneId}/b64-abc_mac`, expect.anything())
82+
})
83+
84+
it('rewrites digest-less {version}/assets/ requests to the legacy per-entity lane', async () => {
85+
const { fetch, dispatch } = makeProxy(() => 'http://127.0.0.1:53211')
86+
fetch.mockResolvedValue(new Response('bundle-bytes', { status: 200 }))
87+
88+
await dispatch('GET', 'v49/assets/b64-abc_windows.br')
89+
90+
expect(fetch).toHaveBeenCalledWith(`http://127.0.0.1:53211/v49/${sceneId}/b64-abc_windows.br`, expect.anything())
8191
})
8292

8393
it('forwards non-GET methods with their body and the sidecar status', async () => {

0 commit comments

Comments
 (0)