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
308 lines (266 loc) • 12.4 kB
text/typescript
import { Record } from '@web5/api';
import { DWeb5 } from '../../lib/dweb5.js';
import { RegistryUtils } from '../../registry/utils.js';
import { DidResolver } from '../did/resolver.js';
import { DrlBuilder } from '../dwn/drl-builder.js';
import dwn from '../dwn/protocol.js';
import { Logger } from '../logger.js';
import { stringifier } from '../misc.js';
import { ResponseUtils } from '../response.js';
import {
CreatePackageParams,
CreateReleaseParams,
DpkDwnResponse,
DpkMetadata,
DpkRequest,
ReadPackageParams,
ReadReleaseParams,
RegistryResponse
} from '../types.js';
const { web5, did } = DWeb5.connectSync();
export class DManager {
// Get DWeb Node endpoints from Did Doc on respective network based on DID Method
static async getDwnEndpoints(didToResolve: string = did): Promise<string[]> {
const { didDocument } = await DidResolver.resolve(didToResolve) ?? {};
Logger.info(`DManager: Resolved didDocument=${stringifier(didDocument)}`);
const services = didDocument?.service;
const didServiceEndpoint = services?.find(
service => service.type === 'DecentralizedWebNode'
)?.serviceEndpoint ?? (
process.env.NODE_ENV === 'development'
? ['http://localhost:3000/']
: ['https://dwn.drpm.tools/']
);
const serviceEndpoints = Array.isArray(didServiceEndpoint) ? didServiceEndpoint : [didServiceEndpoint];
return serviceEndpoints.map(endpoint => endpoint.replace(/\/$/, ''));
}
static async readPackageRecord({ name }: { name: string }): Promise<RegistryResponse> {
for (const endpoint of await this.getDwnEndpoints()) {
Logger.info(`DManager: Fetching DPK ${name} from ${endpoint} ...`);
const drl = DrlBuilder
.create({ did, endpoint })
.buildDrlRead({
protocolPath : 'package',
filters : { tags: [{ subKey: 'name', value: name }] }
});
const response: Response = await fetch(drl);
if (ResponseUtils.fetchFail(response)) {
Logger.error('DManager: DWeb Node request failed', response);
return RegistryUtils.routeFailure({ error: response.statusText });
}
const metadata: DpkMetadata = await response.json();
if (!metadata) {
Logger.error('DManager: DWeb Node request failed - no metadata', response);
return RegistryUtils.routeFailure({ error: 'No data returned' });
}
return RegistryUtils.routeSuccess({ data: { ...metadata, endpoint } });
}
return RegistryUtils.routeFailure({ error: 'All DWeb Node requests failed' });
}
// Fetch DPK metadata from DWeb Node DRPM protocol at /package protocol path
static async readPackage({ builder, name }: ReadPackageParams): Promise<RegistryResponse> {
try {
const drl = builder.buildDrlRead({ protocolPath: 'package', filters: { tags: [{ subKey: 'name', value: name }], } });
Logger.debug(`DManager: Using DRL ${drl} to fetch DPK ${name} ...`);
const response: Response = await fetch(drl);
if (ResponseUtils.fetchFail(response)) {
Logger.error('DManager: DWeb Node request failed', response);
return RegistryUtils.routeFailure({ error: response.statusText });
}
const data: DpkDwnResponse = await response.json();
if (!data) {
Logger.error('DManager: DWeb Node request failed - no data', response);
return RegistryUtils.routeFailure({ error: 'No data returned' });
}
return RegistryUtils.routeSuccess({ data });
} catch (error: any) {
Logger.error('DManager: DWeb Node request error catch', error);
return RegistryUtils.routeFailure({ error: error.message });
}
}
// Fetch DPK release from DWeb Node DRPM protocol at package/release protocol path
static async readRelease({ builder, name, version }: ReadReleaseParams): Promise<RegistryResponse> {
try {
const drl = builder.buildDrlRead({
protocolPath : 'package/release',
filters : {
tags : [
{ subKey: 'name', value: name },
{ subKey: 'version', value: version }
],
}
});
Logger.debug(`DManager: Using DRL ${drl} to fetch DPK ${name}@${version} ...`);
const response: Response = await fetch(drl);
if (ResponseUtils.fetchFail(response)) {
Logger.error('DManager: DWeb Node request error', response);
return RegistryUtils.routeFailure({ error: response.statusText });
}
Logger.debug(`DManager: DWeb Node request success`, response);
if (response.headers.get('content-type') !== 'application/gzip') {
Logger.error('DManager: DWeb Node request error - bad content-type', response);
return RegistryUtils.routeFailure({ error: `Bad content-type: ${response.headers.get('content-type')}` });
}
const data = response.body;
if (!data) {
Logger.error('DManager: DWeb Node request failed - no data', response);
return RegistryUtils.routeFailure({ error: 'No tarball data returned' });
}
return RegistryUtils.routeSuccess({ data });
} catch (error: any) {
Logger.error('DManager: DWeb Node request failed', error);
return RegistryUtils.routeFailure({ error: error.message });
}
}
// Fetch DPK from DWeb Node: either metadata or release
static async readDpk({ did, dpk: { name, version, path } }: DpkRequest): Promise<RegistryResponse> {
try {
for (const endpoint of await this.getDwnEndpoints()) {
Logger.info(`DManager: Fetching DPK ${name}@${version} from ${endpoint} ...`);
const builder = DrlBuilder.create({ did, endpoint });
const response: RegistryResponse = path === 'package/release' && version
? await this.readRelease({ builder, name, version })
: await this.readPackage({ builder, name });
if (ResponseUtils.fail(response)) {
Logger.error('DManager: Error during DWeb Node request, continuing ...', response);
continue;
}
if (!response.data) {
Logger.error('DManager: No data returned from DWeb Node request, continuing ...', response);
continue;
}
return response;
}
return RegistryUtils.routeFailure({ error: 'All DWeb Node requests failed' });
} catch (error: any) {
Logger.error('DManager: Error catch during DWeb Node request', error);
return RegistryUtils.routeFailure({ error: error.message });
}
}
// static async createPackageDidWeb({ metadata, did }: CreatePackageDidWebParams): Promise<RegistryResponse> {
// try {
// // const userAgent = await Web5UserAgent.create();
// const _dwn = new DwnApi({
// agent : await Web5UserAgent.create(),
// did,
// });
// await RecordsWrite.create({
// data : metadata,
// published : true,
// dataFormat : 'application/json',
// schema : dwn.types.package.schema,
// protocolPath : 'package',
// protocol : dwn.protocol,
// tags : {
// name : metadata.name,
// latest : metadata.version
// },
// });
// const {name, version} = metadata;
// for (const endpoint of await DManager.getDwnEndpoints(did)) {
// Logger.info(`DManager: Creating package record for ${name}@${version} at ${endpoint} ...`);
// const builder = DrlBuilder.create({ did, endpoint });
// const response: RegistryResponse = path === 'package' || !version
// ? await this.readPackage({ builder, name })
// : await this.readRelease({ builder, name, version });
// if (ResponseUtils.fail(response)) {
// Logger.error('DManager: Error during DWeb Node request, continuing ...', response);
// continue;
// }
// if(!response.data) {
// Logger.error('DManager: No data returned from DWeb Node request, continuing ...', response);
// continue;
// }
// return RegistryUtils.routeSuccess({ data: response });
// }
// return RegistryUtils.routeFailure({ error: 'All DWeb Node requests failed' });
// } catch(error: any) {
// Logger.error('DManager: Error catch during DWeb Node request', error);
// return RegistryUtils.routeFailure({ error: error.message });
// }
// }
static async createPackage({ metadata }: CreatePackageParams): Promise<RegistryResponse> {
try {
const { record, status: create } = await web5.dwn.records.create({
store : true,
data : metadata,
message : {
published : true,
dataFormat : 'application/json',
schema : dwn.types.package.schema,
protocolPath : 'package',
protocol : dwn.protocol,
tags : {
name : metadata.name,
latest : metadata['dist-tags'].latest
},
},
});
if (ResponseUtils.dwnFail({ status: create })) {
const error = 'Failed to create local package record - Failing DWN Status';
Logger.error(`DManager: ${error}`, create);
return RegistryUtils.routeFailure({ error: `${error}: ${create.detail}` });
}
if (!record) {
const error = 'Failed to create local package record - No Record Returned';
Logger.error(`DManager: ${error}`, create);
return RegistryUtils.routeFailure({ error: `${error}: ${create.detail}` });
}
Logger.log('DManager: Package record created!', create);
const data: Record = await record?.data.json();
Logger.debug('DManager: Package record data parsed!', data);
const { status: send } = await record.send(did);
if (ResponseUtils.dwnFail({ status: send })) {
const error = 'Failed to send remote package record - Failing DWN Status';
Logger.error(`DManager: ${error}`, create);
return RegistryUtils.routeFailure({ error: `${error}: ${create.detail}` });
}
Logger.debug('DManager: Package record sent!', send);
return RegistryUtils.routeSuccess({ data: record.id });
} catch (error: any) {
Logger.error('DManager: Error catch during DWebNode records create', error);
return RegistryUtils.routeFailure({ error: error.message });
}
}
static async createPackageRelease({ parentId, name, version, integrity, release }: CreateReleaseParams): Promise<RegistryResponse> {
try {
const { record = undefined, status }: { record?: Record; status: any } = await web5.dwn.records.create({
data : release,
store : true,
message : {
published : true,
parentContextId : parentId,
dataFormat : 'application/gzip',
schema : dwn.types.release.schema,
protocolPath : 'package/release',
protocol : dwn.protocol,
tags : { name, version, integrity }
},
});
if (ResponseUtils.dwnFail({ status })) {
const error = 'Failed to create local release record - Failing DWN Status';
Logger.error(`DManager: ${error}`, status);
return RegistryUtils.routeFailure({ error: `${error}: ${status.detail}` });
}
if (!record) {
const error = 'Failed to create local release record - No Record Returned';
Logger.error(`DManager: ${error}`, status);
return RegistryUtils.routeFailure({ error: `${error}: ${status.detail}` });
}
Logger.log('DManager: Release record created in local!', status);
const data = record.toJSON();;
Logger.debug('DManager: Release record data parsed!', data);
const { status: send } = await record.send(did);
if (ResponseUtils.dwnFail({ status: send })) {
const error = 'Failed to send release record to remote - Failed Send Status';
Logger.error(`DManager: ${error}`, send);
return RegistryUtils.routeFailure({ error: `${error}: ${send.detail}` });
}
Logger.debug('DManager: Release record sent to remote!', send);
return RegistryUtils.routeSuccess({ data: record });
} catch (error: any) {
Logger.error('DManager: Error catch during DWebNode records create', error);
return RegistryUtils.routeFailure({ error: error.message });
}
}
}