UNPKG

forma-embedded-view-sdk

Version:

The Forma Embedded View SDK is a JavaScript library for creating custom extensions in Autodesk Forma Site Design (previously Spacemaker).

227 lines (226 loc) 8.27 kB
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); } }