forge-server-utils
Version:
Tools for accessing Autodesk Forge APIs from Node.js apps and from browser.
438 lines (437 loc) • 22.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModelDerivativeClient = exports.ManifestHelper = exports.ThumbnailSize = exports.urnify = void 0;
const stream_1 = require("stream");
const common_1 = require("./common");
const isNullOrUndefined = (value) => value === null || value === undefined;
const RootPath = 'modelderivative/v2';
const ReadTokenScopes = ['data:read'];
const WriteTokenScopes = ['data:read', 'data:write', 'data:create'];
const RetryDelay = 5000;
/**
* Converts ID of an object to base64-encoded URN expected by {@link ModelDerivativeClient}.
* @param {string} id Object ID.
* @returns {string} base64-encoded object URN.
* @example
* urnify('urn:adsk.objects:os.object:my-bucket/my-file.dwg');
* // Returns 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6bXktYnVja2V0L215LWZpbGUuZHdn'
*/
function urnify(id) {
return Buffer.from(id).toString('base64').replace(/=/g, '');
}
exports.urnify = urnify;
var ThumbnailSize;
(function (ThumbnailSize) {
ThumbnailSize[ThumbnailSize["Small"] = 100] = "Small";
ThumbnailSize[ThumbnailSize["Medium"] = 200] = "Medium";
ThumbnailSize[ThumbnailSize["Large"] = 400] = "Large";
})(ThumbnailSize || (exports.ThumbnailSize = ThumbnailSize = {}));
/**
* Utility class for querying {@see IDerivativeManifest}.
*/
class ManifestHelper {
constructor(manifest) {
this.manifest = manifest;
}
/**
* Finds manifest derivatives with matching 'guid', 'type', or 'role' properties.
* @param {object} query Dictionary of the requested properties and values.
* @returns {DerivativeChild[]} Matching derivatives.
*/
search(query) {
let matches = [];
this.traverse((child) => {
if ((isNullOrUndefined(query.guid) || child.guid === query.guid)
&& (isNullOrUndefined(query.type) || child.type === query.type)
&& (isNullOrUndefined(query.role) || child.role === query.role)) {
matches.push(child);
}
return true;
});
return matches;
}
/**
* Traverses all derivatives, executing the input callback for each one.
* @param {(child: DerivativeChild) => boolean} callback Function to be called for each derivative,
* returning a bool indicating whether the traversal should recurse deeper in the manifest hierarchy.
*/
traverse(callback) {
function process(node, callback) {
const proceed = callback(node);
if (proceed && node.children) {
for (const child of node.children) {
process(child, callback);
}
}
}
for (const derivative of this.manifest.derivatives) {
if (derivative.children) {
for (const child of derivative.children) {
process(child, callback);
}
}
}
}
}
exports.ManifestHelper = ManifestHelper;
/**
* Client providing access to Autodesk Forge
* {@link https://forge.autodesk.com/en/docs/model-derivative/v2|model derivative APIs}.
* @tutorial model-derivative
*/
class ModelDerivativeClient extends common_1.ForgeClient {
/**
* Initializes new client with specific authentication method.
* @param {IAuthOptions} auth Authentication object,
* containing either `client_id` and `client_secret` properties (for 2-legged authentication),
* or a single `token` property (for 2-legged or 3-legged authentication with pre-generated access token).
* @param {string} [host="https://developer.api.autodesk.com"] Forge API host.
* @param {Region} [region="US"] Forge availability region.
*/
constructor(auth, host, region) {
super(RootPath, auth, host, region);
}
getUrl(path) {
return new URL(this.host + '/' + RootPath + '/' + path);
}
/**
* Gets a list of supported translation formats
* ({@link https://forge.autodesk.com/en/docs/model-derivative/v2/reference/http/formats-GET|docs}).
* @async
* @yields {Promise<IDerivativeFormats>} Dictionary of all supported output formats
* mapped to arrays of formats these outputs can be obtained from.
* @throws Error when the request fails, for example, due to insufficient rights.
*/
async formats() {
const response = await this.get('designdata/formats', {}, ReadTokenScopes);
return response.formats;
}
/**
* Submits a translation job
* ({@link https://forge.autodesk.com/en/docs/model-derivative/v2/reference/http/job-POST|docs}).
* @async
* @param {string} urn Document to be translated.
* @param {IDerivativeOutputType[]} outputs List of requested output formats.
* @param {string} [pathInArchive] Optional relative path to root design if the translated file is an archive.
* @param {boolean} [force] Force translation even if a derivative already exists.
* @param {string} [workflowId] Optional workflow ID to be used with Forge Webhooks.
* @param {object} [workflowAttr] Optional workflow attributes to be used with Forge Webhooks.
* @returns {Promise<IJob>} Translation job details, with properties 'result',
* 'urn', and 'acceptedJobs'.
* @throws Error when the request fails, for example, due to insufficient rights.
*/
async submitJob(urn, outputs, pathInArchive, force, workflowId, workflowAttr) {
const params = {
input: {
urn: urn
},
output: {
formats: outputs,
destination: {
region: this.region
}
}
};
if (pathInArchive) {
params.input.compressedUrn = true;
params.input.rootFilename = pathInArchive;
}
if (workflowId) {
params.misc = {
workflow: workflowId
};
if (workflowAttr) {
params.misc.workflowAttribute = workflowAttr;
}
}
const headers = {};
if (force) {
headers['x-ads-force'] = 'true';
}
return this.post('designdata/job', params, headers, WriteTokenScopes);
}
/**
* Retrieves manifest of a derivative
* ({@link https://forge.autodesk.com/en/docs/model-derivative/v2/reference/http/urn-manifest-GET|docs}).
* @async
* @param {string} urn Document derivative URN.
* @returns {Promise<IDerivativeManifest>} Document derivative manifest.
* @throws Error when the request fails, for example, due to insufficient rights.
*/
async getManifest(urn) {
return this.get(this.region === common_1.Region.EMEA ? `regions/eu/designdata/${urn}/manifest` : `designdata/${urn}/manifest`, {}, ReadTokenScopes);
}
/**
* Deletes manifest
* ({@link https://forge.autodesk.com/en/docs/model-derivative/v2/reference/http/urn-manifest-DELETE|docs}).
* @async
* @param {string} urn Document derivative URN.
* @throws Error when the request fails, for example, due to insufficient rights, or incorrect scopes.
*/
async deleteManifest(urn) {
return this.delete(this.region === common_1.Region.EMEA ? `regions/eu/designdata/${urn}/manifest` : `designdata/${urn}/manifest`, {}, WriteTokenScopes);
}
// Generates URL for downloading specific derivative
// https://forge.autodesk.com/en/docs/model-derivative/v2/reference/http/urn-manifest-derivativeUrn-signedcookies-GET
async getDerivativeDownloadUrl(modelUrn, derivativeUrn) {
const endpoint = this.region === common_1.Region.EMEA
? `regions/eu/designdata/${modelUrn}/manifest/${derivativeUrn}/signedcookies`
: `designdata/${modelUrn}/manifest/${derivativeUrn}/signedcookies`;
const config = {};
await this.setAuthorization(config, ReadTokenScopes);
const resp = await this.axios.get(endpoint, config);
const record = {
etag: resp.data.etag,
size: resp.data.size,
url: resp.data.url,
'content-type': resp.data['content-type'],
expiration: resp.data.expiration,
cookies: {}
};
if (!resp || !resp.headers || !resp.headers['set-cookie']) {
return record;
}
for (const cookie of resp.headers['set-cookie']) {
const tokens = cookie.split(';');
const [key, val] = tokens[0].trim().split('=');
record.cookies[key] = val;
}
return record;
}
/**
* Downloads content of a specific model derivative
* ({@link https://forge.autodesk.com/en/docs/model-derivative/v2/reference/http/urn-manifest-derivativeurn-GET/|docs}).
* @async
* @param {string} modelUrn Model URN.
* @param {string} derivativeUrn Derivative URN.
* @returns {Promise<ArrayBuffer>} Derivative content.
* @throws Error when the request fails, for example, due to insufficient rights, or incorrect scopes.
*/
async getDerivative(modelUrn, derivativeUrn) {
const downloadInfo = await this.getDerivativeDownloadUrl(modelUrn, derivativeUrn);
const resp = await this.axios.get(downloadInfo.url, {
responseType: 'arraybuffer',
decompress: false,
headers: {
Cookie: Object.keys(downloadInfo.cookies).map(key => `${key}=${downloadInfo.cookies[key]}`).join(';')
}
});
return resp.data;
}
/**
* Downloads content of a specific model derivative
* ({@link https://forge.autodesk.com/en/docs/model-derivative/v2/reference/http/urn-manifest-derivativeurn-GET/|docs}).
* @async
* @param {string} modelUrn Model URN.
* @param {string} derivativeUrn Derivative URN.
* @returns {Promise<ReadableStream>} Derivative content stream.
* @throws Error when the request fails, for example, due to insufficient rights, or incorrect scopes.
*/
async getDerivativeStream(modelUrn, derivativeUrn) {
const downloadInfo = await this.getDerivativeDownloadUrl(modelUrn, derivativeUrn);
const resp = await this.axios.get(downloadInfo.url, {
responseType: 'stream',
decompress: false
});
return resp.data;
}
/**
* Downloads content of a specific model derivative asset in chunks
* ({@link https://forge.autodesk.com/en/docs/model-derivative/v2/reference/http/urn-manifest-derivativeurn-GET/|docs}).
* @param {string} modelUrn Model URN.
* @param {string} derivativeUrn Derivative URN.
* @param {number} [maxChunkSize=1<<24] Maximum size (in bytes) of a single downloaded chunk.
* @returns {Readable} Readable stream with the content of the downloaded derivative asset.
* @throws Error when the request fails, for example, due to insufficient rights, or incorrect scopes.
*/
getDerivativeChunked(modelUrn, derivativeUrn, maxChunkSize = 1 << 24) {
const client = this;
async function* read() {
const downloadInfo = await client.getDerivativeDownloadUrl(modelUrn, derivativeUrn);
const contentLength = downloadInfo.size;
let resp = await client.axios.head(downloadInfo.url);
let streamedBytes = 0;
while (streamedBytes < contentLength) {
const chunkSize = Math.min(maxChunkSize, contentLength - streamedBytes);
resp = await client.axios.get(downloadInfo.url, {
responseType: 'arraybuffer',
decompress: false,
headers: {
Range: `bytes=${streamedBytes}-${streamedBytes + chunkSize - 1}`
}
});
yield resp.data;
streamedBytes += chunkSize;
}
}
return stream_1.Readable.from(read());
}
/**
* Retrieves metadata of a derivative
* ({@link https://forge.autodesk.com/en/docs/model-derivative/v2/reference/http/urn-metadata-GET|docs}).
* @async
* @param {string} urn Document derivative URN.
* @returns {Promise<IDerivativeMetadata>} Document derivative metadata.
* @throws Error when the request fails, for example, due to insufficient rights.
*/
async getMetadata(urn) {
return this.get(this.region === common_1.Region.EMEA ? `regions/eu/designdata/${urn}/metadata` : `designdata/${urn}/metadata`, {}, ReadTokenScopes);
}
/**
* Retrieves metadata of a derivative as a readable stream
* ({@link https://forge.autodesk.com/en/docs/model-derivative/v2/reference/http/urn-metadata-GET|docs}).
* @async
* @param {string} urn Document derivative URN.
* @returns {Promise<ReadableStream>} Document derivative metadata.
* @throws Error when the request fails, for example, due to insufficient rights.
*/
async getMetadataStream(urn) {
return this.getStream(this.region === common_1.Region.EMEA ? `regions/eu/designdata/${urn}/metadata` : `designdata/${urn}/metadata`, {}, ReadTokenScopes);
}
/**
* Retrieves object tree of a specific viewable
* ({@link https://forge.autodesk.com/en/docs/model-derivative/v2/reference/http/urn-metadata-guid-GET|docs}).
* @async
* @param {string} urn Document derivative URN.
* @param {string} guid Viewable GUID.
* @param {boolean} [force] Force query even when exceeding the size limit (20MB).
* @param {number} [objectId] If specified, retrieves the sub-tree that has the specified object ID as its parent node.
* If this parameter is not specified, retrieves the entire object tree.
* @param {boolean} [retryOn202] Keep repeating the request while the response status is 202 (indicating that the resource is being prepared).
* @param {boolean} [includeLevel1] If true, grabs only the first level from the specified objectId. ObjectId must be provided.
* @returns {Promise<IDerivativeTree>} Viewable object tree.
* @throws Error when the request fails, for example, due to insufficient rights.
*/
async getViewableTree(urn, guid, force, objectId, retryOn202 = true, includeLevel1) {
const url = this.getUrl(this.region === common_1.Region.EMEA ? `regions/eu/designdata/${urn}/metadata/${guid}` : `designdata/${urn}/metadata/${guid}`);
if (force)
url.searchParams.append('forceget', 'true');
if (objectId)
url.searchParams.append('objectid', objectId.toString());
if (includeLevel1 && objectId)
url.searchParams.append('level', "1");
const config = {};
await this.setAuthorization(config, ReadTokenScopes);
let resp = await this.axios.get(url.toString(), config);
while (resp.status === 202 && retryOn202) {
await (0, common_1.sleep)(RetryDelay);
await this.setAuthorization(config, ReadTokenScopes);
resp = await this.axios.get(url.toString(), config);
}
return resp.data;
}
/**
* Retrieves object tree of a specific viewable as a readable stream
* ({@link https://forge.autodesk.com/en/docs/model-derivative/v2/reference/http/urn-metadata-guid-GET|docs}).
* @async
* @param {string} urn Document derivative URN.
* @param {string} guid Viewable GUID.
* @param {boolean} [force] Force query even when exceeding the size limit (20MB).
* @param {number} [objectId] If specified, retrieves the sub-tree that has the specified object ID as its parent node.
* If this parameter is not specified, retrieves the entire object tree.
* @param {boolean} [retryOn202] Keep repeating the request while the response status is 202 (indicating that the resource is being prepared).
* @returns {Promise<ReadableStream>} Readable stream.
* @throws Error when the request fails, for example, due to insufficient rights.
*/
async getViewableTreeStream(urn, guid, force, objectId, retryOn202 = true) {
const url = this.getUrl(this.region === common_1.Region.EMEA ? `regions/eu/designdata/${urn}/metadata/${guid}` : `designdata/${urn}/metadata/${guid}`);
if (force)
url.searchParams.append('forceget', 'true');
if (objectId)
url.searchParams.append('objectid', objectId.toString());
const config = { responseType: 'stream' };
await this.setAuthorization(config, ReadTokenScopes);
let resp = await this.axios.get(url.toString(), config);
while (resp.status === 202 && retryOn202) {
await (0, common_1.sleep)(RetryDelay);
await this.setAuthorization(config, ReadTokenScopes);
resp = await this.axios.get(url.toString(), config);
}
return resp.data;
}
/**
* Retrieves properties of a specific viewable
* ({@link https://forge.autodesk.com/en/docs/model-derivative/v2/reference/http/urn-metadata-guid-properties-GET|docs}).
* @async
* @param {string} urn Document derivative URN.
* @param {string} guid Viewable GUID.
* @param {boolean} [force] Force query even when exceeding the size limit (20MB).
* @param {number} [objectId] The Object ID of the object you want to query properties for.
* If `objectid` is omitted, the server returns properties for all objects.
* @param {boolean} [retryOn202] Keep repeating the request while the response status is 202 (indicating that the resource is being prepared).
* @returns {Promise<IDerivativeProps>} Viewable properties.
* @throws Error when the request fails, for example, due to insufficient rights.
*/
async getViewableProperties(urn, guid, force, objectId, retryOn202 = true) {
const url = this.getUrl(this.region === common_1.Region.EMEA ? `regions/eu/designdata/${urn}/metadata/${guid}/properties` : `designdata/${urn}/metadata/${guid}/properties`);
if (force)
url.searchParams.append('forceget', 'true');
if (objectId)
url.searchParams.append('objectid', objectId.toString());
const config = {};
await this.setAuthorization(config, ReadTokenScopes);
let resp = await this.axios.get(url.toString(), config);
while (resp.status === 202 && retryOn202) {
await (0, common_1.sleep)(RetryDelay);
await this.setAuthorization(config, ReadTokenScopes);
resp = await this.axios.get(url.toString(), config);
}
return resp.data;
}
/**
* Retrieves properties of a specific viewable as a readable stream
* ({@link https://forge.autodesk.com/en/docs/model-derivative/v2/reference/http/urn-metadata-guid-properties-GET|docs}).
* @async
* @param {string} urn Document derivative URN.
* @param {string} guid Viewable GUID.
* @param {boolean} [force] Force query even when exceeding the size limit (20MB).
* @param {number} [objectId] The Object ID of the object you want to query properties for.
* If `objectid` is omitted, the server returns properties for all objects.
* @param {boolean} [retryOn202] Keep repeating the request while the response status is 202 (indicating that the resource is being prepared).
* @returns {Promise<ReadableStream>} Readable stream.
* @throws Error when the request fails, for example, due to insufficient rights.
*/
async getViewablePropertiesStream(urn, guid, force, objectId, retryOn202 = true) {
const url = this.getUrl(this.region === common_1.Region.EMEA ? `regions/eu/designdata/${urn}/metadata/${guid}/properties` : `designdata/${urn}/metadata/${guid}/properties`);
if (force)
url.searchParams.append('forceget', 'true');
if (objectId)
url.searchParams.append('objectid', objectId.toString());
const config = { responseType: 'stream' };
await this.setAuthorization(config, ReadTokenScopes);
let resp = await this.axios.get(url.toString(), config);
while (resp.status === 202 && retryOn202) {
await (0, common_1.sleep)(RetryDelay);
await this.setAuthorization(config, ReadTokenScopes);
resp = await this.axios.get(url.toString(), config);
}
return resp.data;
}
/**
* Retrieves derivative thumbnail
* ({@link https://forge.autodesk.com/en/docs/model-derivative/v2/reference/http/urn-thumbnail-GET|docs}).
* @async
* @param {string} urn Document derivative URN.
* @param {ThumbnailSize} [size=ThumbnailSize.Medium] Thumbnail size (small: 100x100 px, medium: 200x200 px, or large: 400x400 px).
* @returns {Promise<ArrayBuffer>} Thumbnail data.
* @throws Error when the request fails, for example, due to insufficient rights, or incorrect scopes.
*/
async getThumbnail(urn, size = ThumbnailSize.Medium) {
const endpoint = this.region === common_1.Region.EMEA ? `regions/eu/designdata/${urn}/thumbnail` : `designdata/${urn}/thumbnail`;
return this.getBuffer(endpoint + '?width=' + size, {}, ReadTokenScopes);
}
/**
* Retrieves derivative thumbnail stream
* ({@link https://forge.autodesk.com/en/docs/model-derivative/v2/reference/http/urn-thumbnail-GET|docs}).
* @async
* @param {string} urn Document derivative URN.
* @param {ThumbnailSize} [size=ThumbnailSize.Medium] Thumbnail size (small: 100x100 px, medium: 200x200 px, or large: 400x400 px).
* @returns {Promise<ReadableStream>} Thumbnail data stream.
* @throws Error when the request fails, for example, due to insufficient rights, or incorrect scopes.
*/
async getThumbnailStream(urn, size = ThumbnailSize.Medium) {
const endpoint = this.region === common_1.Region.EMEA ? `regions/eu/designdata/${urn}/thumbnail` : `designdata/${urn}/thumbnail`;
return this.getStream(endpoint + '?width=' + size, {}, ReadTokenScopes);
}
}
exports.ModelDerivativeClient = ModelDerivativeClient;