UNPKG

dwnpm

Version:

Decentralized Registry Package Manager (DRPM) helps developers publish, install, find and manage Decentralized Packages (DPKs) published to Decentralized Web Nodes (DWNs). DRPM does this by looking up a Decentralized Identifier (DID) to find its DID docum

202 lines (173 loc) 8.32 kB
import { Request, Response, Router } from 'express'; import { pipeline } from 'stream/promises'; import { pack } from 'tar-stream'; import zlib from 'zlib'; import { DManager } from '../utils/dpk/manager.js'; import { DrlBuilder } from '../utils/dwn/drl-builder.js'; import { Logger } from '../utils/logger.js'; import { ResponseUtils } from '../utils/response.js'; import { RegistryUtils } from './utils.js'; class RegistryHandlers { private router: Router; constructor() { this.router = Router(); this.initializeRoutes(); } public getRouter(): Router { return this.router; } private initializeRoutes(): void { this.router.get('/', RegistryHandlers.home); this.router.get('/health', RegistryHandlers.health); /** * GET route to handle npm install request * @summary * To install using this route, run the registry server on localhost:2092 and do one of the following: * Option 1 * 1.1) Manually add the package name to package.json dependencies: "@drpm/packageName~methodSpecificId": "[prefix]M.m.p" * 1.2) Run "npm install" in the root directory of your package * * Option 2 * 2.1) Run "npm install @drpm/packageName~methodSpecificId" in the root directory of your package * * Option 3 * 3.1) Run "npm install --registry http://localhost:2092 packageName~methodSpecificId" in the root directory of your package * * Option 4 * 4.1) Manually add the package name to package.json dependencies: "packageName~methodSpecificId": "[prefix]M.m.p" * 4.2) Run "npm install --registry http://localhost:2092" in the root directory of your package */ this.router.get(['/:scope/:name~:id', '/:scope/:name~:method~:id'], RegistryHandlers.install); /** * PUT route to handle npm publish request * @summary * To publish using this route, do one of the following: * Option 1 * 1.1) Run the registry server on localhost:2092 * 1.2) Set "name" in your package.json to one of the following: * 1.2.1) "name": "@drpm/packageName~methodSpecificId" (e.g. "@drpm/mydpk1~8w7ckznnw671az7nmkrd19ddctpj4spgt8sjqxkmnamdartxh1bo") * 1.2.2) "name": "@drpm/packageName~didMethod~methodSpecificId" (e.g. "@drpm/mydpk1~web~nonni.org") * 1.3) Run "npm publish" in the root directory of your package * * Option 2 * 2.1) Run the registry server on localhost:2092 * 2.2) Set "name" in your package.json to one of the following * 2.2.1) "name": "packageName~methodSpecificId" (e.g. "mydpk1~8w7ckznnw671az7nmkrd19ddctpj4spgt8sjqxkmnamdartxh1bo") * 2.2.2) "name": "packageName~didMethod~methodSpecificId" (e.g. "mydpk1~web~nonni.org") * 2.3) Run "npm publish --registry http://localhost:2092" in the root directory of your package */ this.router.put(['/:scope/:name~:id', '/:scope/:name~:method~:id'], RegistryHandlers.publish); } private static async home(_: Request, res: Response): Promise<any> { return res.status(200).json({ ok: true }); } private static async health(_: Request, res: Response): Promise<any> { return res.status(200).json({ message: 'Registry is up and running!' }); } private static async install(req: Request, res: Response): Promise<any> { try { const { scope, name, method = 'dht', id } = req.params ?? {}; const dependency = method === 'dht' ? `${scope}/${name}~${id}` : `${scope}/${name}~${method}~${id}`;; Logger.log(`Installing ${dependency} ...`); const missing = RegistryUtils.checkReqParams({scope, name, id}) ?? []; if(missing.length > 0) { const missingList = missing.join(', '); Logger.error(`RegistryHandlers: Missing required params - ${missingList}`); return res.status(404).json({ error: `Missing required params: ${missingList}` }); } const response = await DManager.readPackageRecord({ name: dependency }); const { data } = response ?? {}; if(ResponseUtils.fail(response)) { const {code, error} = response; Logger.error(`RegistryHandlers: Failed to find or fetch version`, response.error); return res.status(code).json({ error }); } const isValid = Object.keys(data).some(key => ['dist-tags', 'versions'].includes(key)); if(!isValid) { Logger.error(`RegistryHandlers: Invalid metadata`, data); return res.status(404).json({ error: 'Invalid metadata: missing keys "dist-tags" and "versions"' }); } const {'dist-tags': distTags, versions, endpoint} = data; const version = distTags.latest; const did = `did:${method}:${id}`; Logger.debug(`Using ${did} to find dwnEndpoints ...`); versions[version].dist.tarball = DrlBuilder .create({did, endpoint}) .buildDrlRead({ protocolPath : 'package/release', filters : { tags : [ { subKey: 'name', value: dependency }, { subKey: 'version', value: version } ], } }); Logger.log(`Returing tarball url ${data.versions[version].dist.tarball} for npm install`); return res.status(200).json(data); } catch (error: any) { Logger.error(`Error fetching or saving metadata or tarball`, error); return res.status(404).json({ error: `Failed to install package: ${error.message}` }); } } private static async publish(req: Request, res: Response): Promise<any> { try { const { scope, name, method = 'dht', id } = req.params ?? {}; const dependency = method === 'dht' ? `${scope}/${name}~${id}` : `${scope}/${name}~${method}~${id}`; const missing = RegistryUtils.checkReqParams({scope, name, id}) ?? []; if(missing.length > 0) { const missingList = missing.join(', '); Logger.error(`RegistryHandlers: Missing required params - ${missingList}`); return res.status(404).json({ error : `Missing required params: ${missingList}` }); } if(method !== 'dht') { return res.status(404).json({ error: `Unsupported DID method ${method}. DRPM only supports DHT method at this time` }); } const metadata = req.body; const isValid = Object.keys(metadata).some(key => ['dist-tags', 'versions'].includes(key)); if(!isValid) { Logger.error(`RegistryHandlers: Invalid metadata`, metadata); return res.status(404).json({ error: 'Invalid metadata: missing keys "dist-tags" and "versions"' }); } const { 'dist-tags': distTags, _attachments, versions } = metadata; const version = distTags.latest; const attachment = Buffer.from(_attachments[`${scope}/${name}~${id}-${version}.tgz`]?.data, 'base64'); const integrity = versions[version].dist.integrity; const pkgRes = await DManager.createPackage({ metadata }); if(ResponseUtils.fail(pkgRes)) { const {code, error} = pkgRes; Logger.error(`DrgRoute: Failed to publish package metadata`, error); return res.status(code).json({ error }); } Logger.log('Package metadata published successfully!'); const tarPack = pack(); const gzip = zlib.createGzip(); tarPack.entry({ name: `${name}~${id}-${version}.tgz` }, attachment); tarPack.finalize(); const chunks: Buffer[] = []; await pipeline(tarPack, gzip, async (source) => { for await (const chunk of source) { chunks.push(chunk); } }); const release = Buffer.concat(chunks); const relRes = await DManager.createPackageRelease({ version, release, integrity, name : dependency, parentId : pkgRes.data, }); if(ResponseUtils.fail(relRes)) { const {error, code} = relRes; Logger.error(`RegistryHandlers: Failed to upload tarball`, error); return res.status(code).json({ error }); } Logger.log(`Tarball uploaded successfully!`); return res.status(200).json({ message: 'Tarball uploaded successfully' }); } catch (error: any) { Logger.error('Error during publish:', error); return res.status(404).json({ error: `Failed to publish package: ${error.message}` }); } } } export default new RegistryHandlers().getRouter();