forma-embedded-view-sdk
Version:
The Forma Embedded View SDK is a JavaScript library for creating custom extensions in Autodesk Forma (previously Spacemaker).
227 lines (226 loc) • 8.27 kB
JavaScript
import {} from "./iframe-messenger.js";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { EmbeddedViewSdk } from "./embedded-view.js";
/**
* Access extension-specific services, e.g. registered endpoints and cloud storage.
*
* @remarks
* Available via {@link auto.Forma | Forma}.{@link index.EmbeddedViewSdk.extensions | extensions}.
*/
export class ExtensionsApi {
#iframeMessenger;
storage;
/** @hidden */
constructor(iframeMessenger) {
this.#iframeMessenger = iframeMessenger;
this.storage = new ExtensionsStorageApi(iframeMessenger);
}
// TODO: Link to more documentation on how to register endpoints for extensions.
/**
* Invoke an extension-specific endpoint.
*
* Requires edit access. See {@link EmbeddedViewSdk.getCanEdit | getCanEdit} for more info.
*
* @typeParam T - Type of data returned by endpoint.
*
* @returns The data returned by the endpoint.
*
* @throws {@link index.RequestError | RequestError} with type set to `invoke-endpoint-unsuccessful` if
* the endpoint returns an unsuccesful status code. The data field of RequestError will be an
* object with the fields `status`, `statusText`, `contentType` and `body` (string). If you
* return JSON errors from your API you can parse the body field after checking the status
* and content type.
*/
async invokeEndpoint(request) {
return (await this.#iframeMessenger.sendRequest("extensions/invoke-endpoint", request));
}
}
/**
* Extension storage provides a way for extension authors to save data in a given authcontext
* which the extension is installed for without needing their own storage layer.
*
* Any data can be stored and fetched directly via the SDK. Data is stored in
* AWS S3 and accessed using presigned URLs. This way we only act as a proxy for
* generating the presigned links without ever having an opinion on what data is
* actually stored.
*
* NOTE: Even though extension authors can write data on behalf of a user in a specific context, it's
* still the user who owns the data, and can they can access it without going through the extension
* itself.
*
* @see https://aws.amazon.com/s3/
*
* @remarks
* Available via {@link auto.Forma | Forma}.{@link index.EmbeddedViewSdk.extensions | extensions}.{@link extensions.ExtensionsApi.storage | storage}.
*/
export class ExtensionsStorageApi {
#iframeMessenger;
#textDecoder = new TextDecoder();
/** @hidden */
constructor(iframeMessenger) {
this.#iframeMessenger = iframeMessenger;
}
/**
* Add or replace a storage object.
*
* Requires edit access. See {@link EmbeddedViewSdk.getCanEdit | getCanEdit} for more info.
* If you wish to store an object to the hub of the current project, the user needs
* edit access to the hub See {@link EmbeddedViewSdk.getCanEditHub | getCanEditHub}
*
* @example
* // STORE JSON
* const myObject = {
* someData: "someValue"
* }
*
* await Forma.extensions.storage.setObject({key: "some-key", data: JSON.stringify(myObject)})
*
* @example
* // STORE Float32Array
*
* function arrayToBuffer(array: Float32Array): ArrayBuffer {
* const buffer = new ArrayBuffer(array.length * Float32Array.BYTES_PER_ELEMENT);
* const arr = new Float32Array(buffer);
* arr.set(array);
* return arr;
* }
*
* const arr = new Float32Array(100).fill(Math.random())
* await Forma.extensions.storage.setObject({key: "someKey", data: arrayToBuffer(arr)})
*
*/
async setObject(request) {
const url = await this.#iframeMessenger.sendRequest("extension-installation-storage/set", {
key: request.key,
metadata: request.metadata,
authcontext: request.authcontext,
});
const awsRes = await fetch(url, {
method: "PUT",
body: request.data,
});
if (!awsRes.ok) {
throw new Error(`Failed to put data to S3: ${awsRes.status}: ${awsRes.statusText}`);
}
}
/**
* Utility function to fetch string objects without needing to decode an array buffer.
*
* If you wish to get an object on the current project's hub, the user needs
* view access to the hub See {@link EmbeddedViewSdk.getCanViewHub | getCanViewHub}
*
* @returns The data parsed as UTF-8, including metadata if present.
*
* @example
* // READING JSON
* const res = await Forma.extensions.storage.getTextObject({
* key: "some-key",
* })
* if (!res) {
* return
* }
* const metadata = JSON.parse(data.metadata ?? "{}")
* const data = res.data
*
* @example
* function loadImageFromEncodedPng(
* url: string,
* ): Promise<HtmlImageElement> {
* return new Promise((resolve, reject) => {
* const img = new Image()
* img.onload = () => {
* resolve(img)
* }
* img.onerror = () => {
* reject(new Error("Failed to load image"))
* }
* img.src = url
* })
* }
*
* async function createCanvasFromDataUrl(
* url: string,
* ): Promise<HtmlCanvasElement | void> {
* const canvas = document.createElement("canvas")
* const ctx = canvas.getContext("2d")
* const img = await loadImage(url)
* canvas.height = img.height
* canvas.width = img.width
* ctx.drawImage(ctx, img, 0, 0)
* return canvas
* }
*
* const res = await Forma.extensions.storage.getTextObject({
* key: "some-png-key",
* })
* if (!res) {
* return
* }
* const canvas = createCanvasFromDataUrl(res.data)
*
*/
async getTextObject(request) {
const res = await this.#iframeMessenger.sendRequest("extension-installation-storage/get", request);
if (!res) {
return;
}
return {
...res,
data: this.#textDecoder.decode(res.data),
};
}
/**
* Fetch the data for the specified key.
*
* Use this function when you're **not** fetching text data, such as geometry
* or analysis results.
*
* If you wish to get an object on the current project's hub, the user needs
* view access to the hub See {@link EmbeddedViewSdk.getCanViewHub | getCanViewHub}
*
* @returns The data as an ArrayBuffer, including metadata if present.
*
* @example
* const res = await Forma.extensions.storage.getBinaryObject({
* key: "my-float32-array",
* })
* if (!res) {
* return
* }
* const terrainSlope: Float32Array = new Float32Array(res.data)
* const metadata = JSON.parse(res.metadata ?? "{}")
*
*/
async getBinaryObject(request) {
return await this.#iframeMessenger.sendRequest("extension-installation-storage/get", request);
}
/**
* List all storage objects for the extension in the current authcontext.
*
* @returns List of filtered objects with relevant information.
*
* @example
* const availableObjects = await Forma.extensions.storage.listObjects().results
*/
async listObjects(request) {
return await this.#iframeMessenger.sendRequest("extension-installation-storage/list", request);
}
/**
* Delete object corresponding to the specified key.
*
* Requires edit access. See {@link EmbeddedViewSdk.getCanEdit | getCanEdit} for more info.
* If you wish to delete an object to the hub of the current project, the user needs
* edit access to the hub See {@link EmbeddedViewSdk.getCanEditHub | getCanEditHub}
*
* @example
* // Store a JSON object and delete it afterwards
* const myObject = {
* someData: "someValue"
* }
* await Forma.extensions.storage.setObject({key: "some-key", data: JSON.stringify(myObject)})
* await Forma.extensions.storage.deleteObject({key: "some-key"}})
*/
async deleteObject(request) {
await this.#iframeMessenger.sendRequest("extension-installation-storage/delete", request);
}
}