UNPKG

dcl-catalyst-client

Version:

A client to query and perform changes on Decentraland's catalyst servers

162 lines 7.12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildEntityWithoutNewFiles = exports.buildEntity = exports.buildEntityAndFile = void 0; const hashing_1 = require("@dcl/hashing"); const schemas_1 = require("@dcl/schemas"); /** * Take all the entity's data, build the entity file with it, and calculate its id */ async function buildEntityAndFile({ type, pointers, timestamp, content, metadata }) { var _a; // Make sure that there is at least one pointer if (pointers.length === 0) throw new Error(`All entities must have at least one pointer.`); const entity = { // default version is V3 version: 'v3', type, pointers, timestamp, content, metadata }; // prevent duplicated file names if (content) { const usedFilenames = new Set(); for (const a of content) { const lowerCasedFileName = a.file.toLowerCase(); if (usedFilenames.has(lowerCasedFileName)) { throw new Error(`Error creating the deployable entity: Decentraland's file system is case insensitive, the file ${JSON.stringify(a.file)} is repeated`); } usedFilenames.add(lowerCasedFileName); } } const entityFile = new TextEncoder().encode(JSON.stringify(entity)); const entityId = await (0, hashing_1.hashV1)(entityFile); const entityWithId = { id: entityId, ...entity }; if (!schemas_1.Entity.validate(entityWithId)) { throw new Error('Generated entity is not valid:\n' + ((_a = schemas_1.Entity.validate.errors) === null || _a === void 0 ? void 0 : _a.map(($) => $.message).join('\n'))); } return { entity: entityWithId, entityFile }; } exports.buildEntityAndFile = buildEntityAndFile; /** * As part of the deployment process, an entity has to be built. In this method, we are building it, based on the data provided. * After the entity is built, the user will have to sign the entity id, to prove they are actually who they say they are. */ async function buildEntity({ type, pointers, files, metadata, timestamp }) { // Reorder input const contentFiles = Array.from(files ? files : []).map(([key, content]) => ({ key, content })); // Calculate hashes const allInfo = await Promise.all(contentFiles.map(async ({ key, content }) => ({ key, content, hash: await (0, hashing_1.hashV1)(content) }))); const hashesByKey = new Map(allInfo.map(({ hash, key }) => [key, hash])); const filesByHash = new Map(allInfo.map(({ hash, content }) => [hash, content])); return buildEntityInternal(type, pointers, { hashesByKey, filesByHash, metadata, timestamp }); } exports.buildEntity = buildEntity; /** * In cases where we don't need upload content files, we can simply generate the new entity. * We can still use already uploaded hashes on this new entity. */ async function buildEntityWithoutNewFiles(fetcher, { contentUrl, type, pointers, hashesByKey, metadata, timestamp }) { const givenFilesMaps = (hashesByKey !== null && hashesByKey !== void 0 ? hashesByKey : metadata) ? getHashesByKey(metadata) : undefined; // When the old entity has the old hashing algorithm, then the full entity with new hash will need to be deployed. if (!!givenFilesMaps && isObsoleteProfile(type, givenFilesMaps)) { const files = await downloadAllFiles(fetcher, contentUrl, givenFilesMaps); const metadataWithNewHash = await updateMetadata(files, metadata); return buildEntity({ type, pointers, files, metadata: metadataWithNewHash, timestamp }); } return buildEntityInternal(type, pointers, { hashesByKey, metadata, timestamp }); } exports.buildEntityWithoutNewFiles = buildEntityWithoutNewFiles; async function buildEntityInternal(type, pointers, options) { // Make sure that there is at least one pointer if (pointers.length === 0) { throw new Error(`All entities must have at least one pointer.`); } // Re-organize the hashes const hashesByKey = (options === null || options === void 0 ? void 0 : options.hashesByKey) ? options === null || options === void 0 ? void 0 : options.hashesByKey : new Map(); const entityContent = Array.from(hashesByKey.entries()).map(([key, hash]) => ({ file: key, hash })); // Calculate timestamp if necessary const timestamp = (options === null || options === void 0 ? void 0 : options.timestamp) ? options === null || options === void 0 ? void 0 : options.timestamp : Date.now(); // Build entity file const { entity, entityFile } = await buildEntityAndFile({ type, pointers, timestamp, content: entityContent, metadata: options === null || options === void 0 ? void 0 : options.metadata }); // Add entity file to content files const filesByHash = (options === null || options === void 0 ? void 0 : options.filesByHash) ? options.filesByHash : new Map(); filesByHash.set(entity.id, entityFile); return { files: filesByHash, entityId: entity.id }; } function isObsoleteProfile(type, hashesByKey) { return type === schemas_1.EntityType.PROFILE && Array.from(hashesByKey).some(([, hash]) => hash.toLowerCase().startsWith('qm')); } async function fetchArrayBuffer(fetcher, url) { const response = await fetcher.fetch(url); const buffer = await response.arrayBuffer(); return new Uint8Array(buffer); } async function downloadAllFiles(fetcher, contentUrl, hashes) { const oldBodyHash = hashes.get('body.png'); const oldFaceHash = hashes.get('face256.png'); const bodyUrl = new URL(`${contentUrl}/contents/${oldBodyHash}`).toString(); const faceUrl = new URL(`${contentUrl}/contents/${oldFaceHash}`).toString(); const [bodyFileContent, faceFileContent] = await Promise.all([ fetchArrayBuffer(fetcher, bodyUrl), fetchArrayBuffer(fetcher, faceUrl) ]); return new Map([ ['body.png', bodyFileContent], ['face256.png', faceFileContent] ]); } async function updateMetadata(files, metadata) { if (!metadata) return metadata; metadata.avatars = await Promise.all(metadata.avatars.map(async (avatar) => { const newSnapshots = { face256: '', body: '' }; const face256Content = files.get('face256.png'); if (!!face256Content) { newSnapshots['face256'] = await (0, hashing_1.hashV1)(face256Content); } const bodyContent = files.get('body.png'); if (!!bodyContent) { newSnapshots['body'] = await (0, hashing_1.hashV1)(bodyContent); } avatar.avatar.snapshots = newSnapshots; return avatar; })); return metadata; } function getHashesByKey(metadata) { const avatar = metadata.avatars[0]; return new Map([ ['body.png', avatar.avatar.snapshots.body], ['face256.png', avatar.avatar.snapshots.face256] ]); } //# sourceMappingURL=DeploymentBuilder.js.map