@dcl/sdk-commands 7.0.0-4293371227.commit-54082c6 → 7.0.0-4294380152.commit-0d08b10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/analytics.js +5 -1
- package/package.json +6 -3
- package/src/commands/build/index.ts +0 -77
- package/src/commands/export-static/index.ts +0 -150
- package/src/commands/init/index.ts +0 -68
- package/src/commands/init/repos.ts +0 -17
- package/src/commands/start/index.ts +0 -221
- package/src/commands/start/server/endpoints.ts +0 -471
- package/src/commands/start/server/file-watch-notifier.ts +0 -45
- package/src/commands/start/server/realm.ts +0 -63
- package/src/commands/start/server/routes.ts +0 -36
- package/src/commands/start/server/ws.ts +0 -24
- package/src/commands/start/types.ts +0 -26
- package/src/components/analytics.ts +0 -92
- package/src/components/dcl-info-config.ts +0 -63
- package/src/components/eth.ts +0 -3
- package/src/components/fetch.ts +0 -11
- package/src/components/fs.ts +0 -62
- package/src/components/index.ts +0 -26
- package/src/components/log.ts +0 -48
- package/src/index.ts +0 -90
- package/src/logic/args.ts +0 -19
- package/src/logic/beautiful-logs.ts +0 -26
- package/src/logic/catalyst-requests.ts +0 -31
- package/src/logic/commands.ts +0 -28
- package/src/logic/config.ts +0 -45
- package/src/logic/coordinates.ts +0 -95
- package/src/logic/dcl-ignore.ts +0 -50
- package/src/logic/error.ts +0 -1
- package/src/logic/exec.ts +0 -36
- package/src/logic/fs.ts +0 -76
- package/src/logic/get-free-port.ts +0 -15
- package/src/logic/project-files.ts +0 -92
- package/src/logic/project-validations.ts +0 -61
- package/src/logic/realm.ts +0 -28
- package/src/logic/scene-validations.ts +0 -81
- package/tsconfig.json +0 -28
|
@@ -1,471 +0,0 @@
|
|
|
1
|
-
import { Router } from '@well-known-components/http-server'
|
|
2
|
-
import { PreviewComponents } from '../types'
|
|
3
|
-
import * as path from 'path'
|
|
4
|
-
import { WearableJson } from '@dcl/schemas/dist/sdk'
|
|
5
|
-
import { Entity, EntityType, Locale, Wearable } from '@dcl/schemas'
|
|
6
|
-
import fetch, { Headers } from 'node-fetch'
|
|
7
|
-
import { fetchEntityByPointer } from '../../../logic/catalyst-requests'
|
|
8
|
-
import { CliComponents } from '../../../components'
|
|
9
|
-
import { b64HashingFunction, getProjectContentMappings } from '../../../logic/project-files'
|
|
10
|
-
|
|
11
|
-
function getCatalystUrl(): URL {
|
|
12
|
-
return new URL('https://peer.decentraland.org')
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function smartWearableNameToId(name: string) {
|
|
16
|
-
return name.toLocaleLowerCase().replace(/ /g, '-')
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
type LambdasWearable = Wearable & {
|
|
20
|
-
baseUrl: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function setupEcs6Endpoints(components: CliComponents, dir: string, router: Router<PreviewComponents>) {
|
|
24
|
-
const baseFolders = [dir]
|
|
25
|
-
// handle old preview scene.json
|
|
26
|
-
router.get('/scene.json', async () => {
|
|
27
|
-
return {
|
|
28
|
-
headers: { 'content-type': 'application/json' },
|
|
29
|
-
body: components.fs.createReadStream(path.join(dir, 'scene.json'))
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
router.get('/lambdas/explore/realms', async (ctx) => {
|
|
34
|
-
return {
|
|
35
|
-
body: [
|
|
36
|
-
{
|
|
37
|
-
serverName: 'localhost',
|
|
38
|
-
url: `http://${ctx.url.host}`,
|
|
39
|
-
layer: 'stub',
|
|
40
|
-
usersCount: 0,
|
|
41
|
-
maxUsers: 100,
|
|
42
|
-
userParcels: []
|
|
43
|
-
}
|
|
44
|
-
]
|
|
45
|
-
}
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
router.get('/lambdas/contracts/servers', async (ctx) => {
|
|
49
|
-
return {
|
|
50
|
-
body: [
|
|
51
|
-
{
|
|
52
|
-
address: `http://${ctx.url.host}`,
|
|
53
|
-
owner: '0x0000000000000000000000000000000000000000',
|
|
54
|
-
id: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
|
55
|
-
}
|
|
56
|
-
]
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
router.get('/lambdas/profiles', async (ctx, next) => {
|
|
61
|
-
const baseUrl = `${ctx.url.protocol}//${ctx.url.host}/content/contents`
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
const previewWearables = await getAllPreviewWearables(components, {
|
|
65
|
-
baseFolders,
|
|
66
|
-
baseUrl
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
if (previewWearables.length === 1) {
|
|
70
|
-
const catalystUrl = getCatalystUrl()
|
|
71
|
-
|
|
72
|
-
const u = new URL(ctx.url.toString())
|
|
73
|
-
u.host = catalystUrl.host
|
|
74
|
-
u.protocol = catalystUrl.protocol
|
|
75
|
-
u.port = catalystUrl.port
|
|
76
|
-
const req = await fetch(u.toString(), {
|
|
77
|
-
headers: {
|
|
78
|
-
connection: 'close'
|
|
79
|
-
},
|
|
80
|
-
method: ctx.request.method,
|
|
81
|
-
body: ctx.request.method === 'get' ? undefined : ctx.request.body
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
const deployedProfile = (await req.json()) as any[]
|
|
85
|
-
|
|
86
|
-
if (deployedProfile?.length === 1) {
|
|
87
|
-
deployedProfile[0].avatars[0].avatar.wearables.push(...previewWearables.map(($) => $.id))
|
|
88
|
-
return {
|
|
89
|
-
headers: {
|
|
90
|
-
'content-type': req.headers.get('content-type') || 'application/binary'
|
|
91
|
-
},
|
|
92
|
-
body: deployedProfile
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
} catch (err) {
|
|
97
|
-
console.warn(`Failed to catch profile and fill with preview wearables.`, err)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return next()
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
router.all('/lambdas/:path+', async (ctx) => {
|
|
104
|
-
const catalystUrl = getCatalystUrl()
|
|
105
|
-
const u = new URL(ctx.url.toString())
|
|
106
|
-
u.host = catalystUrl.host
|
|
107
|
-
u.protocol = catalystUrl.protocol
|
|
108
|
-
u.port = catalystUrl.port
|
|
109
|
-
const req = await fetch(u.toString(), {
|
|
110
|
-
headers: {
|
|
111
|
-
connection: 'close'
|
|
112
|
-
},
|
|
113
|
-
method: ctx.request.method,
|
|
114
|
-
body: ctx.request.method === 'get' ? undefined : ctx.request.body
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
headers: {
|
|
119
|
-
'content-type': req.headers.get('content-type') || 'application/binary'
|
|
120
|
-
},
|
|
121
|
-
body: req.body
|
|
122
|
-
}
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
router.post('/content/entities', async (ctx) => {
|
|
126
|
-
const catalystUrl = getCatalystUrl()
|
|
127
|
-
const headers = new Headers()
|
|
128
|
-
console.log(ctx.request.headers)
|
|
129
|
-
const res = await fetch(`${catalystUrl.toString()}/content/entities`, {
|
|
130
|
-
method: 'post',
|
|
131
|
-
headers,
|
|
132
|
-
body: ctx.request.body
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
return res
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
serveStatic(components, dir, router)
|
|
139
|
-
|
|
140
|
-
// TODO: get workspace scenes & wearables...
|
|
141
|
-
|
|
142
|
-
serveFolders(components, router, baseFolders)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function serveFolders(components: Pick<CliComponents, 'fs'>, router: Router<PreviewComponents>, baseFolders: string[]) {
|
|
146
|
-
router.get('/content/contents/:hash', async (ctx: any, next: any) => {
|
|
147
|
-
if (ctx.params.hash && ctx.params.hash.startsWith('b64-')) {
|
|
148
|
-
const fullPath = path.resolve(Buffer.from(ctx.params.hash.replace(/^b64-/, ''), 'base64').toString('utf8'))
|
|
149
|
-
|
|
150
|
-
// only return files IF the file is within a baseFolder
|
|
151
|
-
if (!baseFolders.find((folder: string) => fullPath.startsWith(folder))) {
|
|
152
|
-
return next()
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
headers: {
|
|
157
|
-
'x-timestamp': Date.now(),
|
|
158
|
-
'x-sent': true,
|
|
159
|
-
'cache-control': 'no-cache,private,max-age=1'
|
|
160
|
-
},
|
|
161
|
-
body: components.fs.createReadStream(fullPath)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return next()
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
async function pointerRequestHandler(pointers: string[]) {
|
|
169
|
-
if (!pointers || pointers.length === 0) {
|
|
170
|
-
return []
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const requestedPointers = new Set<string>(
|
|
174
|
-
pointers && typeof pointers === 'string' ? [pointers as string] : (pointers as string[])
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
const resultEntities = await getSceneJson(components, baseFolders, Array.from(requestedPointers))
|
|
178
|
-
const catalystUrl = getCatalystUrl()
|
|
179
|
-
const remote = fetchEntityByPointer(
|
|
180
|
-
catalystUrl.toString(),
|
|
181
|
-
pointers.filter(($: string) => !$.match(/-?\d+,-?\d+/))
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
const serverEntities = Array.isArray(remote) ? remote : []
|
|
185
|
-
|
|
186
|
-
return [...resultEntities, ...serverEntities]
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// REVIEW RESPONSE FORMAT
|
|
190
|
-
router.get('/content/entities/scene', async (ctx) => {
|
|
191
|
-
return {
|
|
192
|
-
body: await pointerRequestHandler(ctx.url.searchParams.getAll('pointer'))
|
|
193
|
-
}
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
// REVIEW RESPONSE FORMAT
|
|
197
|
-
router.post('/content/entities/active', async (ctx) => {
|
|
198
|
-
const body = await ctx.request.json()
|
|
199
|
-
return {
|
|
200
|
-
body: await pointerRequestHandler(body.pointers)
|
|
201
|
-
}
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
router.get('/preview-wearables/:id', async (ctx) => {
|
|
205
|
-
const baseUrl = `${ctx.url.protocol}//${ctx.url.host}/content/contents`
|
|
206
|
-
const wearables = await getAllPreviewWearables(components, {
|
|
207
|
-
baseUrl,
|
|
208
|
-
baseFolders
|
|
209
|
-
})
|
|
210
|
-
const wearableId = ctx.params.id
|
|
211
|
-
return {
|
|
212
|
-
body: {
|
|
213
|
-
ok: true,
|
|
214
|
-
data: wearables.filter((wearable) => smartWearableNameToId(wearable?.name) === wearableId)
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
router.get('/preview-wearables', async (ctx) => {
|
|
220
|
-
const baseUrl = `${ctx.url.protocol}//${ctx.url.host}/content/contents`
|
|
221
|
-
return {
|
|
222
|
-
body: {
|
|
223
|
-
ok: true,
|
|
224
|
-
data: await getAllPreviewWearables(components, { baseUrl, baseFolders })
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
})
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async function getAllPreviewWearables(
|
|
231
|
-
components: Pick<CliComponents, 'fs'>,
|
|
232
|
-
{ baseFolders, baseUrl }: { baseFolders: string[]; baseUrl: string }
|
|
233
|
-
) {
|
|
234
|
-
const wearablePathArray: string[] = []
|
|
235
|
-
for (const wearableDir of baseFolders) {
|
|
236
|
-
const wearableJsonPath = path.resolve(wearableDir, 'wearable.json')
|
|
237
|
-
if (await components.fs.fileExists(wearableJsonPath)) {
|
|
238
|
-
wearablePathArray.push(wearableJsonPath)
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const ret: LambdasWearable[] = []
|
|
243
|
-
for (const wearableJsonPath of wearablePathArray) {
|
|
244
|
-
try {
|
|
245
|
-
ret.push(await serveWearable(components, wearableJsonPath, baseUrl))
|
|
246
|
-
} catch (err) {
|
|
247
|
-
console.error(`Couldn't mock the wearable ${wearableJsonPath}. Please verify the correct format and scheme.`, err)
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return ret
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
async function serveWearable(
|
|
254
|
-
components: Pick<CliComponents, 'fs'>,
|
|
255
|
-
wearableJsonPath: string,
|
|
256
|
-
baseUrl: string
|
|
257
|
-
): Promise<LambdasWearable> {
|
|
258
|
-
const wearableDir = path.dirname(wearableJsonPath)
|
|
259
|
-
const wearableJson = JSON.parse((await components.fs.readFile(wearableJsonPath)).toString())
|
|
260
|
-
|
|
261
|
-
if (!WearableJson.validate(wearableJson)) {
|
|
262
|
-
const errors = (WearableJson.validate.errors || []).map((a) => `${a.data} ${a.message}`).join('')
|
|
263
|
-
|
|
264
|
-
console.error(`Unable to validate wearable.json properly, please check it.`, errors)
|
|
265
|
-
throw new Error(`Invalid wearable.json (${wearableJsonPath})`)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const hashedFiles = await getProjectContentMappings(components, wearableDir, b64HashingFunction)
|
|
269
|
-
|
|
270
|
-
const thumbnailFiltered = hashedFiles.filter(($) => $?.file === 'thumbnail.png')
|
|
271
|
-
const thumbnail =
|
|
272
|
-
thumbnailFiltered.length > 0 && thumbnailFiltered[0]?.hash && `${baseUrl}/${thumbnailFiltered[0].hash}`
|
|
273
|
-
|
|
274
|
-
const wearableId = 'urn:8dc2d7ad-97e3-44d0-ba89-e8305d795a6a'
|
|
275
|
-
|
|
276
|
-
const representations = wearableJson.data.representations.map((representation) => ({
|
|
277
|
-
...representation,
|
|
278
|
-
mainFile: `male/${representation.mainFile}`,
|
|
279
|
-
contents: hashedFiles.map(($) => ({
|
|
280
|
-
key: `male/${$?.file}`,
|
|
281
|
-
url: `${baseUrl}/${$?.hash}`,
|
|
282
|
-
hash: $?.hash
|
|
283
|
-
}))
|
|
284
|
-
}))
|
|
285
|
-
|
|
286
|
-
return {
|
|
287
|
-
id: wearableId,
|
|
288
|
-
rarity: wearableJson.rarity,
|
|
289
|
-
i18n: [{ code: 'en' as Locale, text: wearableJson.name }],
|
|
290
|
-
description: wearableJson.description,
|
|
291
|
-
thumbnail: thumbnail || '',
|
|
292
|
-
image: thumbnail || '',
|
|
293
|
-
collectionAddress: '0x0',
|
|
294
|
-
baseUrl: `${baseUrl}/`,
|
|
295
|
-
name: wearableJson.name || '',
|
|
296
|
-
data: {
|
|
297
|
-
category: wearableJson.data.category,
|
|
298
|
-
replaces: [],
|
|
299
|
-
hides: [],
|
|
300
|
-
tags: [],
|
|
301
|
-
representations: representations as any
|
|
302
|
-
// scene: hashedFiles as any,
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
async function getSceneJson(
|
|
308
|
-
components: Pick<CliComponents, 'fs'>,
|
|
309
|
-
projectRoots: string[],
|
|
310
|
-
pointers: string[]
|
|
311
|
-
): Promise<Entity[]> {
|
|
312
|
-
const requestedPointers = new Set<string>(pointers)
|
|
313
|
-
const resultEntities: Entity[] = []
|
|
314
|
-
|
|
315
|
-
const allDeployments = await Promise.all(
|
|
316
|
-
projectRoots.map(async (projectRoot) => fakeEntityV3FromFolder(components, projectRoot, b64HashingFunction))
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
for (const pointer of Array.from(requestedPointers)) {
|
|
320
|
-
// get deployment by pointer
|
|
321
|
-
const theDeployment = allDeployments.find(($) => $ && $.pointers.includes(pointer))
|
|
322
|
-
if (theDeployment) {
|
|
323
|
-
// remove all the required pointers from the requestedPointers set
|
|
324
|
-
// to prevent sending duplicated entities
|
|
325
|
-
theDeployment.pointers.forEach(($) => requestedPointers.delete($))
|
|
326
|
-
|
|
327
|
-
// add the deployment to the results
|
|
328
|
-
resultEntities.push(theDeployment)
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
return resultEntities
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function serveStatic(components: Pick<CliComponents, 'fs'>, projectRoot: string, router: Router<PreviewComponents>) {
|
|
336
|
-
const sdkPath = path.dirname(
|
|
337
|
-
require.resolve('@dcl/sdk/package.json', {
|
|
338
|
-
paths: [projectRoot]
|
|
339
|
-
})
|
|
340
|
-
)
|
|
341
|
-
const dclExplorerJsonPath = path.dirname(
|
|
342
|
-
require.resolve('@dcl/explorer/package.json', {
|
|
343
|
-
paths: [projectRoot, sdkPath]
|
|
344
|
-
})
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
const dclKernelDefaultProfilePath = path.resolve(dclExplorerJsonPath, 'default-profile')
|
|
348
|
-
const dclKernelImagesDecentralandConnect = path.resolve(dclExplorerJsonPath, 'images', 'decentraland-connect')
|
|
349
|
-
|
|
350
|
-
const routes = [
|
|
351
|
-
{
|
|
352
|
-
route: '/',
|
|
353
|
-
path: path.resolve(dclExplorerJsonPath, 'preview.html'),
|
|
354
|
-
type: 'text/html'
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
route: '/favicon.ico',
|
|
358
|
-
path: path.resolve(dclExplorerJsonPath, 'favicon.ico'),
|
|
359
|
-
type: 'text/html'
|
|
360
|
-
},
|
|
361
|
-
{
|
|
362
|
-
route: '/@/explorer/index.js',
|
|
363
|
-
path: path.resolve(dclExplorerJsonPath, 'index.js'),
|
|
364
|
-
type: 'text/javascript'
|
|
365
|
-
}
|
|
366
|
-
]
|
|
367
|
-
|
|
368
|
-
for (const route of routes) {
|
|
369
|
-
router.get(route.route, async (_ctx) => {
|
|
370
|
-
return {
|
|
371
|
-
headers: { 'Content-Type': route.type },
|
|
372
|
-
body: components.fs.createReadStream(route.path)
|
|
373
|
-
}
|
|
374
|
-
})
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function createStaticRoutes(
|
|
378
|
-
components: Pick<CliComponents, 'fs'>,
|
|
379
|
-
route: string,
|
|
380
|
-
folder: string,
|
|
381
|
-
transform = (str: string) => str
|
|
382
|
-
) {
|
|
383
|
-
router.get(route, async (ctx, next) => {
|
|
384
|
-
const file = ctx.params.path
|
|
385
|
-
const fullPath = path.resolve(folder, transform(file))
|
|
386
|
-
|
|
387
|
-
// only return files IF the file is within a baseFolder
|
|
388
|
-
if (!(await components.fs.fileExists(fullPath))) {
|
|
389
|
-
return next()
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const headers: Record<string, any> = {
|
|
393
|
-
'x-timestamp': Date.now(),
|
|
394
|
-
'x-sent': true,
|
|
395
|
-
'cache-control': 'no-cache,private,max-age=1'
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
if (fullPath.endsWith('.wasm')) {
|
|
399
|
-
headers['content-type'] = 'application/wasm'
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
return {
|
|
403
|
-
headers,
|
|
404
|
-
body: components.fs.createReadStream(fullPath)
|
|
405
|
-
}
|
|
406
|
-
})
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
createStaticRoutes(components, '/images/decentraland-connect/:path+', dclKernelImagesDecentralandConnect)
|
|
410
|
-
createStaticRoutes(components, '/default-profile/:path+', dclKernelDefaultProfilePath)
|
|
411
|
-
createStaticRoutes(components, '/@/explorer/:path+', dclExplorerJsonPath, (filePath) => filePath.replace(/.br+$/, ''))
|
|
412
|
-
|
|
413
|
-
router.get('/feature-flags/:file', async (ctx) => {
|
|
414
|
-
const res = await fetch(`https://feature-flags.decentraland.zone/${ctx.params.file}`, {
|
|
415
|
-
headers: {
|
|
416
|
-
connection: 'close'
|
|
417
|
-
}
|
|
418
|
-
})
|
|
419
|
-
return {
|
|
420
|
-
body: await res.arrayBuffer()
|
|
421
|
-
}
|
|
422
|
-
})
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
async function fakeEntityV3FromFolder(
|
|
426
|
-
components: Pick<CliComponents, 'fs'>,
|
|
427
|
-
projectRoot: string,
|
|
428
|
-
hashingFunction: (filePath: string) => Promise<string>
|
|
429
|
-
): Promise<Entity | null> {
|
|
430
|
-
const sceneJsonPath = path.resolve(projectRoot, 'scene.json')
|
|
431
|
-
let isParcelScene = true
|
|
432
|
-
|
|
433
|
-
const wearableJsonPath = path.resolve(projectRoot, 'wearable.json')
|
|
434
|
-
if (await components.fs.fileExists(wearableJsonPath)) {
|
|
435
|
-
try {
|
|
436
|
-
const wearableJson = JSON.parse(await components.fs.readFile(wearableJsonPath, 'utf-8'))
|
|
437
|
-
if (!WearableJson.validate(wearableJson)) {
|
|
438
|
-
const errors = (WearableJson.validate.errors || []).map((a) => `${a.data} ${a.message}`).join('')
|
|
439
|
-
|
|
440
|
-
console.error(`Unable to validate wearable.json properly, please check it.`, errors)
|
|
441
|
-
console.error(`Invalid wearable.json (${wearableJsonPath})`)
|
|
442
|
-
} else {
|
|
443
|
-
isParcelScene = false
|
|
444
|
-
}
|
|
445
|
-
} catch (err) {
|
|
446
|
-
console.error(`Unable to load wearable.json properly`, err)
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if ((await components.fs.fileExists(sceneJsonPath)) && isParcelScene) {
|
|
451
|
-
const sceneJson = JSON.parse(await components.fs.readFile(sceneJsonPath, 'utf-8'))
|
|
452
|
-
const { base, parcels }: { base: string; parcels: string[] } = sceneJson.scene
|
|
453
|
-
const pointers = new Set<string>()
|
|
454
|
-
pointers.add(base)
|
|
455
|
-
parcels.forEach(($) => pointers.add($))
|
|
456
|
-
|
|
457
|
-
const mappedFiles = await getProjectContentMappings(components, projectRoot, hashingFunction)
|
|
458
|
-
|
|
459
|
-
return {
|
|
460
|
-
version: 'v3',
|
|
461
|
-
type: EntityType.SCENE,
|
|
462
|
-
id: await hashingFunction(projectRoot),
|
|
463
|
-
pointers: Array.from(pointers),
|
|
464
|
-
timestamp: Date.now(),
|
|
465
|
-
metadata: sceneJson,
|
|
466
|
-
content: mappedFiles
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
return null
|
|
471
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { sdk } from '@dcl/schemas'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import { WebSocket } from 'ws'
|
|
4
|
-
import chokidar from 'chokidar'
|
|
5
|
-
import { getDCLIgnorePatterns } from '../../../logic/dcl-ignore'
|
|
6
|
-
import { PreviewComponents } from '../types'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* This function gets file modification events and sends them to all the connected
|
|
10
|
-
* websockets, it is used to hot-reload assets of the scene.
|
|
11
|
-
*
|
|
12
|
-
* IMPORTANT: this is a legacy protocol and needs to be revisited for SDK7
|
|
13
|
-
*/
|
|
14
|
-
export async function wireFileWatcherToWebSockets(
|
|
15
|
-
components: Pick<PreviewComponents, 'fs' | 'ws'>,
|
|
16
|
-
projectRoot: string
|
|
17
|
-
) {
|
|
18
|
-
const { clients } = components.ws.ws
|
|
19
|
-
const ignored = await getDCLIgnorePatterns(components, projectRoot)
|
|
20
|
-
|
|
21
|
-
chokidar
|
|
22
|
-
.watch(path.resolve(projectRoot), {
|
|
23
|
-
ignored,
|
|
24
|
-
ignoreInitial: false,
|
|
25
|
-
cwd: projectRoot
|
|
26
|
-
})
|
|
27
|
-
.on('all', async (_, _file) => {
|
|
28
|
-
// TODO: accumulate changes in an array and debounce
|
|
29
|
-
return updateScene(projectRoot, clients)
|
|
30
|
-
})
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function updateScene(dir: string, clients: Set<WebSocket>): void {
|
|
34
|
-
for (const client of clients) {
|
|
35
|
-
if (client.readyState === WebSocket.OPEN) {
|
|
36
|
-
const message: sdk.SceneUpdate = {
|
|
37
|
-
type: sdk.SCENE_UPDATE,
|
|
38
|
-
payload: { sceneId: 'b64-' + Buffer.from(dir).toString('base64'), sceneType: sdk.ProjectType.SCENE }
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
client.send(sdk.UPDATE)
|
|
42
|
-
client.send(JSON.stringify(message))
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { handleSocketLinearProtocol } from '@dcl/mini-comms/dist/logic/handle-linear-protocol'
|
|
2
|
-
import { PreviewComponents } from '../types'
|
|
3
|
-
import { AboutResponse } from '@dcl/protocol/out-ts/decentraland/bff/http_endpoints.gen'
|
|
4
|
-
import { Router } from '@well-known-components/http-server'
|
|
5
|
-
import { upgradeWebSocketResponse } from '@well-known-components/http-server/dist/ws'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* This module handles the BFF mock and communications server for the preview mode.
|
|
9
|
-
* It runs using @dcl/mini-comms implementing RFC-5
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export function setupRealmAndComms(components: PreviewComponents, router: Router<PreviewComponents>) {
|
|
13
|
-
router.get('/about', async (ctx) => {
|
|
14
|
-
const host = ctx.url.host
|
|
15
|
-
|
|
16
|
-
const body: AboutResponse = {
|
|
17
|
-
acceptingUsers: true,
|
|
18
|
-
bff: { healthy: false, publicUrl: host },
|
|
19
|
-
comms: {
|
|
20
|
-
healthy: true,
|
|
21
|
-
protocol: 'v3',
|
|
22
|
-
fixedAdapter: `ws-room:${ctx.url.protocol.replace(/^http/, 'ws')}//${host}/mini-comms/room-1`
|
|
23
|
-
},
|
|
24
|
-
configurations: {
|
|
25
|
-
networkId: 0,
|
|
26
|
-
globalScenesUrn: [],
|
|
27
|
-
scenesUrn: [],
|
|
28
|
-
realmName: 'LocalPreview'
|
|
29
|
-
},
|
|
30
|
-
content: {
|
|
31
|
-
healthy: true,
|
|
32
|
-
publicUrl: `${ctx.url.protocol}//${ctx.url.host}/content`
|
|
33
|
-
},
|
|
34
|
-
lambdas: {
|
|
35
|
-
healthy: true,
|
|
36
|
-
publicUrl: `${ctx.url.protocol}//${ctx.url.host}/lambdas`
|
|
37
|
-
},
|
|
38
|
-
healthy: true
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return { body }
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
router.get('/mini-comms/:roomId', async (ctx) => {
|
|
45
|
-
return upgradeWebSocketResponse((ws: any) => {
|
|
46
|
-
if (ws.protocol === 'rfc5' || ws.protocol === 'rfc4') {
|
|
47
|
-
ws.on('error', (error: any) => {
|
|
48
|
-
console.error(error)
|
|
49
|
-
ws.close()
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
ws.on('close', () => {
|
|
53
|
-
console.debug('Websocket closed')
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
handleSocketLinearProtocol(components, ws, ctx.params.roomId).catch((err: any) => {
|
|
57
|
-
console.info(err)
|
|
58
|
-
ws.close()
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
})
|
|
63
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { WebSocket } from 'ws'
|
|
2
|
-
import { setupRealmAndComms } from './realm'
|
|
3
|
-
import { Router } from '@well-known-components/http-server'
|
|
4
|
-
import { setupEcs6Endpoints } from './endpoints'
|
|
5
|
-
import { PreviewComponents } from '../types'
|
|
6
|
-
import { upgradeWebSocketResponse } from '@well-known-components/http-server/dist/ws'
|
|
7
|
-
|
|
8
|
-
export async function wireRouter(components: PreviewComponents, dir: string) {
|
|
9
|
-
const router = new Router<PreviewComponents>()
|
|
10
|
-
|
|
11
|
-
const sceneUpdateClients = new Set<WebSocket>()
|
|
12
|
-
|
|
13
|
-
router.get('/', async (ctx, next) => {
|
|
14
|
-
if (ctx.request.headers.get('upgrade') === 'websocket') {
|
|
15
|
-
return upgradeWebSocketResponse((ws) => initWsConnection(ws as any as WebSocket, sceneUpdateClients))
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return next()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
setupRealmAndComms(components, router)
|
|
22
|
-
setupEcs6Endpoints(components, dir, router)
|
|
23
|
-
|
|
24
|
-
components.server.setContext(components)
|
|
25
|
-
components.server.use(router.allowedMethods())
|
|
26
|
-
components.server.use(router.middleware())
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const initWsConnection = (ws: WebSocket, clients: Set<WebSocket>) => {
|
|
30
|
-
if (ws.readyState === ws.OPEN) {
|
|
31
|
-
clients.add(ws)
|
|
32
|
-
} else {
|
|
33
|
-
ws.on('open', () => clients.add(ws))
|
|
34
|
-
}
|
|
35
|
-
ws.on('close', () => clients.delete(ws))
|
|
36
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { WebSocketServer } from 'ws'
|
|
2
|
-
import { PreviewComponents } from '../types'
|
|
3
|
-
import { IBaseComponent } from '@well-known-components/interfaces'
|
|
4
|
-
|
|
5
|
-
export type WebSocketComponent = IBaseComponent & {
|
|
6
|
-
ws: WebSocketServer
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Creates a ws-server component
|
|
11
|
-
* @public
|
|
12
|
-
*/
|
|
13
|
-
export async function createWsComponent(_: Pick<PreviewComponents, 'logs'>): Promise<WebSocketComponent> {
|
|
14
|
-
const ws = new WebSocketServer({ noServer: true })
|
|
15
|
-
|
|
16
|
-
async function stop() {
|
|
17
|
-
ws.close()
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return {
|
|
21
|
-
stop,
|
|
22
|
-
ws
|
|
23
|
-
}
|
|
24
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ILoggerComponent,
|
|
3
|
-
IMetricsComponent,
|
|
4
|
-
IHttpServerComponent,
|
|
5
|
-
IConfigComponent
|
|
6
|
-
} from '@well-known-components/interfaces'
|
|
7
|
-
import { HTTPProvider } from 'eth-connect'
|
|
8
|
-
import { RoomComponent } from '@dcl/mini-comms/dist/adapters/rooms'
|
|
9
|
-
import { WebSocketComponent } from './server/ws'
|
|
10
|
-
import { CliComponents } from '../../components'
|
|
11
|
-
|
|
12
|
-
export type PreviewComponents = CliComponents & {
|
|
13
|
-
logs: ILoggerComponent
|
|
14
|
-
server: IHttpServerComponent<PreviewComponents>
|
|
15
|
-
config: IConfigComponent
|
|
16
|
-
metrics: IMetricsComponent<any>
|
|
17
|
-
ethereumProvider: HTTPProvider
|
|
18
|
-
rooms: RoomComponent
|
|
19
|
-
ws: WebSocketComponent
|
|
20
|
-
signaler: ISignalerComponent
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export type ISignalerComponent = {
|
|
24
|
-
// programClosed resolves when the component is stopped
|
|
25
|
-
programClosed: Promise<void>
|
|
26
|
-
}
|