Skip to content

Commit 108fa71

Browse files
authored
fix: update endpoints for compliance (#519)
1 parent 3f2952c commit 108fa71

File tree

4 files changed

+118
-77
lines changed

4 files changed

+118
-77
lines changed

packages/@dcl/sdk-commands/package-lock.json

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@dcl/sdk-commands/src/commands/start/index.ts

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -100,23 +100,25 @@ export async function main(options: Options) {
100100
printWarning(options.components.logger, 'Support for multiple projects is still experimental.')
101101

102102
for (const project of workspace.projects) {
103-
printCurrentProjectStarting(options.components.logger, project, workspace)
104-
105-
// first run `npm run build`, this can be disabled with --skip-build
106-
// then start the embedded compiler, this can be disabled with --no-watch
107-
if (watch) {
108-
await buildScene({ ...options, args: { '--dir': project.workingDirectory, '--watch': true, _: [] } }, project)
109-
} else if (!skipBuild) {
110-
await buildScene({ ...options, args: { '--dir': project.workingDirectory, '--watch': false, _: [] } }, project)
111-
}
103+
if (project.kind === 'scene') {
104+
printCurrentProjectStarting(options.components.logger, project, workspace)
105+
106+
// first run `npm run build`, this can be disabled with --skip-build
107+
// then start the embedded compiler, this can be disabled with --no-watch
108+
if (watch) {
109+
await buildScene({ ...options, args: { '--dir': project.workingDirectory, '--watch': true, _: [] } }, project)
110+
} else if (!skipBuild) {
111+
await buildScene({ ...options, args: { '--dir': project.workingDirectory, '--watch': false, _: [] } }, project)
112+
}
112113

113-
// track the event
114-
baseCoords = getBaseCoords(project.scene)
115-
options.components.analytics.track('Preview started', {
116-
projectHash: await b64HashingFunction(project.workingDirectory),
117-
coords: baseCoords,
118-
isWorkspace: false
119-
})
114+
// track the event
115+
baseCoords = getBaseCoords(project.scene)
116+
options.components.analytics.track('Preview started', {
117+
projectHash: await b64HashingFunction(project.workingDirectory),
118+
coords: baseCoords,
119+
isWorkspace: false
120+
})
121+
}
120122
}
121123

122124
printProgressInfo(options.components.logger, 'Starting preview server')

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

Lines changed: 98 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from '../../../logic/project-files'
1414
import { getCatalystBaseUrl } from '../../../logic/config'
1515
import { Workspace } from '../../../logic/workspace-validations'
16+
import { ProjectUnion, WearableProject } from '../../../logic/project-validations'
1617

1718
function smartWearableNameToId(name: string) {
1819
return name.toLocaleLowerCase().replace(/ /g, '-')
@@ -29,13 +30,11 @@ export async function setupEcs6Endpoints(
2930
) {
3031
const catalystUrl = new URL(await getCatalystBaseUrl(components))
3132

32-
const baseFolders = workspace.projects.map(($) => $.workingDirectory)
33-
34-
// handle old preview scene.json
33+
// handle old preview scene.json DEPRECATED
3534
router.get('/scene.json', async () => {
3635
return {
3736
headers: { 'content-type': 'application/json' },
38-
body: components.fs.createReadStream(path.join(baseFolders[0], 'scene.json'))
37+
body: components.fs.createReadStream(path.join(workspace.projects[0].workingDirectory, 'scene.json'))
3938
}
4039
})
4140

@@ -70,8 +69,7 @@ export async function setupEcs6Endpoints(
7069
const baseUrl = `${ctx.url.protocol}//${ctx.url.host}/content/contents`
7170

7271
try {
73-
const previewWearables = await getAllPreviewWearables(components, {
74-
baseFolders,
72+
const previewWearables = await getAllPreviewWearables(components, workspace, {
7573
baseUrl
7674
})
7775

@@ -144,29 +142,52 @@ export async function setupEcs6Endpoints(
144142

145143
// TODO: get workspace scenes & wearables...
146144

147-
await serveFolders(components, router, baseFolders)
145+
await serveFolders(components, router, workspace)
148146
}
149147

150148
async function serveFolders(
151149
components: Pick<CliComponents, 'fs' | 'logger' | 'fetch' | 'config'>,
152150
router: Router<PreviewComponents>,
153-
baseFolders: string[]
151+
workspace: Workspace
154152
) {
155153
const catalystUrl = await getCatalystBaseUrl(components)
156154

157-
router.get('/content/contents/:hash', async (ctx: any, next: any) => {
155+
router.get('/content/contents/:hash', async (ctx, next) => {
158156
if (ctx.params.hash && ctx.params.hash.startsWith('b64-')) {
159157
const fullPath = path.resolve(Buffer.from(ctx.params.hash.replace(/^b64-/, ''), 'base64').toString('utf8'))
160158

159+
// find a project that we are talking about. NOTE: this filter is not exhaustive
160+
// relative paths should be used instead
161+
const baseProject = workspace.projects.find((project) => fullPath.startsWith(project.workingDirectory))
162+
161163
// only return files IF the file is within a baseFolder
162-
if (!baseFolders.find((folder: string) => fullPath.startsWith(folder))) {
164+
if (!baseProject) {
163165
return next()
164166
}
165167

168+
if (path.resolve(fullPath) === path.resolve(baseProject.workingDirectory)) {
169+
// if we are talking about the root directory, then we must return the json of the entity
170+
const entity = await fakeEntityV3FromProject(components, baseProject, b64HashingFunction)
171+
172+
if (!entity) return { status: 404 }
173+
174+
return {
175+
headers: {
176+
'x-timestamp': Date.now().toString(),
177+
'x-sent': 'true',
178+
'cache-control': 'no-cache,private,max-age=1'
179+
},
180+
body: entity
181+
}
182+
}
183+
184+
if (!(await components.fs.fileExists(fullPath))) return { status: 404 }
185+
if (await components.fs.directoryExists(fullPath)) return { status: 404 }
186+
166187
return {
167188
headers: {
168-
'x-timestamp': Date.now(),
169-
'x-sent': true,
189+
'x-timestamp': Date.now().toString(),
190+
'x-sent': 'true',
170191
'cache-control': 'no-cache,private,max-age=1'
171192
},
172193
body: components.fs.createReadStream(fullPath)
@@ -185,7 +206,8 @@ async function serveFolders(
185206
pointers && typeof pointers === 'string' ? [pointers as string] : (pointers as string[])
186207
)
187208

188-
const resultEntities = await getSceneJson(components, baseFolders, Array.from(requestedPointers))
209+
const resultEntities = await getSceneJson(components, workspace, Array.from(requestedPointers))
210+
189211
const remote = fetchEntityByPointer(
190212
components,
191213
catalystUrl.toString(),
@@ -214,9 +236,8 @@ async function serveFolders(
214236

215237
router.get('/preview-wearables/:id', async (ctx) => {
216238
const baseUrl = `${ctx.url.protocol}//${ctx.url.host}/content/contents`
217-
const wearables = await getAllPreviewWearables(components, {
218-
baseUrl,
219-
baseFolders
239+
const wearables = await getAllPreviewWearables(components, workspace, {
240+
baseUrl
220241
})
221242
const wearableId = ctx.params.id
222243
return {
@@ -232,43 +253,49 @@ async function serveFolders(
232253
return {
233254
body: {
234255
ok: true,
235-
data: await getAllPreviewWearables(components, { baseUrl, baseFolders })
256+
data: await getAllPreviewWearables(components, workspace, { baseUrl })
236257
}
237258
}
238259
})
239260
}
240261

241262
async function getAllPreviewWearables(
242263
components: Pick<CliComponents, 'fs' | 'logger'>,
243-
{ baseFolders, baseUrl }: { baseFolders: string[]; baseUrl: string }
264+
workspace: Workspace,
265+
{ baseUrl }: { baseUrl: string }
244266
) {
267+
// NOTE: the explorers should use the /entities/active endpoint to retrieve the wearables. This endpoint should be removed
245268
const wearablePathArray: string[] = []
246-
for (const wearableDir of baseFolders) {
247-
const wearableJsonPath = path.resolve(wearableDir, 'wearable.json')
248-
if (await components.fs.fileExists(wearableJsonPath)) {
249-
wearablePathArray.push(wearableJsonPath)
269+
270+
for (const project of workspace.projects) {
271+
if (project.kind === 'wearable') {
272+
const wearableJsonPath = path.resolve(project.workingDirectory, 'wearable.json')
273+
if (await components.fs.fileExists(wearableJsonPath)) {
274+
wearablePathArray.push(wearableJsonPath)
275+
}
250276
}
251277
}
252278

253279
const ret: LambdasWearable[] = []
254-
for (const wearableJsonPath of wearablePathArray) {
280+
for (const project of workspace.projects) {
255281
try {
256-
ret.push(await serveWearable(components, wearableJsonPath, baseUrl))
282+
if (project.kind === 'wearable') ret.push(await serveWearable(components, project, baseUrl))
257283
} catch (err) {
258284
components.logger.error(
259-
`Couldn't mock the wearable ${wearableJsonPath}. Please verify the correct format and scheme.` + err
285+
`Couldn't mock the wearable ${project.workingDirectory}. Please verify the correct format and scheme.` + err
260286
)
261287
}
262288
}
289+
263290
return ret
264291
}
265292

266293
async function serveWearable(
267294
components: Pick<CliComponents, 'fs' | 'logger'>,
268-
wearableJsonPath: string,
295+
project: WearableProject,
269296
baseUrl: string
270297
): Promise<LambdasWearable> {
271-
const wearableDir = path.dirname(wearableJsonPath)
298+
const wearableJsonPath = path.join(project.workingDirectory, 'wearable.json')
272299
const wearableJson = JSON.parse((await components.fs.readFile(wearableJsonPath)).toString())
273300

274301
if (!WearableJson.validate(wearableJson)) {
@@ -278,8 +305,12 @@ async function serveWearable(
278305
throw new Error(`Invalid wearable.json (${wearableJsonPath})`)
279306
}
280307

281-
const projectFiles = await getProjectPublishableFilesWithHashes(components, wearableDir, b64HashingFunction)
282-
const contentFiles = projectFilesToContentMappings(wearableDir, projectFiles)
308+
const projectFiles = await getProjectPublishableFilesWithHashes(
309+
components,
310+
project.workingDirectory,
311+
b64HashingFunction
312+
)
313+
const contentFiles = projectFilesToContentMappings(project.workingDirectory, projectFiles)
283314

284315
const thumbnailFiltered = contentFiles.filter(($) => $.file === 'thumbnail.png')
285316
const thumbnail =
@@ -320,19 +351,20 @@ async function serveWearable(
320351

321352
async function getSceneJson(
322353
components: Pick<CliComponents, 'fs' | 'logger'>,
323-
projectRoots: string[],
354+
workspace: Workspace,
324355
pointers: string[]
325356
): Promise<Entity[]> {
326357
const requestedPointers = new Set<string>(pointers)
327358
const resultEntities: Entity[] = []
328359

329360
const allDeployments = await Promise.all(
330-
projectRoots.map(async (projectRoot) => fakeEntityV3FromFolder(components, projectRoot, b64HashingFunction))
361+
workspace.projects.map((project) => fakeEntityV3FromProject(components, project, b64HashingFunction))
331362
)
332363

333364
for (const pointer of Array.from(requestedPointers)) {
334365
// get deployment by pointer
335366
const theDeployment = allDeployments.find(($) => $ && $.pointers.includes(pointer))
367+
336368
if (theDeployment) {
337369
// remove all the required pointers from the requestedPointers set
338370
// to prevent sending duplicated entities
@@ -403,6 +435,10 @@ function serveStatic(components: Pick<CliComponents, 'fs'>, workspace: Workspace
403435
return next()
404436
}
405437

438+
if (await components.fs.directoryExists(fullPath)) {
439+
return { status: 404 }
440+
}
441+
406442
const headers: Record<string, any> = {
407443
'x-timestamp': Date.now(),
408444
'x-sent': true,
@@ -436,51 +472,55 @@ function serveStatic(components: Pick<CliComponents, 'fs'>, workspace: Workspace
436472
})
437473
}
438474

439-
async function fakeEntityV3FromFolder(
475+
async function fakeEntityV3FromProject(
440476
components: Pick<CliComponents, 'fs' | 'logger'>,
441-
projectRoot: string,
477+
project: ProjectUnion,
442478
hashingFunction: (filePath: string) => Promise<string>
443479
): Promise<Entity | null> {
444-
const sceneJsonPath = path.resolve(projectRoot, 'scene.json')
445-
let isParcelScene = true
480+
const projectFiles = await getProjectPublishableFilesWithHashes(components, project.workingDirectory, hashingFunction)
481+
const contentFiles = projectFilesToContentMappings(project.workingDirectory, projectFiles)
446482

447-
const wearableJsonPath = path.resolve(projectRoot, 'wearable.json')
448-
if (await components.fs.fileExists(wearableJsonPath)) {
449-
try {
450-
const wearableJson = JSON.parse(await components.fs.readFile(wearableJsonPath, 'utf-8'))
451-
if (!WearableJson.validate(wearableJson)) {
452-
const errors = (WearableJson.validate.errors || []).map((a) => `${a.data} ${a.message}`).join('')
453-
454-
components.logger.error(`Unable to validate wearable.json properly, please check its schema.` + errors)
455-
components.logger.error(`Invalid wearable.json (${wearableJsonPath})`)
456-
} else {
457-
isParcelScene = false
458-
}
459-
} catch (err: any) {
460-
components.logger.error(`Unable to load wearable.json`)
461-
components.logger.error(err)
462-
}
463-
}
464-
465-
if ((await components.fs.fileExists(sceneJsonPath)) && isParcelScene) {
483+
if (project.kind === 'scene') {
484+
const sceneJsonPath = path.resolve(project.workingDirectory, 'scene.json')
466485
const sceneJson = JSON.parse(await components.fs.readFile(sceneJsonPath, 'utf-8'))
467486
const { base, parcels }: { base: string; parcels: string[] } = sceneJson.scene
468487
const pointers = new Set<string>()
469488
pointers.add(base)
470489
parcels.forEach(($) => pointers.add($))
471490

472-
const projectFiles = await getProjectPublishableFilesWithHashes(components, projectRoot, hashingFunction)
473-
const contentFiles = projectFilesToContentMappings(projectRoot, projectFiles)
474-
475491
return {
476492
version: 'v3',
477493
type: EntityType.SCENE,
478-
id: await hashingFunction(projectRoot),
494+
id: await hashingFunction(project.workingDirectory),
479495
pointers: Array.from(pointers),
480496
timestamp: Date.now(),
481497
metadata: sceneJson,
482498
content: contentFiles
483499
}
500+
} else if (project.kind === 'wearable') {
501+
const wearableJsonPath = path.resolve(project.workingDirectory, 'wearable.json')
502+
try {
503+
const wearableJson = JSON.parse(await components.fs.readFile(wearableJsonPath, 'utf-8'))
504+
if (!WearableJson.validate(wearableJson)) {
505+
const errors = (WearableJson.validate.errors || []).map((a) => `${a.data} ${a.message}`).join('')
506+
507+
components.logger.error(`Unable to validate wearable.json properly, please check its schema.` + errors)
508+
components.logger.error(`Invalid wearable.json (${wearableJsonPath})`)
509+
}
510+
511+
return {
512+
version: 'v3',
513+
type: EntityType.WEARABLE,
514+
id: await hashingFunction(project.workingDirectory),
515+
pointers: Array.from([await hashingFunction(project.workingDirectory)]),
516+
timestamp: Date.now(),
517+
metadata: wearableJson,
518+
content: contentFiles
519+
}
520+
} catch (err: any) {
521+
components.logger.error(`Unable to load wearable.json`)
522+
components.logger.error(err)
523+
}
484524
}
485525

486526
return null

packages/@dcl/sdk-commands/src/logic/project-validations.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { getSceneFilePath, getValidSceneJson } from './scene-validations'
88

99
export type BaseProject = { workingDirectory: string }
1010
export type SceneProject = { kind: 'scene'; scene: Scene } & BaseProject
11-
export type ProjectUnion = SceneProject // | WearableProject
11+
export type WearableProject = { kind: 'wearable'; wearable: any } & BaseProject
12+
export type ProjectUnion = SceneProject | WearableProject
1213

1314
/**
1415
* Asserts that the workingDirectory is a valid project

0 commit comments

Comments
 (0)