├── .gitignore ├── .npmignore ├── lib ├── index.d.ts ├── config.d.ts ├── config.js ├── utils.d.ts ├── utils.js ├── api-class.d.ts ├── api-endpoints.d.ts ├── api-class.js ├── api-endpoints.js └── figma-api.min.js ├── src ├── index.ts ├── config.ts ├── utils.ts ├── api-class.ts └── api-endpoints.ts ├── jest.config.js ├── tests ├── config.test.ts ├── index.test.ts ├── utils.test.ts ├── api-endpoints.test.ts └── api-class.test.ts ├── LICENSE ├── package.json ├── CHANGELOG.md ├── tsconfig.json ├── .github └── copilot-instructions.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /playground 3 | /.idea 4 | /coverage -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | playground 2 | src 3 | node_modules 4 | tsconfig.json 5 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './config'; 2 | export * from './api-class'; 3 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config'; 2 | export * from './api-class'; 3 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | export const API_DOMAIN = 'https://api.figma.com'; 2 | export const API_VER = 'v1'; 3 | export const API_VER_WEBHOOKS = 'v2'; 4 | -------------------------------------------------------------------------------- /lib/config.d.ts: -------------------------------------------------------------------------------- 1 | export declare const API_DOMAIN = "https://api.figma.com"; 2 | export declare const API_VER = "v1"; 3 | export declare const API_VER_WEBHOOKS = "v2"; 4 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.API_VER_WEBHOOKS = exports.API_VER = exports.API_DOMAIN = void 0; 4 | exports.API_DOMAIN = 'https://api.figma.com'; 5 | exports.API_VER = 'v1'; 6 | exports.API_VER_WEBHOOKS = 'v2'; 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | roots: ['/src', '/tests'], 5 | testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'], 6 | transform: { 7 | '^.+\\.ts$': 'ts-jest', 8 | }, 9 | collectCoverageFrom: [ 10 | 'src/**/*.ts', 11 | '!src/**/*.d.ts', 12 | ], 13 | coverageDirectory: 'coverage', 14 | coverageReporters: ['text', 'lcov', 'html'], 15 | }; -------------------------------------------------------------------------------- /lib/utils.d.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse, Method as AxiosMethod, AxiosRequestConfig } from 'axios'; 2 | export declare function toQueryParams(x: any): string; 3 | export type Disposer = () => void; 4 | export declare class ApiError extends Error { 5 | response: AxiosResponse; 6 | constructor(response: AxiosResponse, message?: string); 7 | } 8 | export type ApiRequestMethod = (url: string, opts?: { 9 | method: AxiosMethod; 10 | data: AxiosRequestConfig["data"]; 11 | }) => Promise; 12 | -------------------------------------------------------------------------------- /tests/config.test.ts: -------------------------------------------------------------------------------- 1 | import { API_DOMAIN, API_VER, API_VER_WEBHOOKS } from '../src/config'; 2 | 3 | describe('config', () => { 4 | test('API_DOMAIN should be the correct Figma API domain', () => { 5 | expect(API_DOMAIN).toBe('https://api.figma.com'); 6 | }); 7 | 8 | test('API_VER should be v1', () => { 9 | expect(API_VER).toBe('v1'); 10 | }); 11 | 12 | test('API_VER_WEBHOOKS should be v2', () => { 13 | expect(API_VER_WEBHOOKS).toBe('v2'); 14 | }); 15 | 16 | test('all exports should be strings', () => { 17 | expect(typeof API_DOMAIN).toBe('string'); 18 | expect(typeof API_VER).toBe('string'); 19 | expect(typeof API_VER_WEBHOOKS).toBe('string'); 20 | }); 21 | }); -------------------------------------------------------------------------------- /tests/index.test.ts: -------------------------------------------------------------------------------- 1 | import * as FigmaAPI from '../src/index'; 2 | import { API_DOMAIN, API_VER, API_VER_WEBHOOKS } from '../src/config'; 3 | import { Api } from '../src/api-class'; 4 | 5 | describe('index exports', () => { 6 | test('should export config constants', () => { 7 | expect(FigmaAPI.API_DOMAIN).toBe(API_DOMAIN); 8 | expect(FigmaAPI.API_VER).toBe(API_VER); 9 | expect(FigmaAPI.API_VER_WEBHOOKS).toBe(API_VER_WEBHOOKS); 10 | }); 11 | 12 | test('should export Api class', () => { 13 | expect(FigmaAPI.Api).toBe(Api); 14 | expect(typeof FigmaAPI.Api).toBe('function'); 15 | }); 16 | 17 | test('should export oAuthLink and oAuthToken functions', () => { 18 | expect(typeof FigmaAPI.oAuthLink).toBe('function'); 19 | expect(typeof FigmaAPI.oAuthToken).toBe('function'); 20 | }); 21 | }); -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { AxiosError, Method as AxiosMethod, AxiosRequestConfig } from 'axios'; 2 | 3 | export function toQueryParams(x: any): string { 4 | if (!x) return ''; 5 | return Object.entries(x).map(([ k, v ]) => ( 6 | k && v && `${k}=${encodeURIComponent(v as any)}` 7 | )).filter(Boolean).join('&') 8 | } 9 | 10 | export type Disposer = () => void; 11 | 12 | export class ApiError extends Error { 13 | constructor( 14 | public error: AxiosError, 15 | ) { 16 | super(error.message); 17 | this.name = 'ApiError'; 18 | // Maintains proper stack trace for where our error was thrown (only available on V8) 19 | if (Error.captureStackTrace) { 20 | Error.captureStackTrace(this, ApiError); 21 | } 22 | } 23 | } 24 | 25 | export type ApiRequestMethod = (url: string, opts?: { method: AxiosMethod, data: AxiosRequestConfig["data"] }) => Promise; 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Morglod 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "figma-api", 3 | "version": "2.1.0-beta", 4 | "description": "Thin typed wrapper around the Figma REST API", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "scripts": { 8 | "build": "npm run build:node && npm run build:browser && npm run build:browser:min", 9 | "build:node": "esbuild src/index.ts --bundle --platform=node --outfile=lib/index.js", 10 | "build:browser": "esbuild src/index.ts --bundle --format=iife --global-name=Figma --outfile=lib/figma-api.js", 11 | "build:browser:min": "esbuild src/index.ts --bundle --format=iife --global-name=Figma --minify --outfile=lib/figma-api.min.js", 12 | "test": "jest", 13 | "test:watch": "jest --watch", 14 | "test:coverage": "jest --coverage" 15 | }, 16 | "keywords": [ 17 | "figma", 18 | "rest", 19 | "api", 20 | "typed" 21 | ], 22 | "author": "didoo", 23 | "license": "MIT", 24 | "dependencies": { 25 | "@figma/rest-api-spec": "^0.27.0", 26 | "axios": "^1.12.2" 27 | }, 28 | "devDependencies": { 29 | "@types/jest": "^30.0.0", 30 | "@types/node": "^22.14.1", 31 | "esbuild": "^0.25.10", 32 | "jest": "^30.2.0", 33 | "ts-jest": "^29.4.4", 34 | "ts-node": "^10.9.2", 35 | "typescript": "^5.8.3" 36 | }, 37 | "repository": { 38 | "type": "git", 39 | "url": "https://github.com/didoo/figma-api" 40 | } 41 | } -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __extends = (this && this.__extends) || (function () { 3 | var extendStatics = function (d, b) { 4 | extendStatics = Object.setPrototypeOf || 5 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 6 | function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; 7 | return extendStatics(d, b); 8 | }; 9 | return function (d, b) { 10 | if (typeof b !== "function" && b !== null) 11 | throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); 12 | extendStatics(d, b); 13 | function __() { this.constructor = d; } 14 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 15 | }; 16 | })(); 17 | Object.defineProperty(exports, "__esModule", { value: true }); 18 | exports.ApiError = void 0; 19 | exports.toQueryParams = toQueryParams; 20 | function toQueryParams(x) { 21 | if (!x) 22 | return ''; 23 | return Object.entries(x).map(function (_a) { 24 | var k = _a[0], v = _a[1]; 25 | return (k && v && "".concat(k, "=").concat(encodeURIComponent(v))); 26 | }).filter(Boolean).join('&'); 27 | } 28 | var ApiError = /** @class */ (function (_super) { 29 | __extends(ApiError, _super); 30 | function ApiError(response, message) { 31 | var _this = _super.call(this, message) || this; 32 | _this.response = response; 33 | _this.name = 'ApiError'; 34 | // Maintains proper stack trace for where our error was thrown (only available on V8) 35 | if (Error.captureStackTrace) { 36 | Error.captureStackTrace(_this, ApiError); 37 | } 38 | return _this; 39 | } 40 | return ApiError; 41 | }(Error)); 42 | exports.ApiError = ApiError; 43 | -------------------------------------------------------------------------------- /tests/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { toQueryParams, ApiError } from '../src/utils'; 2 | 3 | describe('utils', () => { 4 | describe('toQueryParams', () => { 5 | test('should return empty string for null/undefined/falsy values', () => { 6 | expect(toQueryParams(null)).toBe(''); 7 | expect(toQueryParams(undefined)).toBe(''); 8 | expect(toQueryParams(false)).toBe(''); 9 | expect(toQueryParams(0)).toBe(''); 10 | expect(toQueryParams('')).toBe(''); 11 | }); 12 | 13 | test('should convert object to query string', () => { 14 | const params = { key1: 'value1', key2: 'value2' }; 15 | const result = toQueryParams(params); 16 | expect(result).toBe('key1=value1&key2=value2'); 17 | }); 18 | 19 | test('should handle special characters by encoding them', () => { 20 | const params = { search: 'hello world', special: 'a+b=c' }; 21 | const result = toQueryParams(params); 22 | expect(result).toBe('search=hello%20world&special=a%2Bb%3Dc'); 23 | }); 24 | 25 | test('should filter out falsy values', () => { 26 | const params = { key1: 'value1', key2: '', key3: null, key4: 'value4', key5: undefined }; 27 | const result = toQueryParams(params); 28 | expect(result).toBe('key1=value1&key4=value4'); 29 | }); 30 | 31 | test('should handle numbers as values', () => { 32 | const params = { page: 1, limit: 50 }; 33 | const result = toQueryParams(params); 34 | expect(result).toBe('page=1&limit=50'); 35 | }); 36 | 37 | test('should handle boolean values', () => { 38 | const params = { enabled: true, disabled: false }; 39 | const result = toQueryParams(params); 40 | expect(result).toBe('enabled=true'); 41 | }); 42 | }); 43 | 44 | describe('ApiError', () => { 45 | test('should create ApiError instance', () => { 46 | const mockAxiosError = { 47 | message: 'Request failed with status code 404', 48 | name: 'AxiosError', 49 | response: { 50 | status: 404, 51 | statusText: 'Not Found', 52 | data: {}, 53 | headers: {}, 54 | config: {}, 55 | } 56 | } as any; 57 | 58 | const error = new ApiError(mockAxiosError); 59 | 60 | expect(error).toBeInstanceOf(Error); 61 | expect(error.name).toBe('ApiError'); 62 | expect(error.message).toBe('Request failed with status code 404'); 63 | expect(error.error).toBe(mockAxiosError); 64 | }); 65 | 66 | test('should create ApiError and maintain stack trace', () => { 67 | const mockAxiosError = { 68 | message: 'Network Error', 69 | name: 'AxiosError', 70 | } as any; 71 | 72 | const error = new ApiError(mockAxiosError); 73 | 74 | expect(error.name).toBe('ApiError'); 75 | expect(error.error).toBe(mockAxiosError); 76 | expect(error.stack).toBeDefined(); 77 | }); 78 | }); 79 | }); -------------------------------------------------------------------------------- /lib/api-class.d.ts: -------------------------------------------------------------------------------- 1 | import * as ApiEndpoints from './api-endpoints'; 2 | import { ApiRequestMethod } from './utils'; 3 | export declare class Api { 4 | personalAccessToken?: string; 5 | oAuthToken?: string; 6 | constructor(params: { 7 | personalAccessToken: string; 8 | } | { 9 | oAuthToken: string; 10 | }); 11 | appendHeaders: (headers: { 12 | [x: string]: string; 13 | }) => void; 14 | request: ApiRequestMethod; 15 | getFile: typeof ApiEndpoints.getFileApi; 16 | getFileNodes: typeof ApiEndpoints.getFileNodesApi; 17 | getImages: typeof ApiEndpoints.getImagesApi; 18 | getImageFills: typeof ApiEndpoints.getImageFillsApi; 19 | getComments: typeof ApiEndpoints.getCommentsApi; 20 | postComment: typeof ApiEndpoints.postCommentApi; 21 | deleteComment: typeof ApiEndpoints.deleteCommentApi; 22 | getCommentReactions: typeof ApiEndpoints.getCommentReactionsApi; 23 | postCommentReaction: typeof ApiEndpoints.postCommentReactionApi; 24 | deleteCommentReactions: typeof ApiEndpoints.deleteCommentReactionsApi; 25 | getUserMe: typeof ApiEndpoints.getUserMeApi; 26 | getFileVersions: typeof ApiEndpoints.getFileVersionsApi; 27 | getTeamProjects: typeof ApiEndpoints.getTeamProjectsApi; 28 | getProjectFiles: typeof ApiEndpoints.getProjectFilesApi; 29 | getTeamComponents: typeof ApiEndpoints.getTeamComponentsApi; 30 | getFileComponents: typeof ApiEndpoints.getFileComponentsApi; 31 | getComponent: typeof ApiEndpoints.getComponentApi; 32 | getTeamComponentSets: typeof ApiEndpoints.getTeamComponentSetsApi; 33 | getFileComponentSets: typeof ApiEndpoints.getFileComponentSetsApi; 34 | getComponentSet: typeof ApiEndpoints.getComponentSetApi; 35 | getTeamStyles: typeof ApiEndpoints.getTeamStylesApi; 36 | getFileStyles: typeof ApiEndpoints.getFileStylesApi; 37 | getStyle: typeof ApiEndpoints.getStyleApi; 38 | getWebhook: typeof ApiEndpoints.getWebhookApi; 39 | postWebhook: typeof ApiEndpoints.postWebhookApi; 40 | putWebhook: typeof ApiEndpoints.putWebhookApi; 41 | deleteWebhook: typeof ApiEndpoints.deleteWebhookApi; 42 | getTeamWebhooks: typeof ApiEndpoints.getTeamWebhooksApi; 43 | getWebhookRequests: typeof ApiEndpoints.getWebhookRequestsApi; 44 | getLocalVariables: typeof ApiEndpoints.getLocalVariablesApi; 45 | getPublishedVariables: typeof ApiEndpoints.getPublishedVariablesApi; 46 | postVariables: typeof ApiEndpoints.postVariablesApi; 47 | getDevResources: typeof ApiEndpoints.getDevResourcesApi; 48 | postDevResources: typeof ApiEndpoints.postDevResourcesApi; 49 | putDevResources: typeof ApiEndpoints.putDevResourcesApi; 50 | deleteDevResources: typeof ApiEndpoints.deleteDevResourcesApi; 51 | getLibraryAnalyticsComponentActions: typeof ApiEndpoints.getLibraryAnalyticsComponentActionsApi; 52 | getLibraryAnalyticsComponentUsages: typeof ApiEndpoints.getLibraryAnalyticsComponentUsagesApi; 53 | getLibraryAnalyticsStyleActions: typeof ApiEndpoints.getLibraryAnalyticsStyleActionsApi; 54 | getLibraryAnalyticsStyleUsages: typeof ApiEndpoints.getLibraryAnalyticsStyleUsagesApi; 55 | getLibraryAnalyticsVariableActions: typeof ApiEndpoints.getLibraryAnalyticsVariableActionsApi; 56 | getLibraryAnalyticsVariableUsages: typeof ApiEndpoints.getLibraryAnalyticsVariableUsagesApi; 57 | } 58 | export declare function oAuthLink(client_id: string, redirect_uri: string, scope: 'file_read', state: string, response_type: 'code'): string; 59 | type OAuthTokenResponseData = { 60 | user_id: string; 61 | access_token: string; 62 | refresh_token: string; 63 | expires_in: number; 64 | }; 65 | export declare function oAuthToken(client_id: string, client_secret: string, redirect_uri: string, code: string, grant_type: 'authorization_code'): Promise; 66 | export {}; 67 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [v2.1.0-beta] - 2025-10-08 9 | 10 | ### Added 11 | - Comprehensive Jest testing suite for TypeScript source files 12 | - Copilot instructions for repository development guidance 13 | - Enhanced error handling with custom `ApiError` class 14 | 15 | ### Changed 16 | - Migrated build system from browserify to esbuild for modern bundling 17 | - Upgraded Axios from v0.27.2 to v1.12.2 18 | - Split build commands into meaningful sub-commands (`build:node`, `build:browser`, `build:browser:min`) 19 | - Updated TypeScript configuration to explicitly include `src` folder 20 | 21 | ### Fixed 22 | - Package.json dependencies structure - moved `axios` and `@figma/rest-api-spec` to `dependencies` 23 | - Fixed failing tests to work with enhanced ApiError implementation 24 | 25 | ## [v2.0.2-beta] - 2025-04-16 26 | 27 | ### Changed 28 | - Dependencies maintenance and security updates 29 | - Bumped `@figma/rest-api-spec` to v0.27.0 30 | - Bumped `@types/node` to v22.14.1 31 | - Bumped `typescript` to v5.8.3 32 | - Bumped `uglifyify` to v5.0.2 33 | 34 | ### Fixed 35 | - Fixed wrong path for `getPublishedVariablesApi` endpoint 36 | - TypeScript compilation issues resolved 37 | 38 | ### Security 39 | - Updated multiple dependency versions to address security vulnerabilities 40 | - Bumped elliptic from 6.6.0 to 6.6.1 41 | - Updated sha.js, pbkdf2, cipher-base, and form-data 42 | 43 | ## [v2.0.1-beta] - 2024-11-06 44 | 45 | ### Added 46 | - Updated exported endpoints in main library 47 | - Enhanced README documentation 48 | 49 | ### Changed 50 | - Updated comment documentation 51 | - Renamed readme.md to README.md for consistency 52 | 53 | ## [v2.0.0-beta] - 2024-11-05 54 | 55 | ### Added 56 | - **BREAKING CHANGE**: Complete refactoring to align with official Figma REST API specifications 57 | - Integration with `@figma/rest-api-spec` for type safety and API alignment 58 | - All missing endpoints from the official Figma API added 59 | - OAuth authentication improvements following new Figma specifications 60 | - Analytics endpoints support 61 | - Variables endpoints support 62 | - Dev Resources endpoints support 63 | - Enhanced webhooks support (v2 API) 64 | 65 | ### Changed 66 | - **BREAKING CHANGE**: All endpoint methods now use object parameters (`pathParams`, `queryParams`, `requestBody`) 67 | - **BREAKING CHANGE**: Method signatures completely restructured to match official API 68 | - Library now acts as a thin wrapper around the official Figma REST API 69 | - Package metadata updated to reflect v2.0 changes 70 | - Types now sourced directly from `@figma/rest-api-spec` 71 | 72 | ### Security 73 | - Updated OAuth token exchange method according to new Figma specifications 74 | 75 | --- 76 | 77 | ## Migration Guide: v1.x to v2.x 78 | 79 | Version 2.0 represents a complete rewrite of the library to align with the official Figma REST API specifications. Here are the key breaking changes: 80 | 81 | ### Method Signatures 82 | **Before (v1.x):** 83 | ```javascript 84 | api.getFile(fileKey, { version, ids, depth, geometry, plugin_data, branch_data }) 85 | ``` 86 | 87 | **After (v2.x):** 88 | ```javascript 89 | api.getFile( 90 | { file_key: fileKey }, // pathParams 91 | { version, ids, depth, geometry, plugin_data, branch_data } // queryParams 92 | ) 93 | ``` 94 | 95 | ### Authentication 96 | OAuth authentication has been updated to follow the new Figma specifications. The `oAuthToken` method signature has changed. 97 | 98 | ### Benefits of v2.x 99 | - Full type safety with official Figma API types 100 | - Complete API coverage with all endpoints 101 | - Future-proof alignment with Figma's specifications 102 | - Better error handling and debugging 103 | 104 | For detailed migration instructions, please refer to the [README.md](README.md) file. 105 | 106 | --- 107 | 108 | ## [v1.12.0] - 2024-11-05 109 | 110 | ### Added 111 | - Missing properties in API response types 112 | - Section type support 113 | - New types from Figma REST API documentation 114 | - Component sets support in API responses 115 | 116 | ### Changed 117 | - Updated dependencies including axios to v0.28.0 118 | - Made `getImageApi` scale parameter optional 119 | 120 | ### Fixed 121 | - Missing fields in `GetFileResult` type 122 | - Scale and format parameters now properly optional in image API 123 | 124 | ### Security 125 | - Updated browserify-sign from 4.0.4 to 4.2.3 126 | - Updated elliptic from 6.5.4 to 6.6.0 127 | - Updated follow-redirects from 1.14.8 to 1.15.9 128 | - Updated minimatch from 3.0.4 to 3.1.2 129 | 130 | ## [v1.11.0] - 2022-10-29 131 | 132 | ### Added 133 | - Autolayout v4 properties support 134 | - Improved `getFileNodesApi` response type 135 | - Enhanced type definitions for various API responses 136 | 137 | ### Fixed 138 | - Minor type issues in API definitions 139 | - Updated `file.components` type definition 140 | 141 | ### Security 142 | - Bumped shell-quote from 1.6.1 to 1.7.3 143 | - Bumped minimist and mkdirp dependencies for security 144 | 145 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ "src/**/*" ], 3 | "exclude": [ "./playground", "./node_modules", "./lib" ], 4 | "compilerOptions": { 5 | /* Basic Options */ 6 | "target": "es2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 7 | "module": "esnext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 8 | "lib": [ "es2020", "dom" ], /* Specify library files to be included in the compilation. */ 9 | // "allowJs": true, /* Allow javascript files to be compiled. */ 10 | // "checkJs": true, /* Report errors in .js files. */ 11 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 12 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 13 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 14 | // "sourceMap": false, /* Generates corresponding '.map' file. */ 15 | // "outFile": "./", /* Concatenate and emit output to single file. */ 16 | "outDir": "./lib", /* Redirect output structure to the directory. */ 17 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 18 | // "composite": true, /* Enable project compilation */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 31 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 32 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 33 | 34 | /* Additional Checks */ 35 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 36 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 37 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 38 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 39 | 40 | /* Module Resolution Options */ 41 | "moduleResolution": "bundler", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 42 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 43 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 44 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 45 | // "typeRoots": [], /* List of folders to include type definitions from. */ 46 | // "types": [], /* Type declaration files to be included in compilation. */ 47 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 48 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 49 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 50 | 51 | /* Source Map Options */ 52 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 53 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 54 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 55 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 56 | 57 | /* Experimental Options */ 58 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 59 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 60 | } 61 | } -------------------------------------------------------------------------------- /src/api-class.ts: -------------------------------------------------------------------------------- 1 | import * as ApiEndpoints from './api-endpoints'; 2 | import { ApiError, ApiRequestMethod, toQueryParams } from './utils'; 3 | 4 | import axios, { AxiosError, AxiosRequestConfig, Method as AxiosMethod } from 'axios'; 5 | 6 | export class Api { 7 | personalAccessToken?: string; 8 | oAuthToken?: string; 9 | 10 | constructor(params: { 11 | personalAccessToken: string, 12 | } | { 13 | oAuthToken: string, 14 | }) { 15 | if ('personalAccessToken' in params) { 16 | this.personalAccessToken = params.personalAccessToken; 17 | } 18 | if ('oAuthToken' in params) { 19 | this.oAuthToken = params.oAuthToken; 20 | } 21 | } 22 | 23 | appendHeaders = (headers: { [x: string]: string }) => { 24 | if (this.personalAccessToken) headers['X-Figma-Token'] = this.personalAccessToken; 25 | if (this.oAuthToken) headers['Authorization'] = `Bearer ${this.oAuthToken}`; 26 | } 27 | 28 | request: ApiRequestMethod = (url: string, opts?: { method: AxiosMethod, data: AxiosRequestConfig["data"] }) => { 29 | const headers = {}; 30 | this.appendHeaders(headers); 31 | 32 | const axiosParams: AxiosRequestConfig = { 33 | url, 34 | ...opts, 35 | headers, 36 | }; 37 | 38 | return axios(axiosParams) 39 | .then(response => response.data) 40 | .catch((error: AxiosError) => { 41 | throw new ApiError(error); 42 | }); 43 | }; 44 | 45 | getFile = ApiEndpoints.getFileApi; 46 | getFileNodes = ApiEndpoints.getFileNodesApi; 47 | getImages = ApiEndpoints.getImagesApi; 48 | getImageFills = ApiEndpoints.getImageFillsApi; 49 | getComments = ApiEndpoints.getCommentsApi; 50 | postComment = ApiEndpoints.postCommentApi; 51 | deleteComment = ApiEndpoints.deleteCommentApi; 52 | getCommentReactions = ApiEndpoints.getCommentReactionsApi; 53 | postCommentReaction = ApiEndpoints.postCommentReactionApi; 54 | deleteCommentReactions = ApiEndpoints.deleteCommentReactionsApi; 55 | getUserMe = ApiEndpoints.getUserMeApi; 56 | getFileVersions = ApiEndpoints.getFileVersionsApi; 57 | getTeamProjects = ApiEndpoints.getTeamProjectsApi; 58 | getProjectFiles = ApiEndpoints.getProjectFilesApi; 59 | getTeamComponents = ApiEndpoints.getTeamComponentsApi; 60 | getFileComponents = ApiEndpoints.getFileComponentsApi; 61 | getComponent = ApiEndpoints.getComponentApi; 62 | getTeamComponentSets = ApiEndpoints.getTeamComponentSetsApi; 63 | getFileComponentSets = ApiEndpoints.getFileComponentSetsApi; 64 | getComponentSet = ApiEndpoints.getComponentSetApi; 65 | getTeamStyles = ApiEndpoints.getTeamStylesApi; 66 | getFileStyles = ApiEndpoints.getFileStylesApi; 67 | getStyle = ApiEndpoints.getStyleApi; 68 | getWebhook = ApiEndpoints.getWebhookApi; 69 | postWebhook = ApiEndpoints.postWebhookApi; 70 | putWebhook = ApiEndpoints.putWebhookApi; 71 | deleteWebhook = ApiEndpoints.deleteWebhookApi; 72 | getTeamWebhooks = ApiEndpoints.getTeamWebhooksApi; 73 | getWebhookRequests = ApiEndpoints.getWebhookRequestsApi; 74 | getLocalVariables = ApiEndpoints.getLocalVariablesApi; 75 | getPublishedVariables = ApiEndpoints.getPublishedVariablesApi; 76 | postVariables = ApiEndpoints.postVariablesApi; 77 | getDevResources = ApiEndpoints.getDevResourcesApi; 78 | postDevResources = ApiEndpoints.postDevResourcesApi; 79 | putDevResources = ApiEndpoints.putDevResourcesApi; 80 | deleteDevResources = ApiEndpoints.deleteDevResourcesApi; 81 | getLibraryAnalyticsComponentActions = ApiEndpoints.getLibraryAnalyticsComponentActionsApi; 82 | getLibraryAnalyticsComponentUsages = ApiEndpoints.getLibraryAnalyticsComponentUsagesApi; 83 | getLibraryAnalyticsStyleActions = ApiEndpoints.getLibraryAnalyticsStyleActionsApi; 84 | getLibraryAnalyticsStyleUsages = ApiEndpoints.getLibraryAnalyticsStyleUsagesApi; 85 | getLibraryAnalyticsVariableActions = ApiEndpoints.getLibraryAnalyticsVariableActionsApi; 86 | getLibraryAnalyticsVariableUsages = ApiEndpoints.getLibraryAnalyticsVariableUsagesApi; 87 | } 88 | 89 | // see: https://www.figma.com/developers/api#auth-oauth2 90 | export function oAuthLink( 91 | client_id: string, 92 | redirect_uri: string, 93 | scope: 'file_read', 94 | state: string, 95 | response_type: 'code', 96 | ) { 97 | const queryParams = toQueryParams({ 98 | client_id, 99 | redirect_uri, 100 | scope, 101 | state, 102 | response_type, 103 | }); 104 | return `https://www.figma.com/oauth?${queryParams}`; 105 | } 106 | 107 | type OAuthTokenResponseData = { 108 | user_id: string, 109 | access_token: string, 110 | refresh_token: string, 111 | expires_in: number, 112 | }; 113 | 114 | export async function oAuthToken( 115 | client_id: string, 116 | client_secret: string, 117 | redirect_uri: string, 118 | code: string, 119 | grant_type: 'authorization_code', 120 | ): Promise { 121 | // see: https://www.figma.com/developers/api#update-oauth-credentials-handling 122 | const headers = { 123 | 'Authorization': `Basic ${Buffer.from(`${client_id}:${client_secret}`).toString('base64')}`, 124 | }; 125 | const queryParams = toQueryParams({ 126 | redirect_uri, 127 | code, 128 | grant_type, 129 | }); 130 | const url = `https://api.figma.com/v1/oauth/token?${queryParams}`; 131 | const res = await axios.post(url, null, { headers }); 132 | if (res.status !== 200) throw new ApiError(res as any); 133 | return res.data; 134 | } 135 | -------------------------------------------------------------------------------- /.github/copilot-instructions.md: -------------------------------------------------------------------------------- 1 | # Copilot Instructions for figma-api 2 | 3 | ## Project Overview 4 | 5 | This is a TypeScript library that provides a thin, fully-typed wrapper around the [Figma REST API](https://www.figma.com/developers/api). The library supports both Node.js and browser environments. 6 | 7 | ## Key Architecture 8 | 9 | - **Source**: TypeScript files in `src/` directory 10 | - **Output**: Compiled JavaScript in `lib/` directory 11 | - **Types**: Uses official `@figma/rest-api-spec` for complete type safety 12 | - **HTTP Client**: Axios v1.12.2 for making API requests 13 | - **Build**: esbuild for fast compilation with multiple targets (Node.js, browser, minified) 14 | - **Testing**: Jest with TypeScript support and comprehensive test coverage 15 | 16 | ## Core Files 17 | 18 | - `src/api-class.ts` - Main API class with authentication and method bindings 19 | - `src/api-endpoints.ts` - Individual endpoint implementations following Figma API structure 20 | - `src/config.ts` - API domain and version constants 21 | - `src/utils.ts` - Utility functions for query parameters and requests 22 | - `src/index.ts` - Main entry point, exports public API 23 | - `tests/` - Jest test suite for all source files 24 | - `jest.config.js` - Jest configuration for TypeScript testing 25 | 26 | ## Development Guidelines 27 | 28 | ### Code Style 29 | - Use TypeScript strict mode (already configured) 30 | - Follow existing naming conventions (camelCase for methods, PascalCase for types) 31 | - Import types from `@figma/rest-api-spec` - do NOT create custom types for API responses 32 | - Use arrow functions for endpoint method bindings in `Api` class 33 | - Keep endpoint implementations pure functions that return `this.request()` calls 34 | 35 | ### API Endpoint Pattern 36 | ```typescript 37 | export function getExampleApi( 38 | this: ApiClass, 39 | pathParams: FigmaRestAPI.GetExamplePathParams, 40 | queryParams?: FigmaRestAPI.GetExampleQueryParams 41 | ): Promise { 42 | const encodedQueryParams = toQueryParams(queryParams); 43 | return this.request(`${API_DOMAIN}/${API_VER}/endpoint/${pathParams.id}?${encodedQueryParams}`); 44 | } 45 | ``` 46 | 47 | ### Adding New Endpoints 48 | 1. Add the endpoint function to `src/api-endpoints.ts` following the pattern above 49 | 2. Export the function at the top of the file 50 | 3. Add method binding in `src/api-class.ts` using arrow function syntax 51 | 4. Group by API category with comments (Files, Comments, Users, etc.) 52 | 5. Include the official Figma API documentation link in comments 53 | 6. Write comprehensive tests in `tests/api-endpoints.test.ts` for the new endpoint 54 | 7. Ensure the new endpoint follows the existing error handling patterns 55 | 56 | ### Authentication 57 | - Support both Personal Access Tokens and OAuth tokens 58 | - Use the existing authentication helper methods in `Api` class 59 | - Headers are automatically populated by `appendHeaders()` method 60 | 61 | ### Building & Testing 62 | - Run `npm run build` to compile for all targets (Node.js, browser, minified) 63 | - `npm run build:node` - Build for Node.js environment using esbuild 64 | - `npm run build:browser` - Build for browser as IIFE with global `Figma` object 65 | - `npm run build:browser:min` - Build minified browser version 66 | - Run `npm test` to execute the Jest test suite 67 | - `npm run test:watch` - Run tests in watch mode for development 68 | - `npm run test:coverage` - Generate test coverage reports 69 | - All builds use esbuild for fast compilation and bundling 70 | - Tests are written in TypeScript and located in the `tests/` directory 71 | 72 | ### Development Workflow 73 | - Use TypeScript strict mode for all development 74 | - Run `npm test` during development to ensure changes don't break existing functionality 75 | - Use `npm run test:watch` for real-time testing during development 76 | - Run builds frequently to catch compilation issues early: `npm run build` 77 | - Use the existing patterns consistently - don't create new patterns 78 | - When in doubt, follow the Figma REST API documentation exactly 79 | - Check `lib/` output after builds to ensure proper compilation 80 | - Write tests for new functionality in the `tests/` directory following existing patterns 81 | 82 | ### Tool Configuration 83 | - **esbuild** handles all compilation and bundling (replaced TypeScript + Browserify) 84 | - **Jest** configuration in `jest.config.js` with ts-jest preset for TypeScript testing 85 | - TypeScript config in `tsconfig.json` targets ES5 with CommonJS modules 86 | - Test files should be placed in `tests/` directory with `.test.ts` extension 87 | - Coverage reports generated in `coverage/` directory 88 | - Use `@figma/rest-api-spec` types exclusively - never create custom API types 89 | 90 | ### Version Alignment 91 | - This library stays in sync with official Figma REST API specifications 92 | - Types come from `@figma/rest-api-spec` package - update that package for new API features 93 | - Endpoint URLs and parameters must match official Figma documentation exactly 94 | 95 | ## Important Notes 96 | 97 | - This is version 2.x which is a complete rewrite from 1.x for API alignment 98 | - All endpoint methods use object parameters (pathParams, queryParams, requestBody) 99 | - The library is designed to be a thin wrapper - avoid adding business logic 100 | - Browser and Node.js compatibility is maintained through build process 101 | - Keep the public API surface minimal and focused on REST API exposure 102 | 103 | ### Security & Dependencies 104 | - Check for vulnerabilities before adding new dependencies: `npm audit` 105 | - Keep dependencies minimal and focused on the library's core purpose 106 | - When adding dependencies, verify they're well-maintained and trusted 107 | - Address security vulnerabilities promptly but carefully to avoid breaking changes 108 | 109 | ### Error Handling 110 | - API errors should be handled consistently using the existing error patterns 111 | - Preserve error information from the Figma API in responses 112 | - Use TypeScript's strict typing to catch errors at compile time 113 | - Handle network errors gracefully in the request utility functions 114 | 115 | ### File Management 116 | - Exclude build artifacts from version control (already configured in `.gitignore`) 117 | - Keep the source in `src/` and compiled output in `lib/` 118 | - Place all tests in `tests/` directory with `.test.ts` extension 119 | - Don't commit `node_modules`, `playground`, `coverage/`, or temporary files 120 | - Use `.npmignore` to control what gets published to npm 121 | - Test coverage reports are generated in `coverage/` directory 122 | 123 | ## When Making Changes 124 | 125 | 1. Write or update tests first: `npm test` (Test-Driven Development approach) 126 | 2. Ensure TypeScript compilation succeeds: `npm run build` 127 | 3. Run the full test suite to ensure no regressions: `npm test` 128 | 4. Verify all build targets work correctly (Node.js, browser, minified) 129 | 5. Check test coverage is maintained: `npm run test:coverage` 130 | 6. Check that new endpoints follow the established patterns 131 | 7. Update documentation in README.md if adding major new functionality 132 | 8. Maintain backward compatibility within major version 133 | 9. Run `npm audit` to check for security vulnerabilities 134 | 10. Test both Node.js and browser environments when possible -------------------------------------------------------------------------------- /lib/api-endpoints.d.ts: -------------------------------------------------------------------------------- 1 | import { ApiRequestMethod } from "./utils"; 2 | type ApiClass = { 3 | request: ApiRequestMethod; 4 | }; 5 | import type * as FigmaRestAPI from '@figma/rest-api-spec'; 6 | export declare function getFileApi(this: ApiClass, pathParams: FigmaRestAPI.GetFilePathParams, queryParams?: FigmaRestAPI.GetFileQueryParams): Promise; 7 | export declare function getFileNodesApi(this: ApiClass, pathParams: FigmaRestAPI.GetFileNodesPathParams, queryParams?: FigmaRestAPI.GetFileNodesQueryParams): Promise; 8 | export declare function getImagesApi(this: ApiClass, pathParams: FigmaRestAPI.GetImagesPathParams, queryParams?: FigmaRestAPI.GetImagesQueryParams): Promise; 9 | export declare function getImageFillsApi(this: ApiClass, pathParams: FigmaRestAPI.GetImageFillsPathParams): Promise; 10 | export declare function getCommentsApi(this: ApiClass, pathParams: FigmaRestAPI.GetCommentsPathParams): Promise; 11 | export declare function postCommentApi(this: ApiClass, pathParams: FigmaRestAPI.PostCommentPathParams, requestBody?: FigmaRestAPI.PostCommentRequestBody): Promise; 12 | export declare function deleteCommentApi(this: ApiClass, pathParams: FigmaRestAPI.DeleteCommentPathParams): Promise; 13 | export declare function getCommentReactionsApi(this: ApiClass, pathParams: FigmaRestAPI.GetCommentReactionsPathParams, queryParams?: FigmaRestAPI.GetCommentReactionsQueryParams): Promise; 14 | export declare function postCommentReactionApi(this: ApiClass, pathParams: FigmaRestAPI.PostCommentReactionPathParams, requestBody?: FigmaRestAPI.PostCommentReactionRequestBody): Promise; 15 | export declare function deleteCommentReactionsApi(this: ApiClass, pathParams: FigmaRestAPI.DeleteCommentReactionPathParams): Promise; 16 | export declare function getUserMeApi(this: ApiClass): Promise; 17 | export declare function getFileVersionsApi(this: ApiClass, pathParams: FigmaRestAPI.GetFileVersionsPathParams): Promise; 18 | export declare function getTeamProjectsApi(this: ApiClass, pathParams: FigmaRestAPI.GetTeamProjectsPathParams): Promise; 19 | export declare function getProjectFilesApi(this: ApiClass, pathParams: FigmaRestAPI.GetProjectFilesPathParams, queryParams?: FigmaRestAPI.GetProjectFilesQueryParams): Promise; 20 | export declare function getTeamComponentsApi(this: ApiClass, pathParams: FigmaRestAPI.GetTeamComponentsPathParams, queryParams?: FigmaRestAPI.GetTeamComponentsQueryParams): Promise; 21 | export declare function getFileComponentsApi(this: ApiClass, pathParams: FigmaRestAPI.GetFileComponentsPathParams): Promise; 22 | export declare function getComponentApi(this: ApiClass, pathParams: FigmaRestAPI.GetComponentPathParams): Promise; 23 | export declare function getTeamComponentSetsApi(this: ApiClass, pathParams: FigmaRestAPI.GetTeamComponentSetsPathParams, queryParams?: FigmaRestAPI.GetTeamComponentSetsQueryParams): Promise; 24 | export declare function getFileComponentSetsApi(this: ApiClass, pathParams: FigmaRestAPI.GetFileComponentSetsPathParams): Promise; 25 | export declare function getComponentSetApi(this: ApiClass, pathParams: FigmaRestAPI.GetComponentSetPathParams): Promise; 26 | export declare function getTeamStylesApi(this: ApiClass, pathParams: FigmaRestAPI.GetTeamStylesPathParams, queryParams?: FigmaRestAPI.GetTeamStylesQueryParams): Promise; 27 | export declare function getFileStylesApi(this: ApiClass, pathParams: FigmaRestAPI.GetFileStylesPathParams): Promise; 28 | export declare function getStyleApi(this: ApiClass, pathParams: FigmaRestAPI.GetStylePathParams): Promise; 29 | export declare function getWebhookApi(this: ApiClass, pathParams: FigmaRestAPI.GetWebhookPathParams): Promise; 30 | export declare function postWebhookApi(this: ApiClass, requestBody?: FigmaRestAPI.PostWebhookRequestBody): Promise; 31 | export declare function putWebhookApi(this: ApiClass, pathParams: FigmaRestAPI.PutWebhookPathParams, requestBody?: FigmaRestAPI.PutWebhookRequestBody): Promise; 32 | export declare function deleteWebhookApi(this: ApiClass, pathParams: FigmaRestAPI.DeleteWebhookPathParams): Promise; 33 | export declare function getTeamWebhooksApi(this: ApiClass, pathParams: FigmaRestAPI.GetTeamWebhooksPathParams): Promise; 34 | export declare function getWebhookRequestsApi(this: ApiClass, pathParams: FigmaRestAPI.GetWebhookRequestsPathParams): Promise; 35 | export declare function getLocalVariablesApi(this: ApiClass, pathParams: FigmaRestAPI.GetLocalVariablesPathParams): Promise; 36 | export declare function getPublishedVariablesApi(this: ApiClass, pathParams: FigmaRestAPI.GetPublishedVariablesPathParams): Promise; 37 | export declare function postVariablesApi(this: ApiClass, pathParams: FigmaRestAPI.PostVariablesPathParams, requestBody?: FigmaRestAPI.PostVariablesRequestBody): Promise; 38 | export declare function getDevResourcesApi(this: ApiClass, pathParams: FigmaRestAPI.GetDevResourcesPathParams, queryParams?: FigmaRestAPI.GetDevResourcesQueryParams): Promise; 39 | export declare function postDevResourcesApi(this: ApiClass, requestBody?: FigmaRestAPI.PostDevResourcesRequestBody): Promise; 40 | export declare function putDevResourcesApi(this: ApiClass, requestBody?: FigmaRestAPI.PutDevResourcesRequestBody): Promise; 41 | export declare function deleteDevResourcesApi(this: ApiClass, pathParams: FigmaRestAPI.DeleteDevResourcePathParams): Promise; 42 | export declare function getLibraryAnalyticsComponentActionsApi(this: ApiClass, pathParams: FigmaRestAPI.GetLibraryAnalyticsComponentActionsPathParams, queryParams?: FigmaRestAPI.GetLibraryAnalyticsComponentActionsQueryParams): Promise; 43 | export declare function getLibraryAnalyticsComponentUsagesApi(this: ApiClass, pathParams: FigmaRestAPI.GetLibraryAnalyticsComponentUsagesPathParams, queryParams?: FigmaRestAPI.GetLibraryAnalyticsComponentUsagesQueryParams): Promise; 44 | export declare function getLibraryAnalyticsStyleActionsApi(this: ApiClass, pathParams: FigmaRestAPI.GetLibraryAnalyticsStyleActionsPathParams, queryParams?: FigmaRestAPI.GetLibraryAnalyticsStyleActionsQueryParams): Promise; 45 | export declare function getLibraryAnalyticsStyleUsagesApi(this: ApiClass, pathParams: FigmaRestAPI.GetLibraryAnalyticsStyleUsagesPathParams, queryParams?: FigmaRestAPI.GetLibraryAnalyticsStyleUsagesQueryParams): Promise; 46 | export declare function getLibraryAnalyticsVariableActionsApi(this: ApiClass, pathParams: FigmaRestAPI.GetLibraryAnalyticsVariableActionsPathParams, queryParams?: FigmaRestAPI.GetLibraryAnalyticsVariableActionsQueryParams): Promise; 47 | export declare function getLibraryAnalyticsVariableUsagesApi(this: ApiClass, pathParams: FigmaRestAPI.GetLibraryAnalyticsVariableUsagesPathParams, queryParams?: FigmaRestAPI.GetLibraryAnalyticsVariableUsagesQueryParams): Promise; 48 | export {}; 49 | -------------------------------------------------------------------------------- /tests/api-endpoints.test.ts: -------------------------------------------------------------------------------- 1 | import * as apiEndpoints from '../src/api-endpoints'; 2 | import { API_DOMAIN, API_VER, API_VER_WEBHOOKS } from '../src/config'; 3 | 4 | describe('api-endpoints', () => { 5 | let mockApiClass: { request: jest.Mock }; 6 | 7 | beforeEach(() => { 8 | mockApiClass = { 9 | request: jest.fn(), 10 | }; 11 | }); 12 | 13 | describe('Files endpoints', () => { 14 | test('getFileApi should generate correct URL', () => { 15 | const pathParams = { file_key: 'test-file-key' }; 16 | const queryParams = { version: '123', ids: '1,2,3' }; 17 | 18 | apiEndpoints.getFileApi.call(mockApiClass, pathParams, queryParams); 19 | 20 | expect(mockApiClass.request).toHaveBeenCalledWith( 21 | `${API_DOMAIN}/${API_VER}/files/test-file-key?version=123&ids=1%2C2%2C3` 22 | ); 23 | }); 24 | 25 | test('getFileNodesApi should generate correct URL', () => { 26 | const pathParams = { file_key: 'test-file-key' }; 27 | const queryParams = { ids: 'node1,node2' }; 28 | 29 | apiEndpoints.getFileNodesApi.call(mockApiClass, pathParams, queryParams); 30 | 31 | expect(mockApiClass.request).toHaveBeenCalledWith( 32 | `${API_DOMAIN}/${API_VER}/files/test-file-key/nodes?ids=node1%2Cnode2` 33 | ); 34 | }); 35 | 36 | test('getImagesApi should generate correct URL', () => { 37 | const pathParams = { file_key: 'test-file-key' }; 38 | const queryParams = { ids: 'node1,node2', format: 'png' as const }; 39 | 40 | apiEndpoints.getImagesApi.call(mockApiClass, pathParams, queryParams); 41 | 42 | expect(mockApiClass.request).toHaveBeenCalledWith( 43 | `${API_DOMAIN}/${API_VER}/images/test-file-key?ids=node1%2Cnode2&format=png` 44 | ); 45 | }); 46 | 47 | test('getImageFillsApi should generate correct URL', () => { 48 | const pathParams = { file_key: 'test-file-key' }; 49 | 50 | apiEndpoints.getImageFillsApi.call(mockApiClass, pathParams); 51 | 52 | expect(mockApiClass.request).toHaveBeenCalledWith( 53 | `${API_DOMAIN}/${API_VER}/files/test-file-key/images` 54 | ); 55 | }); 56 | }); 57 | 58 | describe('Comments endpoints', () => { 59 | test('getCommentsApi should generate correct URL', () => { 60 | const pathParams = { file_key: 'test-file-key' }; 61 | 62 | apiEndpoints.getCommentsApi.call(mockApiClass, pathParams); 63 | 64 | expect(mockApiClass.request).toHaveBeenCalledWith( 65 | `${API_DOMAIN}/${API_VER}/files/test-file-key/comments` 66 | ); 67 | }); 68 | 69 | test('postCommentApi should generate correct URL and method', () => { 70 | const pathParams = { file_key: 'test-file-key' }; 71 | const requestBody = { message: 'Test comment' }; 72 | 73 | apiEndpoints.postCommentApi.call(mockApiClass, pathParams, requestBody); 74 | 75 | expect(mockApiClass.request).toHaveBeenCalledWith( 76 | `${API_DOMAIN}/${API_VER}/files/test-file-key/comments`, 77 | { 78 | method: 'POST', 79 | data: requestBody, 80 | } 81 | ); 82 | }); 83 | 84 | test('deleteCommentApi should generate correct URL and method', () => { 85 | const pathParams = { file_key: 'test-file-key', comment_id: 'comment123' }; 86 | 87 | apiEndpoints.deleteCommentApi.call(mockApiClass, pathParams); 88 | 89 | expect(mockApiClass.request).toHaveBeenCalledWith( 90 | `${API_DOMAIN}/${API_VER}/files/test-file-key/comments/comment123`, 91 | { 92 | method: 'DELETE', 93 | data: '', 94 | } 95 | ); 96 | }); 97 | 98 | test('getCommentReactionsApi should generate correct URL', () => { 99 | const pathParams = { file_key: 'test-file-key', comment_id: 'comment123' }; 100 | const queryParams = { cursor: 'abc123' }; 101 | 102 | apiEndpoints.getCommentReactionsApi.call(mockApiClass, pathParams, queryParams); 103 | 104 | expect(mockApiClass.request).toHaveBeenCalledWith( 105 | `${API_DOMAIN}/${API_VER}/files/test-file-key/comments/comment123/reactions?cursor=abc123` 106 | ); 107 | }); 108 | 109 | test('postCommentReactionApi should generate correct URL and method', () => { 110 | const pathParams = { file_key: 'test-file-key', comment_id: 'comment123' }; 111 | const requestBody = { emoji: '👍' }; 112 | 113 | apiEndpoints.postCommentReactionApi.call(mockApiClass, pathParams, requestBody); 114 | 115 | expect(mockApiClass.request).toHaveBeenCalledWith( 116 | `${API_DOMAIN}/${API_VER}/files/test-file-key/comments/comment123/reactions`, 117 | { 118 | method: 'POST', 119 | data: requestBody, 120 | } 121 | ); 122 | }); 123 | 124 | test('deleteCommentReactionsApi should generate correct URL and method', () => { 125 | const pathParams = { file_key: 'test-file-key', comment_id: 'comment123' }; 126 | 127 | apiEndpoints.deleteCommentReactionsApi.call(mockApiClass, pathParams); 128 | 129 | expect(mockApiClass.request).toHaveBeenCalledWith( 130 | `${API_DOMAIN}/${API_VER}/files/test-file-key/comments/comment123/reactions`, 131 | { 132 | method: 'DELETE', 133 | data: '', 134 | } 135 | ); 136 | }); 137 | }); 138 | 139 | describe('Users endpoints', () => { 140 | test('getUserMeApi should generate correct URL', () => { 141 | apiEndpoints.getUserMeApi.call(mockApiClass); 142 | 143 | expect(mockApiClass.request).toHaveBeenCalledWith( 144 | `${API_DOMAIN}/${API_VER}/me` 145 | ); 146 | }); 147 | }); 148 | 149 | describe('Version History endpoints', () => { 150 | test('getFileVersionsApi should generate correct URL', () => { 151 | const pathParams = { file_key: 'test-file-key' }; 152 | 153 | apiEndpoints.getFileVersionsApi.call(mockApiClass, pathParams); 154 | 155 | expect(mockApiClass.request).toHaveBeenCalledWith( 156 | `${API_DOMAIN}/${API_VER}/files/test-file-key/versions` 157 | ); 158 | }); 159 | }); 160 | 161 | describe('Projects endpoints', () => { 162 | test('getTeamProjectsApi should generate correct URL', () => { 163 | const pathParams = { team_id: 'team123' }; 164 | 165 | apiEndpoints.getTeamProjectsApi.call(mockApiClass, pathParams); 166 | 167 | expect(mockApiClass.request).toHaveBeenCalledWith( 168 | `${API_DOMAIN}/${API_VER}/teams/team123/projects` 169 | ); 170 | }); 171 | }); 172 | 173 | describe('Endpoint function existence', () => { 174 | test('should export all expected endpoint functions', () => { 175 | // Test that key endpoint functions are exported 176 | expect(typeof apiEndpoints.getFileApi).toBe('function'); 177 | expect(typeof apiEndpoints.getFileNodesApi).toBe('function'); 178 | expect(typeof apiEndpoints.getImagesApi).toBe('function'); 179 | expect(typeof apiEndpoints.getImageFillsApi).toBe('function'); 180 | expect(typeof apiEndpoints.getCommentsApi).toBe('function'); 181 | expect(typeof apiEndpoints.postCommentApi).toBe('function'); 182 | expect(typeof apiEndpoints.deleteCommentApi).toBe('function'); 183 | expect(typeof apiEndpoints.getUserMeApi).toBe('function'); 184 | expect(typeof apiEndpoints.getFileVersionsApi).toBe('function'); 185 | expect(typeof apiEndpoints.getTeamProjectsApi).toBe('function'); 186 | }); 187 | }); 188 | 189 | describe('URL generation with empty/undefined query params', () => { 190 | test('should handle undefined query params', () => { 191 | const pathParams = { file_key: 'test-file-key' }; 192 | 193 | apiEndpoints.getFileApi.call(mockApiClass, pathParams, undefined); 194 | 195 | expect(mockApiClass.request).toHaveBeenCalledWith( 196 | `${API_DOMAIN}/${API_VER}/files/test-file-key?` 197 | ); 198 | }); 199 | 200 | test('should handle empty query params object', () => { 201 | const pathParams = { file_key: 'test-file-key' }; 202 | 203 | apiEndpoints.getFileApi.call(mockApiClass, pathParams, {}); 204 | 205 | expect(mockApiClass.request).toHaveBeenCalledWith( 206 | `${API_DOMAIN}/${API_VER}/files/test-file-key?` 207 | ); 208 | }); 209 | }); 210 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM Version](https://badge.fury.io/js/figma-api.svg?style=flat)](https://www.npmjs.com/package/figma-api) 2 | 3 | > [!IMPORTANT] 4 | > **Version 2.0 Beta** - This version is a complete rewrite of the library, based on the [Figma REST API specifications](https://github.com/figma/rest-api-spec). Many endpoint methods have been renamed from version 1.x, and all the endpoint methods' arguments now match the [Figma REST API](https://www.figma.com/developers/api) documentation. If you were using the previous version, and intend to use the new one, **you will have to update your code accordingly**. The good news is that from now on they should always remain in sync, so no major disruptions in the future, with the benefit of a full alignment with the official Figma REST API documentation and specifications. 5 | 6 | # figma-api 7 | 8 | JavaScript client-side implementation of the [Figma REST API](https://www.figma.com/developers/api#intro). 9 | 10 | Thin layer on top of the official [Figma REST API specifications](https://github.com/figma/rest-api-spec), fully typed with TypeScript, uses Promises (via [Axios](https://github.com/axios/axios)) & ES6. 11 | 12 | Supports both browser & Node.js implementations. 13 | 14 | ## Install 15 | 16 | `npm i figma-api` 17 | 18 | or browser version: 19 | 20 | `https://raw.githubusercontent.com/didoo/figma-api/master/lib/figma-api.js` 21 | `https://raw.githubusercontent.com/didoo/figma-api/master/lib/figma-api.min.js` 22 | 23 | If you have CORS limitation, import the `figma-api[.min].js` file in your codebase via the npm package. 24 | 25 | ## Usage 26 | 27 | In a Node.js script: 28 | 29 | ```ts 30 | import * as Figma from 'figma-api'; 31 | 32 | export async function main() { 33 | const api = new Figma.Api({ 34 | personalAccessToken: 'my-token', 35 | }); 36 | 37 | const file = await api.getFile({ file_key: 'my-file-key'}); 38 | // ... access file data ... 39 | } 40 | ``` 41 | 42 | In a browser script: 43 | 44 | ```js 45 | const api = new Figma.Api({ personalAccessToken: 'my-personal-access-token' }); 46 | 47 | api.getFile({ file_key: 'my-file-key'}).then((file) => { 48 | // access file data 49 | }); 50 | ``` 51 | 52 | In this case, the `Figma` object is gloabl and all the API methods are associated with it. 53 | 54 | ## Api 55 | 56 | We have followed the same organisation as the official [Figma API documentation](https://www.figma.com/developers/api) to describe our API methods, so it's easier to find the exact endpoint call you are looking for. 57 | 58 | ### Authentication 59 | 60 | ```ts 61 | new Api ({ personalAccessToken, oAuthToken }) 62 | ``` 63 | 64 | Creates new Api object with specified `personalAccessToken` or `oAuthToken`. For details about how to get these tokens, [see the documentation](https://www.figma.com/developers/api#authentication) 65 | 66 | ```ts 67 | function oAuthLink( 68 | client_id: string, 69 | redirect_uri: string, 70 | scope: 'file_read', 71 | state: string, 72 | response_type: 'code', 73 | ): string; 74 | ``` 75 | 76 | Returns link for OAuth auth flow. Users should open this link, allow access and they will be redirected to `redirect_uri?code=`. Then they should use `oAuthToken` method to get an access token. 77 | 78 | ```ts 79 | function oAuthToken( 80 | client_id: string, 81 | client_secret: string, 82 | redirect_uri: string, 83 | code: string, 84 | grant_type: 'authorization_code', 85 | ): Promise<{ 86 | access_token: string, 87 | refresh_token: string, 88 | expires_in: number, 89 | }> 90 | ``` 91 | Returns the access token from oauth code (see `oAuthLink` method). 92 | 93 | Other helpers: 94 | 95 | - `Api.appendHeaders(headers)` - Populate headers with auth. 96 | - `Api.request(url, opts)` - Make request with auth headers. 97 | 98 | ### Endpoints 99 | 100 | All these endpoints methods receive objects like `pathParams`, `queryParams`, `requestBody`, as arguments, and return a Promise. For details about the shape of these objects refer to the official Figma REST API documentation (see links below). 101 | 102 | > [!IMPORTANT] 103 | > Version 2.x differs considerably from version 1.x in that the arguments of the API endpoint methods are _always_ contained in these objects, while before they were passed singularly as values directly to the function. 104 | 105 | #### Files 106 | 107 | See: https://www.figma.com/developers/api#files-endpoints 108 | 109 | - `Api.getFile(pathParams,queryParams)` 110 | - `Api.getFileNodes(pathParams,queryParams)` 111 | - `Api.getImages(pathParams,queryParams)` 112 | - `Api.getImageFills(pathParams)` 113 | 114 | #### Comments 115 | 116 | See: https://www.figma.com/developers/api#comments-endpoints 117 | 118 | - `Api.getComments(pathParams)` 119 | - `Api.postComment(pathParams,requestBody)` 120 | - `Api.deleteComment(pathParams)` 121 | - `Api.getCommentReactions(pathParams,queryParams)` 122 | - `Api.postCommentReaction(pathParams,requestBody)` 123 | - `Api.deleteCommentReactions(pathParams)` 124 | 125 | #### Users 126 | 127 | See: https://www.figma.com/developers/api#users-endpoints 128 | 129 | - `Api.getUserMe()` 130 | 131 | #### Version History (File Versions) 132 | 133 | See: https://www.figma.com/developers/api#version-history-endpoints 134 | 135 | - `Api.getFileVersions(pathParams)` 136 | 137 | #### Projects 138 | 139 | See: https://www.figma.com/developers/api#projects-endpoints 140 | 141 | - `Api.getTeamProjects(pathParams)` 142 | - `Api.getProjectFiles(pathParams,queryParams)` 143 | 144 | #### Components and Styles (Library Items) 145 | 146 | See: https://www.figma.com/developers/api#library-items-endpoints 147 | 148 | - `Api.getTeamComponents(pathParams,queryParams)` 149 | - `Api.getFileComponents(pathParams)` 150 | - `Api.getComponent(pathParams)` 151 | - `Api.getTeamComponentSets(pathParams,queryParams)` 152 | - `Api.getFileComponentSets(pathParams)` 153 | - `Api.getComponentSet(pathParams)` 154 | - `Api.getTeamStyles(pathParams,queryParams)` 155 | - `Api.getFileStyles(pathParams)` 156 | - `Api.getStyle(pathParams)` 157 | 158 | #### Webhooks 159 | 160 | See: https://www.figma.com/developers/api#webhooks_v2 161 | 162 | - `Api.getWebhook(pathParams)` 163 | - `Api.postWebhook(requestBody)` 164 | - `Api.putWebhook(pathParams,requestBody)` 165 | - `Api.deleteWebhook(pathParams)` 166 | - `Api.getTeamWebhooks(pathParams)` 167 | - `Api.getWebhookRequests(pathParams)` 168 | 169 | #### Activity Logs 170 | 171 | See: https://www.figma.com/developers/api#activity-logs-endpoints 172 | 173 | > [!TIP] 174 | > [TODO] Open to contributions if someone is needs to use these endpoints 175 | 176 | 177 | #### Payments 178 | 179 | See: https://www.figma.com/developers/api#payments-endpoints 180 | 181 | > [!TIP] 182 | > [TODO] Open to contributions if someone is needs to use these endpoints 183 | 184 | #### Variables 185 | 186 | > [!NOTE] 187 | > These APIs are available only to full members of Enterprise orgs. 188 | 189 | See: https://www.figma.com/developers/api#variables-endpoints 190 | 191 | - `Api.getLocalVariables(pathParams)` 192 | - `Api.getPublishedVariables(pathParams)` 193 | - `Api.postVariables(pathParams,requestBody)` 194 | 195 | #### Dev Resources 196 | 197 | See: https://www.figma.com/developers/api#dev-resources-endpoints 198 | 199 | - `Api.getDevResources(pathParams,queryParams)` 200 | - `Api.postDevResources(requestBody)` 201 | - `Api.putDevResources(requestBody)` 202 | - `Api.deleteDevResources(pathParams)` 203 | 204 | #### Analytics 205 | 206 | See: https://www.figma.com/developers/api#library-analytics-endpoints 207 | 208 | - `Api.getLibraryAnalyticsComponentActions(pathParams,queryParams)` 209 | - `Api.getLibraryAnalyticsComponentUsages(pathParams,queryParams)` 210 | - `Api.getLibraryAnalyticsStyleActions(pathParams,queryParams)` 211 | - `Api.getLibraryAnalyticsStyleUsages(pathParams,queryParams)` 212 | - `Api.getLibraryAnalyticsVariableActions(pathParams,queryParams)` 213 | - `Api.getLibraryAnalyticsVariableUsages(pathParams,queryParams)` 214 | 215 | ## Types 216 | 217 | The library is fully typed using the official [Figma REST API specifications](https://github.com/figma/rest-api-spec). You can see those types in the generated file here: https://github.com/figma/rest-api-spec/blob/main/dist/api_types.ts. 218 | 219 | Alternatively, you can refer to the official Figma REST API documentation (see links above). 220 | 221 | --- 222 | 223 | ## Development 224 | 225 | ``` 226 | git clone https://github.com/didoo/figma-api.git 227 | cd figma-api 228 | git checkout main 229 | npm install 230 | npm run build 231 | ``` 232 | 233 | ## Testing 234 | 235 | ``` 236 | npm run test 237 | ``` 238 | 239 | ## Release 240 | 241 | ``` 242 | npm version [ | major | minor | patch] 243 | #if not yet logged in 244 | npm login 245 | npm publish 246 | ``` 247 | 248 | (notice: tags are created automatically after a few minutes from the publishing) 249 | -------------------------------------------------------------------------------- /tests/api-class.test.ts: -------------------------------------------------------------------------------- 1 | import { Api, oAuthLink, oAuthToken } from '../src/api-class'; 2 | import { ApiError } from '../src/utils'; 3 | import axios, { AxiosResponse } from 'axios'; 4 | 5 | // Mock axios module 6 | jest.mock('axios'); 7 | const mockedAxios = axios as jest.MockedFunction; 8 | mockedAxios.post = jest.fn(); 9 | 10 | describe('api-class', () => { 11 | describe('Api class', () => { 12 | beforeEach(() => { 13 | jest.clearAllMocks(); 14 | }); 15 | 16 | describe('constructor', () => { 17 | test('should create instance with personal access token', () => { 18 | const api = new Api({ personalAccessToken: 'test-token' }); 19 | 20 | expect(api.personalAccessToken).toBe('test-token'); 21 | expect(api.oAuthToken).toBeUndefined(); 22 | }); 23 | 24 | test('should create instance with OAuth token', () => { 25 | const api = new Api({ oAuthToken: 'oauth-token' }); 26 | 27 | expect(api.oAuthToken).toBe('oauth-token'); 28 | expect(api.personalAccessToken).toBeUndefined(); 29 | }); 30 | }); 31 | 32 | describe('appendHeaders', () => { 33 | test('should append personal access token header', () => { 34 | const api = new Api({ personalAccessToken: 'test-token' }); 35 | const headers: { [x: string]: string } = {}; 36 | 37 | api.appendHeaders(headers); 38 | 39 | expect(headers['X-Figma-Token']).toBe('test-token'); 40 | expect(headers['Authorization']).toBeUndefined(); 41 | }); 42 | 43 | test('should append OAuth token header', () => { 44 | const api = new Api({ oAuthToken: 'oauth-token' }); 45 | const headers: { [x: string]: string } = {}; 46 | 47 | api.appendHeaders(headers); 48 | 49 | expect(headers['Authorization']).toBe('Bearer oauth-token'); 50 | expect(headers['X-Figma-Token']).toBeUndefined(); 51 | }); 52 | }); 53 | 54 | describe('request', () => { 55 | test('should make successful request with personal access token', async () => { 56 | const mockResponse: AxiosResponse = { 57 | status: 200, 58 | statusText: 'OK', 59 | data: { test: 'data' }, 60 | headers: {}, 61 | config: {} as any, 62 | }; 63 | mockedAxios.mockResolvedValueOnce(mockResponse); 64 | 65 | const api = new Api({ personalAccessToken: 'test-token' }); 66 | const result = await api.request('https://api.figma.com/v1/test'); 67 | 68 | expect(mockedAxios).toHaveBeenCalledWith({ 69 | url: 'https://api.figma.com/v1/test', 70 | headers: { 'X-Figma-Token': 'test-token' }, 71 | }); 72 | expect(result).toEqual({ test: 'data' }); 73 | }); 74 | 75 | test('should make successful request with OAuth token', async () => { 76 | const mockResponse: AxiosResponse = { 77 | status: 200, 78 | statusText: 'OK', 79 | data: { test: 'data' }, 80 | headers: {}, 81 | config: {} as any, 82 | }; 83 | mockedAxios.mockResolvedValueOnce(mockResponse); 84 | 85 | const api = new Api({ oAuthToken: 'oauth-token' }); 86 | const result = await api.request('https://api.figma.com/v1/test'); 87 | 88 | expect(mockedAxios).toHaveBeenCalledWith({ 89 | url: 'https://api.figma.com/v1/test', 90 | headers: { 'Authorization': 'Bearer oauth-token' }, 91 | }); 92 | expect(result).toEqual({ test: 'data' }); 93 | }); 94 | 95 | test('should make request with custom method and data', async () => { 96 | const mockResponse: AxiosResponse = { 97 | status: 201, 98 | statusText: 'Created', 99 | data: { created: true }, 100 | headers: {}, 101 | config: {} as any, 102 | }; 103 | mockedAxios.mockResolvedValueOnce(mockResponse); 104 | 105 | const api = new Api({ personalAccessToken: 'test-token' }); 106 | const requestData = { name: 'test' }; 107 | 108 | const result = await api.request('https://api.figma.com/v1/test', { 109 | method: 'POST', 110 | data: requestData, 111 | }); 112 | 113 | expect(mockedAxios).toHaveBeenCalledWith({ 114 | url: 'https://api.figma.com/v1/test', 115 | method: 'POST', 116 | data: requestData, 117 | headers: { 'X-Figma-Token': 'test-token' }, 118 | }); 119 | expect(result).toEqual({ created: true }); 120 | }); 121 | 122 | test('should throw ApiError for axios errors', async () => { 123 | const mockAxiosError = { 124 | message: 'Request failed with status code 404', 125 | name: 'AxiosError', 126 | response: { 127 | status: 404, 128 | statusText: 'Not Found', 129 | data: {}, 130 | headers: {}, 131 | config: {} as any, 132 | } 133 | }; 134 | mockedAxios.mockRejectedValueOnce(mockAxiosError); 135 | 136 | const api = new Api({ personalAccessToken: 'test-token' }); 137 | 138 | try { 139 | await api.request('https://api.figma.com/v1/test'); 140 | fail('Expected request to throw an error'); 141 | } catch (error) { 142 | expect(error).toBeInstanceOf(ApiError); 143 | expect((error as ApiError).error).toBe(mockAxiosError); 144 | } 145 | }); 146 | }); 147 | 148 | describe('endpoint methods', () => { 149 | test('should have all expected endpoint methods', () => { 150 | const api = new Api({ personalAccessToken: 'test-token' }); 151 | 152 | // Test a few key endpoint methods exist 153 | expect(typeof api.getFile).toBe('function'); 154 | expect(typeof api.getFileNodes).toBe('function'); 155 | expect(typeof api.getImages).toBe('function'); 156 | expect(typeof api.getUserMe).toBe('function'); 157 | expect(typeof api.getComments).toBe('function'); 158 | expect(typeof api.postComment).toBe('function'); 159 | }); 160 | }); 161 | }); 162 | 163 | describe('oAuthLink', () => { 164 | test('should generate correct OAuth link', () => { 165 | const link = oAuthLink( 166 | 'client123', 167 | 'https://example.com/callback', 168 | 'file_read', 169 | 'state123', 170 | 'code' 171 | ); 172 | 173 | expect(link).toBe( 174 | 'https://www.figma.com/oauth?client_id=client123&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback&scope=file_read&state=state123&response_type=code' 175 | ); 176 | }); 177 | }); 178 | 179 | describe('oAuthToken', () => { 180 | beforeEach(() => { 181 | jest.clearAllMocks(); 182 | }); 183 | 184 | test('should exchange code for OAuth token', async () => { 185 | const mockResponse: AxiosResponse = { 186 | status: 200, 187 | statusText: 'OK', 188 | data: { 189 | user_id: 'user123', 190 | access_token: 'token123', 191 | refresh_token: 'refresh123', 192 | expires_in: 3600, 193 | }, 194 | headers: {}, 195 | config: {} as any, 196 | }; 197 | (mockedAxios.post as jest.MockedFunction).mockResolvedValueOnce(mockResponse); 198 | 199 | const result = await oAuthToken( 200 | 'client123', 201 | 'secret456', 202 | 'https://example.com/callback', 203 | 'code789', 204 | 'authorization_code' 205 | ); 206 | 207 | expect(mockedAxios.post).toHaveBeenCalledWith( 208 | 'https://api.figma.com/v1/oauth/token?redirect_uri=https%3A%2F%2Fexample.com%2Fcallback&code=code789&grant_type=authorization_code', 209 | null, 210 | { 211 | headers: { 212 | 'Authorization': `Basic ${Buffer.from('client123:secret456').toString('base64')}`, 213 | }, 214 | } 215 | ); 216 | expect(result).toEqual(mockResponse.data); 217 | }); 218 | 219 | test('should throw ApiError for non-200 status', async () => { 220 | const mockResponse: AxiosResponse = { 221 | status: 400, 222 | statusText: 'Bad Request', 223 | data: {}, 224 | headers: {}, 225 | config: {} as any, 226 | }; 227 | (mockedAxios.post as jest.MockedFunction).mockResolvedValueOnce(mockResponse); 228 | 229 | try { 230 | await oAuthToken( 231 | 'client123', 232 | 'secret456', 233 | 'https://example.com/callback', 234 | 'invalid-code', 235 | 'authorization_code' 236 | ); 237 | fail('Expected oAuthToken to throw an error'); 238 | } catch (error) { 239 | expect(error).toBeInstanceOf(ApiError); 240 | } 241 | }); 242 | }); 243 | }); -------------------------------------------------------------------------------- /lib/api-class.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __assign = (this && this.__assign) || function () { 3 | __assign = Object.assign || function(t) { 4 | for (var s, i = 1, n = arguments.length; i < n; i++) { 5 | s = arguments[i]; 6 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 7 | t[p] = s[p]; 8 | } 9 | return t; 10 | }; 11 | return __assign.apply(this, arguments); 12 | }; 13 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 14 | if (k2 === undefined) k2 = k; 15 | var desc = Object.getOwnPropertyDescriptor(m, k); 16 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 17 | desc = { enumerable: true, get: function() { return m[k]; } }; 18 | } 19 | Object.defineProperty(o, k2, desc); 20 | }) : (function(o, m, k, k2) { 21 | if (k2 === undefined) k2 = k; 22 | o[k2] = m[k]; 23 | })); 24 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 25 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 26 | }) : function(o, v) { 27 | o["default"] = v; 28 | }); 29 | var __importStar = (this && this.__importStar) || (function () { 30 | var ownKeys = function(o) { 31 | ownKeys = Object.getOwnPropertyNames || function (o) { 32 | var ar = []; 33 | for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; 34 | return ar; 35 | }; 36 | return ownKeys(o); 37 | }; 38 | return function (mod) { 39 | if (mod && mod.__esModule) return mod; 40 | var result = {}; 41 | if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); 42 | __setModuleDefault(result, mod); 43 | return result; 44 | }; 45 | })(); 46 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 47 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 48 | return new (P || (P = Promise))(function (resolve, reject) { 49 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 50 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 51 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 52 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 53 | }); 54 | }; 55 | var __generator = (this && this.__generator) || function (thisArg, body) { 56 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); 57 | return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 58 | function verb(n) { return function (v) { return step([n, v]); }; } 59 | function step(op) { 60 | if (f) throw new TypeError("Generator is already executing."); 61 | while (g && (g = 0, op[0] && (_ = 0)), _) try { 62 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 63 | if (y = 0, t) op = [op[0] & 2, t.value]; 64 | switch (op[0]) { 65 | case 0: case 1: t = op; break; 66 | case 4: _.label++; return { value: op[1], done: false }; 67 | case 5: _.label++; y = op[1]; op = [0]; continue; 68 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 69 | default: 70 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 71 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 72 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 73 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 74 | if (t[2]) _.ops.pop(); 75 | _.trys.pop(); continue; 76 | } 77 | op = body.call(thisArg, _); 78 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 79 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 80 | } 81 | }; 82 | var __importDefault = (this && this.__importDefault) || function (mod) { 83 | return (mod && mod.__esModule) ? mod : { "default": mod }; 84 | }; 85 | Object.defineProperty(exports, "__esModule", { value: true }); 86 | exports.Api = void 0; 87 | exports.oAuthLink = oAuthLink; 88 | exports.oAuthToken = oAuthToken; 89 | var ApiEndpoints = __importStar(require("./api-endpoints")); 90 | var utils_1 = require("./utils"); 91 | var axios_1 = __importDefault(require("axios")); 92 | var Api = /** @class */ (function () { 93 | function Api(params) { 94 | var _this = this; 95 | this.appendHeaders = function (headers) { 96 | if (_this.personalAccessToken) 97 | headers['X-Figma-Token'] = _this.personalAccessToken; 98 | if (_this.oAuthToken) 99 | headers['Authorization'] = "Bearer ".concat(_this.oAuthToken); 100 | }; 101 | this.request = function (url, opts) { return __awaiter(_this, void 0, void 0, function () { 102 | var headers, axiosParams, res; 103 | return __generator(this, function (_a) { 104 | switch (_a.label) { 105 | case 0: 106 | headers = {}; 107 | this.appendHeaders(headers); 108 | axiosParams = __assign(__assign({ url: url }, opts), { headers: headers }); 109 | return [4 /*yield*/, (0, axios_1.default)(axiosParams)]; 110 | case 1: 111 | res = _a.sent(); 112 | if (Math.floor(res.status / 100) !== 2) 113 | throw res.statusText; 114 | return [2 /*return*/, res.data]; 115 | } 116 | }); 117 | }); }; 118 | this.getFile = ApiEndpoints.getFileApi; 119 | this.getFileNodes = ApiEndpoints.getFileNodesApi; 120 | this.getImages = ApiEndpoints.getImagesApi; 121 | this.getImageFills = ApiEndpoints.getImageFillsApi; 122 | this.getComments = ApiEndpoints.getCommentsApi; 123 | this.postComment = ApiEndpoints.postCommentApi; 124 | this.deleteComment = ApiEndpoints.deleteCommentApi; 125 | this.getCommentReactions = ApiEndpoints.getCommentReactionsApi; 126 | this.postCommentReaction = ApiEndpoints.postCommentReactionApi; 127 | this.deleteCommentReactions = ApiEndpoints.deleteCommentReactionsApi; 128 | this.getUserMe = ApiEndpoints.getUserMeApi; 129 | this.getFileVersions = ApiEndpoints.getFileVersionsApi; 130 | this.getTeamProjects = ApiEndpoints.getTeamProjectsApi; 131 | this.getProjectFiles = ApiEndpoints.getProjectFilesApi; 132 | this.getTeamComponents = ApiEndpoints.getTeamComponentsApi; 133 | this.getFileComponents = ApiEndpoints.getFileComponentsApi; 134 | this.getComponent = ApiEndpoints.getComponentApi; 135 | this.getTeamComponentSets = ApiEndpoints.getTeamComponentSetsApi; 136 | this.getFileComponentSets = ApiEndpoints.getFileComponentSetsApi; 137 | this.getComponentSet = ApiEndpoints.getComponentSetApi; 138 | this.getTeamStyles = ApiEndpoints.getTeamStylesApi; 139 | this.getFileStyles = ApiEndpoints.getFileStylesApi; 140 | this.getStyle = ApiEndpoints.getStyleApi; 141 | this.getWebhook = ApiEndpoints.getWebhookApi; 142 | this.postWebhook = ApiEndpoints.postWebhookApi; 143 | this.putWebhook = ApiEndpoints.putWebhookApi; 144 | this.deleteWebhook = ApiEndpoints.deleteWebhookApi; 145 | this.getTeamWebhooks = ApiEndpoints.getTeamWebhooksApi; 146 | this.getWebhookRequests = ApiEndpoints.getWebhookRequestsApi; 147 | this.getLocalVariables = ApiEndpoints.getLocalVariablesApi; 148 | this.getPublishedVariables = ApiEndpoints.getPublishedVariablesApi; 149 | this.postVariables = ApiEndpoints.postVariablesApi; 150 | this.getDevResources = ApiEndpoints.getDevResourcesApi; 151 | this.postDevResources = ApiEndpoints.postDevResourcesApi; 152 | this.putDevResources = ApiEndpoints.putDevResourcesApi; 153 | this.deleteDevResources = ApiEndpoints.deleteDevResourcesApi; 154 | this.getLibraryAnalyticsComponentActions = ApiEndpoints.getLibraryAnalyticsComponentActionsApi; 155 | this.getLibraryAnalyticsComponentUsages = ApiEndpoints.getLibraryAnalyticsComponentUsagesApi; 156 | this.getLibraryAnalyticsStyleActions = ApiEndpoints.getLibraryAnalyticsStyleActionsApi; 157 | this.getLibraryAnalyticsStyleUsages = ApiEndpoints.getLibraryAnalyticsStyleUsagesApi; 158 | this.getLibraryAnalyticsVariableActions = ApiEndpoints.getLibraryAnalyticsVariableActionsApi; 159 | this.getLibraryAnalyticsVariableUsages = ApiEndpoints.getLibraryAnalyticsVariableUsagesApi; 160 | if ('personalAccessToken' in params) { 161 | this.personalAccessToken = params.personalAccessToken; 162 | } 163 | if ('oAuthToken' in params) { 164 | this.oAuthToken = params.oAuthToken; 165 | } 166 | } 167 | return Api; 168 | }()); 169 | exports.Api = Api; 170 | // see: https://www.figma.com/developers/api#auth-oauth2 171 | function oAuthLink(client_id, redirect_uri, scope, state, response_type) { 172 | var queryParams = (0, utils_1.toQueryParams)({ 173 | client_id: client_id, 174 | redirect_uri: redirect_uri, 175 | scope: scope, 176 | state: state, 177 | response_type: response_type, 178 | }); 179 | return "https://www.figma.com/oauth?".concat(queryParams); 180 | } 181 | function oAuthToken(client_id, client_secret, redirect_uri, code, grant_type) { 182 | return __awaiter(this, void 0, void 0, function () { 183 | var headers, queryParams, url, res; 184 | return __generator(this, function (_a) { 185 | switch (_a.label) { 186 | case 0: 187 | headers = { 188 | 'Authorization': "Basic ".concat(Buffer.from("".concat(client_id, ":").concat(client_secret)).toString('base64')), 189 | }; 190 | queryParams = (0, utils_1.toQueryParams)({ 191 | redirect_uri: redirect_uri, 192 | code: code, 193 | grant_type: grant_type, 194 | }); 195 | url = "https://api.figma.com/v1/oauth/token?".concat(queryParams); 196 | return [4 /*yield*/, axios_1.default.post(url, null, { headers: headers })]; 197 | case 1: 198 | res = _a.sent(); 199 | if (res.status !== 200) 200 | throw res.statusText; 201 | return [2 /*return*/, res.data]; 202 | } 203 | }); 204 | }); 205 | } 206 | -------------------------------------------------------------------------------- /lib/api-endpoints.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.getFileApi = getFileApi; 4 | exports.getFileNodesApi = getFileNodesApi; 5 | exports.getImagesApi = getImagesApi; 6 | exports.getImageFillsApi = getImageFillsApi; 7 | exports.getCommentsApi = getCommentsApi; 8 | exports.postCommentApi = postCommentApi; 9 | exports.deleteCommentApi = deleteCommentApi; 10 | exports.getCommentReactionsApi = getCommentReactionsApi; 11 | exports.postCommentReactionApi = postCommentReactionApi; 12 | exports.deleteCommentReactionsApi = deleteCommentReactionsApi; 13 | exports.getUserMeApi = getUserMeApi; 14 | exports.getFileVersionsApi = getFileVersionsApi; 15 | exports.getTeamProjectsApi = getTeamProjectsApi; 16 | exports.getProjectFilesApi = getProjectFilesApi; 17 | exports.getTeamComponentsApi = getTeamComponentsApi; 18 | exports.getFileComponentsApi = getFileComponentsApi; 19 | exports.getComponentApi = getComponentApi; 20 | exports.getTeamComponentSetsApi = getTeamComponentSetsApi; 21 | exports.getFileComponentSetsApi = getFileComponentSetsApi; 22 | exports.getComponentSetApi = getComponentSetApi; 23 | exports.getTeamStylesApi = getTeamStylesApi; 24 | exports.getFileStylesApi = getFileStylesApi; 25 | exports.getStyleApi = getStyleApi; 26 | exports.getWebhookApi = getWebhookApi; 27 | exports.postWebhookApi = postWebhookApi; 28 | exports.putWebhookApi = putWebhookApi; 29 | exports.deleteWebhookApi = deleteWebhookApi; 30 | exports.getTeamWebhooksApi = getTeamWebhooksApi; 31 | exports.getWebhookRequestsApi = getWebhookRequestsApi; 32 | exports.getLocalVariablesApi = getLocalVariablesApi; 33 | exports.getPublishedVariablesApi = getPublishedVariablesApi; 34 | exports.postVariablesApi = postVariablesApi; 35 | exports.getDevResourcesApi = getDevResourcesApi; 36 | exports.postDevResourcesApi = postDevResourcesApi; 37 | exports.putDevResourcesApi = putDevResourcesApi; 38 | exports.deleteDevResourcesApi = deleteDevResourcesApi; 39 | exports.getLibraryAnalyticsComponentActionsApi = getLibraryAnalyticsComponentActionsApi; 40 | exports.getLibraryAnalyticsComponentUsagesApi = getLibraryAnalyticsComponentUsagesApi; 41 | exports.getLibraryAnalyticsStyleActionsApi = getLibraryAnalyticsStyleActionsApi; 42 | exports.getLibraryAnalyticsStyleUsagesApi = getLibraryAnalyticsStyleUsagesApi; 43 | exports.getLibraryAnalyticsVariableActionsApi = getLibraryAnalyticsVariableActionsApi; 44 | exports.getLibraryAnalyticsVariableUsagesApi = getLibraryAnalyticsVariableUsagesApi; 45 | var config_1 = require("./config"); 46 | var utils_1 = require("./utils"); 47 | // FILES 48 | // https://www.figma.com/developers/api#files-endpoints 49 | // ----------------------------------------------------------------- 50 | function getFileApi(pathParams, queryParams) { 51 | var encodedQueryParams = (0, utils_1.toQueryParams)(queryParams); 52 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "?").concat(encodedQueryParams)); 53 | } 54 | function getFileNodesApi(pathParams, queryParams) { 55 | var encodedQueryParams = (0, utils_1.toQueryParams)(queryParams); 56 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "/nodes?").concat(encodedQueryParams)); 57 | } 58 | function getImagesApi(pathParams, queryParams) { 59 | var encodedQueryParams = (0, utils_1.toQueryParams)(queryParams); 60 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/images/").concat(pathParams.file_key, "?").concat(encodedQueryParams)); 61 | } 62 | function getImageFillsApi(pathParams) { 63 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "/images")); 64 | } 65 | // COMMENTS 66 | // https://www.figma.com/developers/api#comments-endpoints 67 | // ----------------------------------------------------------------- 68 | function getCommentsApi(pathParams) { 69 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "/comments")); 70 | } 71 | function postCommentApi(pathParams, requestBody) { 72 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "/comments"), { 73 | method: 'POST', 74 | data: requestBody, 75 | }); 76 | } 77 | function deleteCommentApi(pathParams) { 78 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "/comments/").concat(pathParams.comment_id), { 79 | method: 'DELETE', 80 | data: '' 81 | }); 82 | } 83 | function getCommentReactionsApi(pathParams, queryParams) { 84 | var encodedQueryParams = (0, utils_1.toQueryParams)(queryParams); 85 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "/comments/").concat(pathParams.comment_id, "/reactions?").concat(encodedQueryParams)); 86 | } 87 | function postCommentReactionApi(pathParams, requestBody) { 88 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "/comments/").concat(pathParams.comment_id, "/reactions"), { 89 | method: 'POST', 90 | data: requestBody, 91 | }); 92 | } 93 | function deleteCommentReactionsApi(pathParams) { 94 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "/comments/").concat(pathParams.comment_id, "/reactions"), { 95 | method: 'DELETE', 96 | data: '' 97 | }); 98 | } 99 | // USERS 100 | // https://www.figma.com/developers/api#users-endpoints 101 | // ----------------------------------------------------------------- 102 | function getUserMeApi() { 103 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/me")); 104 | } 105 | // VERSION HISTORY (FILE VERSIONS) 106 | // https://www.figma.com/developers/api#version-history-endpoints 107 | // ----------------------------------------------------------------- 108 | function getFileVersionsApi(pathParams) { 109 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "/versions")); 110 | } 111 | // PROJECTS 112 | // https://www.figma.com/developers/api#projects-endpoints 113 | // ----------------------------------------------------------------- 114 | function getTeamProjectsApi(pathParams) { 115 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/teams/").concat(pathParams.team_id, "/projects")); 116 | } 117 | function getProjectFilesApi(pathParams, queryParams) { 118 | var encodedQueryParams = (0, utils_1.toQueryParams)(queryParams); 119 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/projects/").concat(pathParams.project_id, "/files?").concat(encodedQueryParams)); 120 | } 121 | // COMPONENTS AND STYLES (LIBRARY ITEMS) 122 | // https://www.figma.com/developers/api#library-items-endpoints 123 | // ----------------------------------------------------------------- 124 | function getTeamComponentsApi(pathParams, queryParams) { 125 | var encodedQueryParams = (0, utils_1.toQueryParams)(queryParams); 126 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/teams/").concat(pathParams.team_id, "/components?").concat(encodedQueryParams)); 127 | } 128 | function getFileComponentsApi(pathParams) { 129 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "/components")); 130 | } 131 | function getComponentApi(pathParams) { 132 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/components/").concat(pathParams.key)); 133 | } 134 | function getTeamComponentSetsApi(pathParams, queryParams) { 135 | var encodedQueryParams = (0, utils_1.toQueryParams)(queryParams); 136 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/teams/").concat(pathParams.team_id, "/component_sets?").concat(encodedQueryParams)); 137 | } 138 | function getFileComponentSetsApi(pathParams) { 139 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "/component_sets")); 140 | } 141 | function getComponentSetApi(pathParams) { 142 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/component_sets/").concat(pathParams.key)); 143 | } 144 | function getTeamStylesApi(pathParams, queryParams) { 145 | var encodedQueryParams = (0, utils_1.toQueryParams)(queryParams); 146 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/teams/").concat(pathParams.team_id, "/styles?").concat(encodedQueryParams)); 147 | } 148 | function getFileStylesApi(pathParams) { 149 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "/styles")); 150 | } 151 | function getStyleApi(pathParams) { 152 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/styles/").concat(pathParams.key)); 153 | } 154 | // WEBHOOKS 155 | // https://www.figma.com/developers/api#webhooks_v2 156 | // ----------------------------------------------------------------- 157 | function getWebhookApi(pathParams) { 158 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER_WEBHOOKS, "/webhooks/").concat(pathParams.webhook_id)); 159 | } 160 | function postWebhookApi(requestBody) { 161 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER_WEBHOOKS, "/webhooks"), { 162 | method: 'POST', 163 | data: requestBody, 164 | }); 165 | } 166 | function putWebhookApi(pathParams, requestBody) { 167 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER_WEBHOOKS, "/webhooks/").concat(pathParams.webhook_id), { 168 | method: 'PUT', 169 | data: requestBody, 170 | }); 171 | } 172 | function deleteWebhookApi(pathParams) { 173 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER_WEBHOOKS, "/webhooks/").concat(pathParams.webhook_id, "/"), { 174 | method: 'DELETE', 175 | data: '' 176 | }); 177 | } 178 | function getTeamWebhooksApi(pathParams) { 179 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER_WEBHOOKS, "/teams/").concat(pathParams.team_id, "/webhooks")); 180 | } 181 | function getWebhookRequestsApi(pathParams) { 182 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER_WEBHOOKS, "/webhooks/").concat(pathParams.webhook_id, "/requests")); 183 | } 184 | // ACTIVITY LOGS 185 | // https://www.figma.com/developers/api#activity-logs-endpoints 186 | // ----------------------------------------------------------------- 187 | // TODO - Open to contributions if someone is needs to use these endpoints 188 | // PAYMENTS 189 | // https://www.figma.com/developers/api#payments-endpoints 190 | // ----------------------------------------------------------------- 191 | // TODO - Open to contributions if someone is needs to use these endpoints 192 | // VARIABLES 193 | // These APIs are available only to full members of Enterprise orgs. 194 | // https://www.figma.com/developers/api#variables-endpoints 195 | // ----------------------------------------------------------------- 196 | function getLocalVariablesApi(pathParams) { 197 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "/variables/local")); 198 | } 199 | function getPublishedVariablesApi(pathParams) { 200 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "/variables/published")); 201 | } 202 | function postVariablesApi(pathParams, requestBody) { 203 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "/variables"), { 204 | method: 'POST', 205 | data: requestBody, 206 | }); 207 | } 208 | // DEV RESOURCES 209 | // https://www.figma.com/developers/api#dev-resources-endpoints 210 | // ----------------------------------------------------------------- 211 | function getDevResourcesApi(pathParams, queryParams) { 212 | var encodedQueryParams = (0, utils_1.toQueryParams)(queryParams); 213 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "/dev_resources")); 214 | } 215 | function postDevResourcesApi(requestBody) { 216 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/dev_resources"), { 217 | method: 'POST', 218 | data: requestBody, 219 | }); 220 | } 221 | function putDevResourcesApi(requestBody) { 222 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/dev_resources"), { 223 | method: 'PUT', 224 | data: requestBody, 225 | }); 226 | } 227 | function deleteDevResourcesApi(pathParams) { 228 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/files/").concat(pathParams.file_key, "/dev_resources/").concat(pathParams.dev_resource_id), { 229 | method: 'DELETE', 230 | data: '' 231 | }); 232 | } 233 | // ANALYTICS 234 | // https://www.figma.com/developers/api#library-analytics-endpoints 235 | // ----------------------------------------------------------------- 236 | function getLibraryAnalyticsComponentActionsApi(pathParams, queryParams) { 237 | var encodedQueryParams = (0, utils_1.toQueryParams)(queryParams); 238 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/analytics/libraries/").concat(pathParams.file_key, "/component/actions?").concat(encodedQueryParams)); 239 | } 240 | function getLibraryAnalyticsComponentUsagesApi(pathParams, queryParams) { 241 | var encodedQueryParams = (0, utils_1.toQueryParams)(queryParams); 242 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/analytics/libraries/").concat(pathParams.file_key, "/component/usages?").concat(encodedQueryParams)); 243 | } 244 | function getLibraryAnalyticsStyleActionsApi(pathParams, queryParams) { 245 | var encodedQueryParams = (0, utils_1.toQueryParams)(queryParams); 246 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/analytics/libraries/").concat(pathParams.file_key, "/style/actions?").concat(encodedQueryParams)); 247 | } 248 | function getLibraryAnalyticsStyleUsagesApi(pathParams, queryParams) { 249 | var encodedQueryParams = (0, utils_1.toQueryParams)(queryParams); 250 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/analytics/libraries/").concat(pathParams.file_key, "/style/usages?").concat(encodedQueryParams)); 251 | } 252 | function getLibraryAnalyticsVariableActionsApi(pathParams, queryParams) { 253 | var encodedQueryParams = (0, utils_1.toQueryParams)(queryParams); 254 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/analytics/libraries/").concat(pathParams.file_key, "/variable/actions?").concat(encodedQueryParams)); 255 | } 256 | function getLibraryAnalyticsVariableUsagesApi(pathParams, queryParams) { 257 | var encodedQueryParams = (0, utils_1.toQueryParams)(queryParams); 258 | return this.request("".concat(config_1.API_DOMAIN, "/").concat(config_1.API_VER, "/analytics/libraries/").concat(pathParams.file_key, "/variable/usages?").concat(encodedQueryParams)); 259 | } 260 | -------------------------------------------------------------------------------- /src/api-endpoints.ts: -------------------------------------------------------------------------------- 1 | import { API_DOMAIN, API_VER, API_VER_WEBHOOKS } from "./config"; 2 | import { ApiRequestMethod, toQueryParams } from "./utils"; 3 | 4 | // types 5 | 6 | type ApiClass = { 7 | request: ApiRequestMethod 8 | }; 9 | 10 | import type * as FigmaRestAPI from '@figma/rest-api-spec'; 11 | 12 | 13 | // FILES 14 | // https://www.figma.com/developers/api#files-endpoints 15 | // ----------------------------------------------------------------- 16 | 17 | export function getFileApi( 18 | this: ApiClass, 19 | pathParams: FigmaRestAPI.GetFilePathParams, 20 | queryParams?: FigmaRestAPI.GetFileQueryParams 21 | ): Promise { 22 | const encodedQueryParams = toQueryParams(queryParams); 23 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}?${encodedQueryParams}`); 24 | } 25 | 26 | export function getFileNodesApi( 27 | this: ApiClass, 28 | pathParams: FigmaRestAPI.GetFileNodesPathParams, 29 | queryParams?: FigmaRestAPI.GetFileNodesQueryParams 30 | ): Promise { 31 | const encodedQueryParams = toQueryParams(queryParams); 32 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}/nodes?${encodedQueryParams}`); 33 | } 34 | 35 | export function getImagesApi( 36 | this: ApiClass, 37 | pathParams: FigmaRestAPI.GetImagesPathParams, 38 | queryParams?: FigmaRestAPI.GetImagesQueryParams, 39 | ): Promise { 40 | const encodedQueryParams = toQueryParams(queryParams); 41 | return this.request(`${API_DOMAIN}/${API_VER}/images/${pathParams.file_key}?${encodedQueryParams}`); 42 | } 43 | 44 | export function getImageFillsApi( 45 | this: ApiClass, 46 | pathParams: FigmaRestAPI.GetImageFillsPathParams, 47 | ): Promise { 48 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}/images`); 49 | } 50 | 51 | 52 | // COMMENTS 53 | // https://www.figma.com/developers/api#comments-endpoints 54 | // ----------------------------------------------------------------- 55 | 56 | export function getCommentsApi( 57 | this: ApiClass, 58 | pathParams: FigmaRestAPI.GetCommentsPathParams 59 | ): Promise { 60 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}/comments`); 61 | } 62 | 63 | export function postCommentApi( 64 | this: ApiClass, 65 | pathParams: FigmaRestAPI.PostCommentPathParams, 66 | requestBody?: FigmaRestAPI.PostCommentRequestBody, 67 | ): Promise { 68 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}/comments`, { 69 | method: 'POST', 70 | data: requestBody, 71 | }); 72 | } 73 | 74 | export function deleteCommentApi( 75 | this: ApiClass, 76 | pathParams: FigmaRestAPI.DeleteCommentPathParams, 77 | ): Promise { 78 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}/comments/${pathParams.comment_id}`, { 79 | method: 'DELETE', 80 | data: '' 81 | }); 82 | } 83 | 84 | export function getCommentReactionsApi( 85 | this: ApiClass, 86 | pathParams: FigmaRestAPI.GetCommentReactionsPathParams, 87 | queryParams?: FigmaRestAPI.GetCommentReactionsQueryParams, 88 | ): Promise { 89 | const encodedQueryParams = toQueryParams(queryParams); 90 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}/comments/${pathParams.comment_id}/reactions?${encodedQueryParams}`); 91 | } 92 | 93 | export function postCommentReactionApi( 94 | this: ApiClass, 95 | pathParams: FigmaRestAPI.PostCommentReactionPathParams, 96 | requestBody?: FigmaRestAPI.PostCommentReactionRequestBody, 97 | ): Promise { 98 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}/comments/${pathParams.comment_id}/reactions`, { 99 | method: 'POST', 100 | data: requestBody, 101 | }); 102 | } 103 | 104 | export function deleteCommentReactionsApi( 105 | this: ApiClass, 106 | pathParams: FigmaRestAPI.DeleteCommentReactionPathParams, 107 | ): Promise { 108 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}/comments/${pathParams.comment_id}/reactions`, { 109 | method: 'DELETE', 110 | data: '' 111 | }); 112 | } 113 | 114 | 115 | // USERS 116 | // https://www.figma.com/developers/api#users-endpoints 117 | // ----------------------------------------------------------------- 118 | 119 | export function getUserMeApi( 120 | this: ApiClass 121 | ): Promise { 122 | return this.request(`${API_DOMAIN}/${API_VER}/me`); 123 | } 124 | 125 | 126 | // VERSION HISTORY (FILE VERSIONS) 127 | // https://www.figma.com/developers/api#version-history-endpoints 128 | // ----------------------------------------------------------------- 129 | 130 | export function getFileVersionsApi( 131 | this: ApiClass, 132 | pathParams: FigmaRestAPI.GetFileVersionsPathParams 133 | ): Promise { 134 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}/versions`); 135 | } 136 | 137 | 138 | // PROJECTS 139 | // https://www.figma.com/developers/api#projects-endpoints 140 | // ----------------------------------------------------------------- 141 | 142 | export function getTeamProjectsApi( 143 | this: ApiClass, 144 | pathParams: FigmaRestAPI.GetTeamProjectsPathParams 145 | ): Promise { 146 | return this.request(`${API_DOMAIN}/${API_VER}/teams/${pathParams.team_id}/projects`); 147 | } 148 | 149 | export function getProjectFilesApi( 150 | this: ApiClass, 151 | pathParams: FigmaRestAPI.GetProjectFilesPathParams, 152 | queryParams?: FigmaRestAPI.GetProjectFilesQueryParams, 153 | ): Promise { 154 | const encodedQueryParams = toQueryParams(queryParams); 155 | return this.request(`${API_DOMAIN}/${API_VER}/projects/${pathParams.project_id}/files?${encodedQueryParams}`); 156 | } 157 | 158 | 159 | // COMPONENTS AND STYLES (LIBRARY ITEMS) 160 | // https://www.figma.com/developers/api#library-items-endpoints 161 | // ----------------------------------------------------------------- 162 | 163 | export function getTeamComponentsApi( 164 | this: ApiClass, 165 | pathParams: FigmaRestAPI.GetTeamComponentsPathParams, 166 | queryParams?: FigmaRestAPI.GetTeamComponentsQueryParams, 167 | ): Promise { 168 | const encodedQueryParams = toQueryParams(queryParams); 169 | return this.request(`${API_DOMAIN}/${API_VER}/teams/${pathParams.team_id}/components?${encodedQueryParams}`); 170 | } 171 | 172 | export function getFileComponentsApi( 173 | this: ApiClass, 174 | pathParams: FigmaRestAPI.GetFileComponentsPathParams, 175 | ): Promise { 176 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}/components`); 177 | } 178 | 179 | export function getComponentApi( 180 | this: ApiClass, 181 | pathParams: FigmaRestAPI.GetComponentPathParams, 182 | ): Promise { 183 | return this.request(`${API_DOMAIN}/${API_VER}/components/${pathParams.key}`); 184 | } 185 | 186 | export function getTeamComponentSetsApi( 187 | this: ApiClass, 188 | pathParams: FigmaRestAPI.GetTeamComponentSetsPathParams, 189 | queryParams?: FigmaRestAPI.GetTeamComponentSetsQueryParams, 190 | ): Promise { 191 | const encodedQueryParams = toQueryParams(queryParams); 192 | return this.request(`${API_DOMAIN}/${API_VER}/teams/${pathParams.team_id}/component_sets?${encodedQueryParams}`); 193 | } 194 | 195 | export function getFileComponentSetsApi( 196 | this: ApiClass, 197 | pathParams: FigmaRestAPI.GetFileComponentSetsPathParams, 198 | ): Promise { 199 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}/component_sets`); 200 | } 201 | 202 | export function getComponentSetApi( 203 | this: ApiClass, 204 | pathParams: FigmaRestAPI.GetComponentSetPathParams, 205 | ): Promise { 206 | return this.request(`${API_DOMAIN}/${API_VER}/component_sets/${pathParams.key}`); 207 | } 208 | 209 | export function getTeamStylesApi( 210 | this: ApiClass, 211 | pathParams: FigmaRestAPI.GetTeamStylesPathParams, 212 | queryParams?: FigmaRestAPI.GetTeamStylesQueryParams, 213 | ): Promise { 214 | const encodedQueryParams = toQueryParams(queryParams); 215 | return this.request(`${API_DOMAIN}/${API_VER}/teams/${pathParams.team_id}/styles?${encodedQueryParams}`); 216 | } 217 | 218 | export function getFileStylesApi( 219 | this: ApiClass, 220 | pathParams: FigmaRestAPI.GetFileStylesPathParams, 221 | ): Promise { 222 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}/styles`); 223 | } 224 | 225 | export function getStyleApi( 226 | this: ApiClass, 227 | pathParams: FigmaRestAPI.GetStylePathParams, 228 | ): Promise { 229 | return this.request(`${API_DOMAIN}/${API_VER}/styles/${pathParams.key}`); 230 | } 231 | 232 | 233 | // WEBHOOKS 234 | // https://www.figma.com/developers/api#webhooks_v2 235 | // ----------------------------------------------------------------- 236 | 237 | export function getWebhookApi( 238 | this: ApiClass, 239 | pathParams: FigmaRestAPI.GetWebhookPathParams, 240 | ): Promise { 241 | return this.request(`${API_DOMAIN}/${API_VER_WEBHOOKS}/webhooks/${pathParams.webhook_id}`); 242 | } 243 | 244 | export function postWebhookApi( 245 | this: ApiClass, 246 | requestBody?: FigmaRestAPI.PostWebhookRequestBody, 247 | ): Promise { 248 | return this.request(`${API_DOMAIN}/${API_VER_WEBHOOKS}/webhooks`, { 249 | method: 'POST', 250 | data: requestBody, 251 | }); 252 | } 253 | 254 | export function putWebhookApi( 255 | this: ApiClass, 256 | pathParams: FigmaRestAPI.PutWebhookPathParams, 257 | requestBody?: FigmaRestAPI.PutWebhookRequestBody, 258 | ): Promise { 259 | return this.request(`${API_DOMAIN}/${API_VER_WEBHOOKS}/webhooks/${pathParams.webhook_id}`, { 260 | method: 'PUT', 261 | data: requestBody, 262 | }); 263 | } 264 | 265 | export function deleteWebhookApi( 266 | this: ApiClass, 267 | pathParams: FigmaRestAPI.DeleteWebhookPathParams, 268 | ): Promise { 269 | return this.request(`${API_DOMAIN}/${API_VER_WEBHOOKS}/webhooks/${pathParams.webhook_id}/`, { 270 | method: 'DELETE', 271 | data: '' 272 | }); 273 | } 274 | 275 | export function getTeamWebhooksApi( 276 | this: ApiClass, 277 | pathParams: FigmaRestAPI.GetTeamWebhooksPathParams, 278 | ): Promise { 279 | return this.request(`${API_DOMAIN}/${API_VER_WEBHOOKS}/teams/${pathParams.team_id}/webhooks`); 280 | } 281 | 282 | export function getWebhookRequestsApi( 283 | this: ApiClass, 284 | pathParams: FigmaRestAPI.GetWebhookRequestsPathParams, 285 | ): Promise { 286 | return this.request(`${API_DOMAIN}/${API_VER_WEBHOOKS}/webhooks/${pathParams.webhook_id}/requests`); 287 | } 288 | 289 | 290 | // ACTIVITY LOGS 291 | // https://www.figma.com/developers/api#activity-logs-endpoints 292 | // ----------------------------------------------------------------- 293 | 294 | // TODO - Open to contributions if someone is needs to use these endpoints 295 | 296 | 297 | // PAYMENTS 298 | // https://www.figma.com/developers/api#payments-endpoints 299 | // ----------------------------------------------------------------- 300 | 301 | // TODO - Open to contributions if someone is needs to use these endpoints 302 | 303 | 304 | // VARIABLES 305 | // These APIs are available only to full members of Enterprise orgs. 306 | // https://www.figma.com/developers/api#variables-endpoints 307 | // ----------------------------------------------------------------- 308 | 309 | export function getLocalVariablesApi( 310 | this: ApiClass, 311 | pathParams: FigmaRestAPI.GetLocalVariablesPathParams, 312 | ): Promise { 313 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}/variables/local`); 314 | } 315 | 316 | export function getPublishedVariablesApi( 317 | this: ApiClass, 318 | pathParams: FigmaRestAPI.GetPublishedVariablesPathParams, 319 | ): Promise { 320 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}/variables/published`); 321 | } 322 | 323 | export function postVariablesApi( 324 | this: ApiClass, 325 | pathParams: FigmaRestAPI.PostVariablesPathParams, 326 | requestBody?: FigmaRestAPI.PostVariablesRequestBody, 327 | ): Promise { 328 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}/variables`, { 329 | method: 'POST', 330 | data: requestBody, 331 | }); 332 | } 333 | 334 | 335 | // DEV RESOURCES 336 | // https://www.figma.com/developers/api#dev-resources-endpoints 337 | // ----------------------------------------------------------------- 338 | 339 | export function getDevResourcesApi( 340 | this: ApiClass, 341 | pathParams: FigmaRestAPI.GetDevResourcesPathParams, 342 | queryParams?: FigmaRestAPI.GetDevResourcesQueryParams, 343 | ): Promise { 344 | const encodedQueryParams = toQueryParams(queryParams); 345 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}/dev_resources`); 346 | } 347 | 348 | export function postDevResourcesApi( 349 | this: ApiClass, 350 | requestBody?: FigmaRestAPI.PostDevResourcesRequestBody, 351 | ): Promise { 352 | return this.request(`${API_DOMAIN}/${API_VER}/dev_resources`, { 353 | method: 'POST', 354 | data: requestBody, 355 | }); 356 | } 357 | 358 | export function putDevResourcesApi( 359 | this: ApiClass, 360 | requestBody?: FigmaRestAPI.PutDevResourcesRequestBody, 361 | ): Promise { 362 | return this.request(`${API_DOMAIN}/${API_VER}/dev_resources`, { 363 | method: 'PUT', 364 | data: requestBody, 365 | }); 366 | } 367 | 368 | export function deleteDevResourcesApi( 369 | this: ApiClass, 370 | pathParams: FigmaRestAPI.DeleteDevResourcePathParams, 371 | ): Promise { 372 | return this.request(`${API_DOMAIN}/${API_VER}/files/${pathParams.file_key}/dev_resources/${pathParams.dev_resource_id}`, { 373 | method: 'DELETE', 374 | data: '' 375 | }); 376 | } 377 | 378 | 379 | // ANALYTICS 380 | // https://www.figma.com/developers/api#library-analytics-endpoints 381 | // ----------------------------------------------------------------- 382 | 383 | export function getLibraryAnalyticsComponentActionsApi( 384 | this: ApiClass, 385 | pathParams: FigmaRestAPI.GetLibraryAnalyticsComponentActionsPathParams, 386 | queryParams?: FigmaRestAPI.GetLibraryAnalyticsComponentActionsQueryParams, 387 | ): Promise { 388 | const encodedQueryParams = toQueryParams(queryParams); 389 | return this.request(`${API_DOMAIN}/${API_VER}/analytics/libraries/${pathParams.file_key}/component/actions?${encodedQueryParams}`); 390 | } 391 | 392 | export function getLibraryAnalyticsComponentUsagesApi( 393 | this: ApiClass, 394 | pathParams: FigmaRestAPI.GetLibraryAnalyticsComponentUsagesPathParams, 395 | queryParams?: FigmaRestAPI.GetLibraryAnalyticsComponentUsagesQueryParams, 396 | ): Promise { 397 | const encodedQueryParams = toQueryParams(queryParams); 398 | return this.request(`${API_DOMAIN}/${API_VER}/analytics/libraries/${pathParams.file_key}/component/usages?${encodedQueryParams}`); 399 | } 400 | 401 | export function getLibraryAnalyticsStyleActionsApi( 402 | this: ApiClass, 403 | pathParams: FigmaRestAPI.GetLibraryAnalyticsStyleActionsPathParams, 404 | queryParams?: FigmaRestAPI.GetLibraryAnalyticsStyleActionsQueryParams, 405 | ): Promise { 406 | const encodedQueryParams = toQueryParams(queryParams); 407 | return this.request(`${API_DOMAIN}/${API_VER}/analytics/libraries/${pathParams.file_key}/style/actions?${encodedQueryParams}`); 408 | } 409 | 410 | export function getLibraryAnalyticsStyleUsagesApi( 411 | this: ApiClass, 412 | pathParams: FigmaRestAPI.GetLibraryAnalyticsStyleUsagesPathParams, 413 | queryParams?: FigmaRestAPI.GetLibraryAnalyticsStyleUsagesQueryParams, 414 | ): Promise { 415 | const encodedQueryParams = toQueryParams(queryParams); 416 | return this.request(`${API_DOMAIN}/${API_VER}/analytics/libraries/${pathParams.file_key}/style/usages?${encodedQueryParams}`); 417 | } 418 | 419 | export function getLibraryAnalyticsVariableActionsApi( 420 | this: ApiClass, 421 | pathParams: FigmaRestAPI.GetLibraryAnalyticsVariableActionsPathParams, 422 | queryParams?: FigmaRestAPI.GetLibraryAnalyticsVariableActionsQueryParams, 423 | ): Promise { 424 | const encodedQueryParams = toQueryParams(queryParams); 425 | return this.request(`${API_DOMAIN}/${API_VER}/analytics/libraries/${pathParams.file_key}/variable/actions?${encodedQueryParams}`); 426 | } 427 | 428 | export function getLibraryAnalyticsVariableUsagesApi( 429 | this: ApiClass, 430 | pathParams: FigmaRestAPI.GetLibraryAnalyticsVariableUsagesPathParams, 431 | queryParams?: FigmaRestAPI.GetLibraryAnalyticsVariableUsagesQueryParams, 432 | ): Promise { 433 | const encodedQueryParams = toQueryParams(queryParams); 434 | return this.request(`${API_DOMAIN}/${API_VER}/analytics/libraries/${pathParams.file_key}/variable/usages?${encodedQueryParams}`); 435 | } 436 | 437 | -------------------------------------------------------------------------------- /lib/figma-api.min.js: -------------------------------------------------------------------------------- 1 | "use strict";var Figma=(()=>{var _e=Object.defineProperty;var Bs=Object.getOwnPropertyDescriptor;var Gs=Object.getOwnPropertyNames;var js=Object.prototype.hasOwnProperty;var pt=(e,t)=>{for(var s in t)_e(e,s,{get:t[s],enumerable:!0})},Vs=(e,t,s,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Gs(t))!js.call(e,o)&&o!==s&&_e(e,o,{get:()=>t[o],enumerable:!(r=Bs(t,o))||r.enumerable});return e};var Hs=e=>Vs(_e({},"__esModule",{value:!0}),e);var ao={};pt(ao,{API_DOMAIN:()=>g,API_VER:()=>y,API_VER_WEBHOOKS:()=>V,Api:()=>ot,oAuthLink:()=>no,oAuthToken:()=>io});var g="https://api.figma.com",y="v1",V="v2";function x(e){return e?Object.entries(e).map(([t,s])=>t&&s&&`${t}=${encodeURIComponent(s)}`).filter(Boolean).join("&"):""}var Y=class e extends Error{constructor(s){super(s.message);this.error=s;this.name="ApiError",Error.captureStackTrace&&Error.captureStackTrace(this,e)}};function mt(e,t){let s=x(t);return this.request(`${g}/${"v1"}/files/${e.file_key}?${s}`)}function ft(e,t){let s=x(t);return this.request(`${g}/${"v1"}/files/${e.file_key}/nodes?${s}`)}function dt(e,t){let s=x(t);return this.request(`${g}/${"v1"}/images/${e.file_key}?${s}`)}function ht(e){return this.request(`${g}/${"v1"}/files/${e.file_key}/images`)}function At(e){return this.request(`${g}/${"v1"}/files/${e.file_key}/comments`)}function gt(e,t){return this.request(`${g}/${"v1"}/files/${e.file_key}/comments`,{method:"POST",data:t})}function yt(e){return this.request(`${g}/${"v1"}/files/${e.file_key}/comments/${e.comment_id}`,{method:"DELETE",data:""})}function Pt(e,t){let s=x(t);return this.request(`${g}/${"v1"}/files/${e.file_key}/comments/${e.comment_id}/reactions?${s}`)}function Rt(e,t){return this.request(`${g}/${"v1"}/files/${e.file_key}/comments/${e.comment_id}/reactions`,{method:"POST",data:t})}function bt(e){return this.request(`${g}/${"v1"}/files/${e.file_key}/comments/${e.comment_id}/reactions`,{method:"DELETE",data:""})}function Et(){return this.request(`${g}/${"v1"}/me`)}function xt(e){return this.request(`${g}/${"v1"}/files/${e.file_key}/versions`)}function Ct(e){return this.request(`${g}/${"v1"}/teams/${e.team_id}/projects`)}function Ft(e,t){let s=x(t);return this.request(`${g}/${"v1"}/projects/${e.project_id}/files?${s}`)}function wt(e,t){let s=x(t);return this.request(`${g}/${"v1"}/teams/${e.team_id}/components?${s}`)}function St(e){return this.request(`${g}/${"v1"}/files/${e.file_key}/components`)}function Tt(e){return this.request(`${g}/${"v1"}/components/${e.key}`)}function Ot(e,t){let s=x(t);return this.request(`${g}/${"v1"}/teams/${e.team_id}/component_sets?${s}`)}function It(e){return this.request(`${g}/${"v1"}/files/${e.file_key}/component_sets`)}function kt(e){return this.request(`${g}/${"v1"}/component_sets/${e.key}`)}function $t(e,t){let s=x(t);return this.request(`${g}/${"v1"}/teams/${e.team_id}/styles?${s}`)}function _t(e){return this.request(`${g}/${"v1"}/files/${e.file_key}/styles`)}function Lt(e){return this.request(`${g}/${"v1"}/styles/${e.key}`)}function qt(e){return this.request(`${g}/${"v2"}/webhooks/${e.webhook_id}`)}function Dt(e){return this.request(`${g}/${"v2"}/webhooks`,{method:"POST",data:e})}function Ut(e,t){return this.request(`${g}/${"v2"}/webhooks/${e.webhook_id}`,{method:"PUT",data:t})}function Nt(e){return this.request(`${g}/${"v2"}/webhooks/${e.webhook_id}/`,{method:"DELETE",data:""})}function Bt(e){return this.request(`${g}/${"v2"}/teams/${e.team_id}/webhooks`)}function Gt(e){return this.request(`${g}/${"v2"}/webhooks/${e.webhook_id}/requests`)}function jt(e){return this.request(`${g}/${"v1"}/files/${e.file_key}/variables/local`)}function Vt(e){return this.request(`${g}/${"v1"}/files/${e.file_key}/variables/published`)}function Ht(e,t){return this.request(`${g}/${"v1"}/files/${e.file_key}/variables`,{method:"POST",data:t})}function Mt(e,t){let s=x(t);return this.request(`${g}/${"v1"}/files/${e.file_key}/dev_resources`)}function Wt(e){return this.request(`${g}/${"v1"}/dev_resources`,{method:"POST",data:e})}function vt(e){return this.request(`${g}/${"v1"}/dev_resources`,{method:"PUT",data:e})}function Qt(e){return this.request(`${g}/${"v1"}/files/${e.file_key}/dev_resources/${e.dev_resource_id}`,{method:"DELETE",data:""})}function zt(e,t){let s=x(t);return this.request(`${g}/${"v1"}/analytics/libraries/${e.file_key}/component/actions?${s}`)}function Jt(e,t){let s=x(t);return this.request(`${g}/${"v1"}/analytics/libraries/${e.file_key}/component/usages?${s}`)}function Kt(e,t){let s=x(t);return this.request(`${g}/${"v1"}/analytics/libraries/${e.file_key}/style/actions?${s}`)}function Xt(e,t){let s=x(t);return this.request(`${g}/${"v1"}/analytics/libraries/${e.file_key}/style/usages?${s}`)}function Zt(e,t){let s=x(t);return this.request(`${g}/${"v1"}/analytics/libraries/${e.file_key}/variable/actions?${s}`)}function Yt(e,t){let s=x(t);return this.request(`${g}/${"v1"}/analytics/libraries/${e.file_key}/variable/usages?${s}`)}function ee(e,t){return function(){return e.apply(t,arguments)}}var{toString:Ws}=Object.prototype,{getPrototypeOf:qe}=Object,{iterator:ge,toStringTag:ts}=Symbol,ye=(e=>t=>{let s=Ws.call(t);return e[s]||(e[s]=s.slice(8,-1).toLowerCase())})(Object.create(null)),$=e=>(e=e.toLowerCase(),t=>ye(t)===e),Pe=e=>t=>typeof t===e,{isArray:v}=Array,W=Pe("undefined");function te(e){return e!==null&&!W(e)&&e.constructor!==null&&!W(e.constructor)&&T(e.constructor.isBuffer)&&e.constructor.isBuffer(e)}var ss=$("ArrayBuffer");function vs(e){let t;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?t=ArrayBuffer.isView(e):t=e&&e.buffer&&ss(e.buffer),t}var Qs=Pe("string"),T=Pe("function"),rs=Pe("number"),se=e=>e!==null&&typeof e=="object",zs=e=>e===!0||e===!1,Ae=e=>{if(ye(e)!=="object")return!1;let t=qe(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(ts in e)&&!(ge in e)},Js=e=>{if(!se(e)||te(e))return!1;try{return Object.keys(e).length===0&&Object.getPrototypeOf(e)===Object.prototype}catch{return!1}},Ks=$("Date"),Xs=$("File"),Zs=$("Blob"),Ys=$("FileList"),er=e=>se(e)&&T(e.pipe),tr=e=>{let t;return e&&(typeof FormData=="function"&&e instanceof FormData||T(e.append)&&((t=ye(e))==="formdata"||t==="object"&&T(e.toString)&&e.toString()==="[object FormData]"))},sr=$("URLSearchParams"),[rr,or,nr,ir]=["ReadableStream","Request","Response","Headers"].map($),ar=e=>e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function re(e,t,{allOwnKeys:s=!1}={}){if(e===null||typeof e>"u")return;let r,o;if(typeof e!="object"&&(e=[e]),v(e))for(r=0,o=e.length;r0;)if(o=s[r],t===o.toLowerCase())return o;return null}var H=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global,ns=e=>!W(e)&&e!==H;function Le(){let{caseless:e,skipUndefined:t}=ns(this)&&this||{},s={},r=(o,i)=>{let n=e&&os(s,i)||i;Ae(s[n])&&Ae(o)?s[n]=Le(s[n],o):Ae(o)?s[n]=Le({},o):v(o)?s[n]=o.slice():(!t||!W(o))&&(s[n]=o)};for(let o=0,i=arguments.length;o(re(t,(o,i)=>{s&&T(o)?e[i]=ee(o,s):e[i]=o},{allOwnKeys:r}),e),cr=e=>(e.charCodeAt(0)===65279&&(e=e.slice(1)),e),ur=(e,t,s,r)=>{e.prototype=Object.create(t.prototype,r),e.prototype.constructor=e,Object.defineProperty(e,"super",{value:t.prototype}),s&&Object.assign(e.prototype,s)},pr=(e,t,s,r)=>{let o,i,n,l={};if(t=t||{},e==null)return t;do{for(o=Object.getOwnPropertyNames(e),i=o.length;i-- >0;)n=o[i],(!r||r(n,e,t))&&!l[n]&&(t[n]=e[n],l[n]=!0);e=s!==!1&&qe(e)}while(e&&(!s||s(e,t))&&e!==Object.prototype);return t},mr=(e,t,s)=>{e=String(e),(s===void 0||s>e.length)&&(s=e.length),s-=t.length;let r=e.indexOf(t,s);return r!==-1&&r===s},fr=e=>{if(!e)return null;if(v(e))return e;let t=e.length;if(!rs(t))return null;let s=new Array(t);for(;t-- >0;)s[t]=e[t];return s},dr=(e=>t=>e&&t instanceof e)(typeof Uint8Array<"u"&&qe(Uint8Array)),hr=(e,t)=>{let r=(e&&e[ge]).call(e),o;for(;(o=r.next())&&!o.done;){let i=o.value;t.call(e,i[0],i[1])}},Ar=(e,t)=>{let s,r=[];for(;(s=e.exec(t))!==null;)r.push(s);return r},gr=$("HTMLFormElement"),yr=e=>e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(s,r,o){return r.toUpperCase()+o}),es=(({hasOwnProperty:e})=>(t,s)=>e.call(t,s))(Object.prototype),Pr=$("RegExp"),is=(e,t)=>{let s=Object.getOwnPropertyDescriptors(e),r={};re(s,(o,i)=>{let n;(n=t(o,i,e))!==!1&&(r[i]=n||o)}),Object.defineProperties(e,r)},Rr=e=>{is(e,(t,s)=>{if(T(e)&&["arguments","caller","callee"].indexOf(s)!==-1)return!1;let r=e[s];if(T(r)){if(t.enumerable=!1,"writable"in t){t.writable=!1;return}t.set||(t.set=()=>{throw Error("Can not rewrite read-only method '"+s+"'")})}})},br=(e,t)=>{let s={},r=o=>{o.forEach(i=>{s[i]=!0})};return v(e)?r(e):r(String(e).split(t)),s},Er=()=>{},xr=(e,t)=>e!=null&&Number.isFinite(e=+e)?e:t;function Cr(e){return!!(e&&T(e.append)&&e[ts]==="FormData"&&e[ge])}var Fr=e=>{let t=new Array(10),s=(r,o)=>{if(se(r)){if(t.indexOf(r)>=0)return;if(te(r))return r;if(!("toJSON"in r)){t[o]=r;let i=v(r)?[]:{};return re(r,(n,l)=>{let m=s(n,o+1);!W(m)&&(i[l]=m)}),t[o]=void 0,i}}return r};return s(e,0)},wr=$("AsyncFunction"),Sr=e=>e&&(se(e)||T(e))&&T(e.then)&&T(e.catch),as=((e,t)=>e?setImmediate:t?((s,r)=>(H.addEventListener("message",({source:o,data:i})=>{o===H&&i===s&&r.length&&r.shift()()},!1),o=>{r.push(o),H.postMessage(s,"*")}))(`axios@${Math.random()}`,[]):s=>setTimeout(s))(typeof setImmediate=="function",T(H.postMessage)),Tr=typeof queueMicrotask<"u"?queueMicrotask.bind(H):typeof process<"u"&&process.nextTick||as,Or=e=>e!=null&&T(e[ge]),a={isArray:v,isArrayBuffer:ss,isBuffer:te,isFormData:tr,isArrayBufferView:vs,isString:Qs,isNumber:rs,isBoolean:zs,isObject:se,isPlainObject:Ae,isEmptyObject:Js,isReadableStream:rr,isRequest:or,isResponse:nr,isHeaders:ir,isUndefined:W,isDate:Ks,isFile:Xs,isBlob:Zs,isRegExp:Pr,isFunction:T,isStream:er,isURLSearchParams:sr,isTypedArray:dr,isFileList:Ys,forEach:re,merge:Le,extend:lr,trim:ar,stripBOM:cr,inherits:ur,toFlatObject:pr,kindOf:ye,kindOfTest:$,endsWith:mr,toArray:fr,forEachEntry:hr,matchAll:Ar,isHTMLForm:gr,hasOwnProperty:es,hasOwnProp:es,reduceDescriptors:is,freezeMethods:Rr,toObjectSet:br,toCamelCase:yr,noop:Er,toFiniteNumber:xr,findKey:os,global:H,isContextDefined:ns,isSpecCompliantForm:Cr,toJSONObject:Fr,isAsyncFn:wr,isThenable:Sr,setImmediate:as,asap:Tr,isIterable:Or};function Q(e,t,s,r,o){Error.call(this),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack,this.message=e,this.name="AxiosError",t&&(this.code=t),s&&(this.config=s),r&&(this.request=r),o&&(this.response=o,this.status=o.status?o.status:null)}a.inherits(Q,Error,{toJSON:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:a.toJSONObject(this.config),code:this.code,status:this.status}}});var ls=Q.prototype,cs={};["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED","ERR_NOT_SUPPORT","ERR_INVALID_URL"].forEach(e=>{cs[e]={value:e}});Object.defineProperties(Q,cs);Object.defineProperty(ls,"isAxiosError",{value:!0});Q.from=(e,t,s,r,o,i)=>{let n=Object.create(ls);a.toFlatObject(e,n,function(c){return c!==Error.prototype},p=>p!=="isAxiosError");let l=e&&e.message?e.message:"Error",m=t==null&&e?e.code:t;return Q.call(n,l,m,s,r,o),e&&n.cause==null&&Object.defineProperty(n,"cause",{value:e,configurable:!0}),n.name=e&&e.name||"Error",i&&Object.assign(n,i),n};var A=Q;var Re=null;function De(e){return a.isPlainObject(e)||a.isArray(e)}function ps(e){return a.endsWith(e,"[]")?e.slice(0,-2):e}function us(e,t,s){return e?e.concat(t).map(function(o,i){return o=ps(o),!s&&i?"["+o+"]":o}).join(s?".":""):t}function Ir(e){return a.isArray(e)&&!e.some(De)}var kr=a.toFlatObject(a,{},null,function(t){return/^is[A-Z]/.test(t)});function $r(e,t,s){if(!a.isObject(e))throw new TypeError("target must be an object");t=t||new(Re||FormData),s=a.toFlatObject(s,{metaTokens:!0,dots:!1,indexes:!1},!1,function(h,f){return!a.isUndefined(f[h])});let r=s.metaTokens,o=s.visitor||c,i=s.dots,n=s.indexes,m=(s.Blob||typeof Blob<"u"&&Blob)&&a.isSpecCompliantForm(t);if(!a.isFunction(o))throw new TypeError("visitor must be a function");function p(u){if(u===null)return"";if(a.isDate(u))return u.toISOString();if(a.isBoolean(u))return u.toString();if(!m&&a.isBlob(u))throw new A("Blob is not supported. Use a Buffer instead.");return a.isArrayBuffer(u)||a.isTypedArray(u)?m&&typeof Blob=="function"?new Blob([u]):Buffer.from(u):u}function c(u,h,f){let E=u;if(u&&!f&&typeof u=="object"){if(a.endsWith(h,"{}"))h=r?h:h.slice(0,-2),u=JSON.stringify(u);else if(a.isArray(u)&&Ir(u)||(a.isFileList(u)||a.endsWith(h,"[]"))&&(E=a.toArray(u)))return h=ps(h),E.forEach(function(F,S){!(a.isUndefined(F)||F===null)&&t.append(n===!0?us([h],S,i):n===null?h:h+"[]",p(F))}),!1}return De(u)?!0:(t.append(us(f,h,i),p(u)),!1)}let d=[],P=Object.assign(kr,{defaultVisitor:c,convertValue:p,isVisitable:De});function w(u,h){if(!a.isUndefined(u)){if(d.indexOf(u)!==-1)throw Error("Circular reference detected in "+h.join("."));d.push(u),a.forEach(u,function(E,O){(!(a.isUndefined(E)||E===null)&&o.call(t,E,a.isString(O)?O.trim():O,h,P))===!0&&w(E,h?h.concat(O):[O])}),d.pop()}}if(!a.isObject(e))throw new TypeError("data must be an object");return w(e),t}var B=$r;function ms(e){let t={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+","%00":"\0"};return encodeURIComponent(e).replace(/[!'()~]|%20|%00/g,function(r){return t[r]})}function fs(e,t){this._pairs=[],e&&B(e,this,t)}var ds=fs.prototype;ds.append=function(t,s){this._pairs.push([t,s])};ds.toString=function(t){let s=t?function(r){return t.call(this,r,ms)}:ms;return this._pairs.map(function(o){return s(o[0])+"="+s(o[1])},"").join("&")};var be=fs;function _r(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+")}function oe(e,t,s){if(!t)return e;let r=s&&s.encode||_r;a.isFunction(s)&&(s={serialize:s});let o=s&&s.serialize,i;if(o?i=o(t,s):i=a.isURLSearchParams(t)?t.toString():new be(t,s).toString(r),i){let n=e.indexOf("#");n!==-1&&(e=e.slice(0,n)),e+=(e.indexOf("?")===-1?"?":"&")+i}return e}var Ue=class{constructor(){this.handlers=[]}use(t,s,r){return this.handlers.push({fulfilled:t,rejected:s,synchronous:r?r.synchronous:!1,runWhen:r?r.runWhen:null}),this.handlers.length-1}eject(t){this.handlers[t]&&(this.handlers[t]=null)}clear(){this.handlers&&(this.handlers=[])}forEach(t){a.forEach(this.handlers,function(r){r!==null&&t(r)})}},Ne=Ue;var Ee={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1};var hs=typeof URLSearchParams<"u"?URLSearchParams:be;var As=typeof FormData<"u"?FormData:null;var gs=typeof Blob<"u"?Blob:null;var ys={isBrowser:!0,classes:{URLSearchParams:hs,FormData:As,Blob:gs},protocols:["http","https","file","blob","url","data"]};var je={};pt(je,{hasBrowserEnv:()=>Ge,hasStandardBrowserEnv:()=>Lr,hasStandardBrowserWebWorkerEnv:()=>qr,navigator:()=>Be,origin:()=>Dr});var Ge=typeof window<"u"&&typeof document<"u",Be=typeof navigator=="object"&&navigator||void 0,Lr=Ge&&(!Be||["ReactNative","NativeScript","NS"].indexOf(Be.product)<0),qr=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope&&typeof self.importScripts=="function",Dr=Ge&&window.location.href||"http://localhost";var R={...je,...ys};function Ve(e,t){return B(e,new R.classes.URLSearchParams,{visitor:function(s,r,o,i){return R.isNode&&a.isBuffer(s)?(this.append(r,s.toString("base64")),!1):i.defaultVisitor.apply(this,arguments)},...t})}function Ur(e){return a.matchAll(/\w+|\[(\w*)]/g,e).map(t=>t[0]==="[]"?"":t[1]||t[0])}function Nr(e){let t={},s=Object.keys(e),r,o=s.length,i;for(r=0;r=s.length;return n=!n&&a.isArray(o)?o.length:n,m?(a.hasOwnProp(o,n)?o[n]=[o[n],r]:o[n]=r,!l):((!o[n]||!a.isObject(o[n]))&&(o[n]=[]),t(s,r,o[n],i)&&a.isArray(o[n])&&(o[n]=Nr(o[n])),!l)}if(a.isFormData(e)&&a.isFunction(e.entries)){let s={};return a.forEachEntry(e,(r,o)=>{t(Ur(r),o,s,0)}),s}return null}var xe=Br;function Gr(e,t,s){if(a.isString(e))try{return(t||JSON.parse)(e),a.trim(e)}catch(r){if(r.name!=="SyntaxError")throw r}return(s||JSON.stringify)(e)}var He={transitional:Ee,adapter:["xhr","http","fetch"],transformRequest:[function(t,s){let r=s.getContentType()||"",o=r.indexOf("application/json")>-1,i=a.isObject(t);if(i&&a.isHTMLForm(t)&&(t=new FormData(t)),a.isFormData(t))return o?JSON.stringify(xe(t)):t;if(a.isArrayBuffer(t)||a.isBuffer(t)||a.isStream(t)||a.isFile(t)||a.isBlob(t)||a.isReadableStream(t))return t;if(a.isArrayBufferView(t))return t.buffer;if(a.isURLSearchParams(t))return s.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),t.toString();let l;if(i){if(r.indexOf("application/x-www-form-urlencoded")>-1)return Ve(t,this.formSerializer).toString();if((l=a.isFileList(t))||r.indexOf("multipart/form-data")>-1){let m=this.env&&this.env.FormData;return B(l?{"files[]":t}:t,m&&new m,this.formSerializer)}}return i||o?(s.setContentType("application/json",!1),Gr(t)):t}],transformResponse:[function(t){let s=this.transitional||He.transitional,r=s&&s.forcedJSONParsing,o=this.responseType==="json";if(a.isResponse(t)||a.isReadableStream(t))return t;if(t&&a.isString(t)&&(r&&!this.responseType||o)){let n=!(s&&s.silentJSONParsing)&&o;try{return JSON.parse(t,this.parseReviver)}catch(l){if(n)throw l.name==="SyntaxError"?A.from(l,A.ERR_BAD_RESPONSE,this,null,this.response):l}}return t}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:R.classes.FormData,Blob:R.classes.Blob},validateStatus:function(t){return t>=200&&t<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};a.forEach(["delete","get","head","post","put","patch"],e=>{He.headers[e]={}});var z=He;var jr=a.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),Ps=e=>{let t={},s,r,o;return e&&e.split(` 2 | `).forEach(function(n){o=n.indexOf(":"),s=n.substring(0,o).trim().toLowerCase(),r=n.substring(o+1).trim(),!(!s||t[s]&&jr[s])&&(s==="set-cookie"?t[s]?t[s].push(r):t[s]=[r]:t[s]=t[s]?t[s]+", "+r:r)}),t};var Rs=Symbol("internals");function ne(e){return e&&String(e).trim().toLowerCase()}function Ce(e){return e===!1||e==null?e:a.isArray(e)?e.map(Ce):String(e)}function Vr(e){let t=Object.create(null),s=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g,r;for(;r=s.exec(e);)t[r[1]]=r[2];return t}var Hr=e=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim());function Me(e,t,s,r,o){if(a.isFunction(r))return r.call(this,t,s);if(o&&(t=s),!!a.isString(t)){if(a.isString(r))return t.indexOf(r)!==-1;if(a.isRegExp(r))return r.test(t)}}function Mr(e){return e.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(t,s,r)=>s.toUpperCase()+r)}function Wr(e,t){let s=a.toCamelCase(" "+t);["get","set","has"].forEach(r=>{Object.defineProperty(e,r+s,{value:function(o,i,n){return this[r].call(this,t,o,i,n)},configurable:!0})})}var J=class{constructor(t){t&&this.set(t)}set(t,s,r){let o=this;function i(l,m,p){let c=ne(m);if(!c)throw new Error("header name must be a non-empty string");let d=a.findKey(o,c);(!d||o[d]===void 0||p===!0||p===void 0&&o[d]!==!1)&&(o[d||m]=Ce(l))}let n=(l,m)=>a.forEach(l,(p,c)=>i(p,c,m));if(a.isPlainObject(t)||t instanceof this.constructor)n(t,s);else if(a.isString(t)&&(t=t.trim())&&!Hr(t))n(Ps(t),s);else if(a.isObject(t)&&a.isIterable(t)){let l={},m,p;for(let c of t){if(!a.isArray(c))throw TypeError("Object iterator must return a key-value pair");l[p=c[0]]=(m=l[p])?a.isArray(m)?[...m,c[1]]:[m,c[1]]:c[1]}n(l,s)}else t!=null&&i(s,t,r);return this}get(t,s){if(t=ne(t),t){let r=a.findKey(this,t);if(r){let o=this[r];if(!s)return o;if(s===!0)return Vr(o);if(a.isFunction(s))return s.call(this,o,r);if(a.isRegExp(s))return s.exec(o);throw new TypeError("parser must be boolean|regexp|function")}}}has(t,s){if(t=ne(t),t){let r=a.findKey(this,t);return!!(r&&this[r]!==void 0&&(!s||Me(this,this[r],r,s)))}return!1}delete(t,s){let r=this,o=!1;function i(n){if(n=ne(n),n){let l=a.findKey(r,n);l&&(!s||Me(r,r[l],l,s))&&(delete r[l],o=!0)}}return a.isArray(t)?t.forEach(i):i(t),o}clear(t){let s=Object.keys(this),r=s.length,o=!1;for(;r--;){let i=s[r];(!t||Me(this,this[i],i,t,!0))&&(delete this[i],o=!0)}return o}normalize(t){let s=this,r={};return a.forEach(this,(o,i)=>{let n=a.findKey(r,i);if(n){s[n]=Ce(o),delete s[i];return}let l=t?Mr(i):String(i).trim();l!==i&&delete s[i],s[l]=Ce(o),r[l]=!0}),this}concat(...t){return this.constructor.concat(this,...t)}toJSON(t){let s=Object.create(null);return a.forEach(this,(r,o)=>{r!=null&&r!==!1&&(s[o]=t&&a.isArray(r)?r.join(", "):r)}),s}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([t,s])=>t+": "+s).join(` 3 | `)}getSetCookie(){return this.get("set-cookie")||[]}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(t){return t instanceof this?t:new this(t)}static concat(t,...s){let r=new this(t);return s.forEach(o=>r.set(o)),r}static accessor(t){let r=(this[Rs]=this[Rs]={accessors:{}}).accessors,o=this.prototype;function i(n){let l=ne(n);r[l]||(Wr(o,n),r[l]=!0)}return a.isArray(t)?t.forEach(i):i(t),this}};J.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]);a.reduceDescriptors(J.prototype,({value:e},t)=>{let s=t[0].toUpperCase()+t.slice(1);return{get:()=>e,set(r){this[s]=r}}});a.freezeMethods(J);var C=J;function ie(e,t){let s=this||z,r=t||s,o=C.from(r.headers),i=r.data;return a.forEach(e,function(l){i=l.call(s,i,o.normalize(),t?t.status:void 0)}),o.normalize(),i}function ae(e){return!!(e&&e.__CANCEL__)}function bs(e,t,s){A.call(this,e??"canceled",A.ERR_CANCELED,t,s),this.name="CanceledError"}a.inherits(bs,A,{__CANCEL__:!0});var q=bs;function le(e,t,s){let r=s.config.validateStatus;!s.status||!r||r(s.status)?e(s):t(new A("Request failed with status code "+s.status,[A.ERR_BAD_REQUEST,A.ERR_BAD_RESPONSE][Math.floor(s.status/100)-4],s.config,s.request,s))}function We(e){let t=/^([-+\w]{1,25})(:?\/\/|:)/.exec(e);return t&&t[1]||""}function vr(e,t){e=e||10;let s=new Array(e),r=new Array(e),o=0,i=0,n;return t=t!==void 0?t:1e3,function(m){let p=Date.now(),c=r[i];n||(n=p),s[o]=m,r[o]=p;let d=i,P=0;for(;d!==o;)P+=s[d++],d=d%e;if(o=(o+1)%e,o===i&&(i=(i+1)%e),p-n{s=c,o=null,i&&(clearTimeout(i),i=null),e(...p)};return[(...p)=>{let c=Date.now(),d=c-s;d>=r?n(p,c):(o=p,i||(i=setTimeout(()=>{i=null,n(o)},r-d)))},()=>o&&n(o)]}var xs=Qr;var K=(e,t,s=3)=>{let r=0,o=Es(50,250);return xs(i=>{let n=i.loaded,l=i.lengthComputable?i.total:void 0,m=n-r,p=o(m),c=n<=l;r=n;let d={loaded:n,total:l,progress:l?n/l:void 0,bytes:m,rate:p||void 0,estimated:p&&l&&c?(l-n)/p:void 0,event:i,lengthComputable:l!=null,[t?"download":"upload"]:!0};e(d)},s)},ve=(e,t)=>{let s=e!=null;return[r=>t[0]({lengthComputable:s,total:e,loaded:r}),t[1]]},Qe=e=>(...t)=>a.asap(()=>e(...t));var Cs=R.hasStandardBrowserEnv?((e,t)=>s=>(s=new URL(s,R.origin),e.protocol===s.protocol&&e.host===s.host&&(t||e.port===s.port)))(new URL(R.origin),R.navigator&&/(msie|trident)/i.test(R.navigator.userAgent)):()=>!0;var Fs=R.hasStandardBrowserEnv?{write(e,t,s,r,o,i){let n=[e+"="+encodeURIComponent(t)];a.isNumber(s)&&n.push("expires="+new Date(s).toGMTString()),a.isString(r)&&n.push("path="+r),a.isString(o)&&n.push("domain="+o),i===!0&&n.push("secure"),document.cookie=n.join("; ")},read(e){let t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove(e){this.write(e,"",Date.now()-864e5)}}:{write(){},read(){return null},remove(){}};function ze(e){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(e)}function Je(e,t){return t?e.replace(/\/?\/$/,"")+"/"+t.replace(/^\/+/,""):e}function ce(e,t,s){let r=!ze(t);return e&&(r||s==!1)?Je(e,t):t}var ws=e=>e instanceof C?{...e}:e;function _(e,t){t=t||{};let s={};function r(p,c,d,P){return a.isPlainObject(p)&&a.isPlainObject(c)?a.merge.call({caseless:P},p,c):a.isPlainObject(c)?a.merge({},c):a.isArray(c)?c.slice():c}function o(p,c,d,P){if(a.isUndefined(c)){if(!a.isUndefined(p))return r(void 0,p,d,P)}else return r(p,c,d,P)}function i(p,c){if(!a.isUndefined(c))return r(void 0,c)}function n(p,c){if(a.isUndefined(c)){if(!a.isUndefined(p))return r(void 0,p)}else return r(void 0,c)}function l(p,c,d){if(d in t)return r(p,c);if(d in e)return r(void 0,p)}let m={url:i,method:i,data:i,baseURL:n,transformRequest:n,transformResponse:n,paramsSerializer:n,timeout:n,timeoutMessage:n,withCredentials:n,withXSRFToken:n,adapter:n,responseType:n,xsrfCookieName:n,xsrfHeaderName:n,onUploadProgress:n,onDownloadProgress:n,decompress:n,maxContentLength:n,maxBodyLength:n,beforeRedirect:n,transport:n,httpAgent:n,httpsAgent:n,cancelToken:n,socketPath:n,responseEncoding:n,validateStatus:l,headers:(p,c,d)=>o(ws(p),ws(c),d,!0)};return a.forEach(Object.keys({...e,...t}),function(c){let d=m[c]||o,P=d(e[c],t[c],c);a.isUndefined(P)&&d!==l||(s[c]=P)}),s}var Fe=e=>{let t=_({},e),{data:s,withXSRFToken:r,xsrfHeaderName:o,xsrfCookieName:i,headers:n,auth:l}=t;if(t.headers=n=C.from(n),t.url=oe(ce(t.baseURL,t.url,t.allowAbsoluteUrls),e.params,e.paramsSerializer),l&&n.set("Authorization","Basic "+btoa((l.username||"")+":"+(l.password?unescape(encodeURIComponent(l.password)):""))),a.isFormData(s)){if(R.hasStandardBrowserEnv||R.hasStandardBrowserWebWorkerEnv)n.setContentType(void 0);else if(a.isFunction(s.getHeaders)){let m=s.getHeaders(),p=["content-type","content-length"];Object.entries(m).forEach(([c,d])=>{p.includes(c.toLowerCase())&&n.set(c,d)})}}if(R.hasStandardBrowserEnv&&(r&&a.isFunction(r)&&(r=r(t)),r||r!==!1&&Cs(t.url))){let m=o&&i&&Fs.read(i);m&&n.set(o,m)}return t};var zr=typeof XMLHttpRequest<"u",Ss=zr&&function(e){return new Promise(function(s,r){let o=Fe(e),i=o.data,n=C.from(o.headers).normalize(),{responseType:l,onUploadProgress:m,onDownloadProgress:p}=o,c,d,P,w,u;function h(){w&&w(),u&&u(),o.cancelToken&&o.cancelToken.unsubscribe(c),o.signal&&o.signal.removeEventListener("abort",c)}let f=new XMLHttpRequest;f.open(o.method.toUpperCase(),o.url,!0),f.timeout=o.timeout;function E(){if(!f)return;let F=C.from("getAllResponseHeaders"in f&&f.getAllResponseHeaders()),k={data:!l||l==="text"||l==="json"?f.responseText:f.response,status:f.status,statusText:f.statusText,headers:F,config:e,request:f};le(function(I){s(I),h()},function(I){r(I),h()},k),f=null}"onloadend"in f?f.onloadend=E:f.onreadystatechange=function(){!f||f.readyState!==4||f.status===0&&!(f.responseURL&&f.responseURL.indexOf("file:")===0)||setTimeout(E)},f.onabort=function(){f&&(r(new A("Request aborted",A.ECONNABORTED,e,f)),f=null)},f.onerror=function(S){let k=S&&S.message?S.message:"Network Error",G=new A(k,A.ERR_NETWORK,e,f);G.event=S||null,r(G),f=null},f.ontimeout=function(){let S=o.timeout?"timeout of "+o.timeout+"ms exceeded":"timeout exceeded",k=o.transitional||Ee;o.timeoutErrorMessage&&(S=o.timeoutErrorMessage),r(new A(S,k.clarifyTimeoutError?A.ETIMEDOUT:A.ECONNABORTED,e,f)),f=null},i===void 0&&n.setContentType(null),"setRequestHeader"in f&&a.forEach(n.toJSON(),function(S,k){f.setRequestHeader(k,S)}),a.isUndefined(o.withCredentials)||(f.withCredentials=!!o.withCredentials),l&&l!=="json"&&(f.responseType=o.responseType),p&&([P,u]=K(p,!0),f.addEventListener("progress",P)),m&&f.upload&&([d,w]=K(m),f.upload.addEventListener("progress",d),f.upload.addEventListener("loadend",w)),(o.cancelToken||o.signal)&&(c=F=>{f&&(r(!F||F.type?new q(null,e,f):F),f.abort(),f=null)},o.cancelToken&&o.cancelToken.subscribe(c),o.signal&&(o.signal.aborted?c():o.signal.addEventListener("abort",c)));let O=We(o.url);if(O&&R.protocols.indexOf(O)===-1){r(new A("Unsupported protocol "+O+":",A.ERR_BAD_REQUEST,e));return}f.send(i||null)})};var Jr=(e,t)=>{let{length:s}=e=e?e.filter(Boolean):[];if(t||s){let r=new AbortController,o,i=function(p){if(!o){o=!0,l();let c=p instanceof Error?p:this.reason;r.abort(c instanceof A?c:new q(c instanceof Error?c.message:c))}},n=t&&setTimeout(()=>{n=null,i(new A(`timeout ${t} of ms exceeded`,A.ETIMEDOUT))},t),l=()=>{e&&(n&&clearTimeout(n),n=null,e.forEach(p=>{p.unsubscribe?p.unsubscribe(i):p.removeEventListener("abort",i)}),e=null)};e.forEach(p=>p.addEventListener("abort",i));let{signal:m}=r;return m.unsubscribe=()=>a.asap(l),m}},Ts=Jr;var Kr=function*(e,t){let s=e.byteLength;if(!t||s{let o=Xr(e,t),i=0,n,l=m=>{n||(n=!0,r&&r(m))};return new ReadableStream({async pull(m){try{let{done:p,value:c}=await o.next();if(p){l(),m.close();return}let d=c.byteLength;if(s){let P=i+=d;s(P)}m.enqueue(new Uint8Array(c))}catch(p){throw l(p),p}},cancel(m){return l(m),o.return()}},{highWaterMark:2})};var Os=64*1024,{isFunction:we}=a,Yr=(({Request:e,Response:t})=>({Request:e,Response:t}))(a.global),{ReadableStream:Is,TextEncoder:ks}=a.global,$s=(e,...t)=>{try{return!!e(...t)}catch{return!1}},eo=e=>{e=a.merge.call({skipUndefined:!0},Yr,e);let{fetch:t,Request:s,Response:r}=e,o=t?we(t):typeof fetch=="function",i=we(s),n=we(r);if(!o)return!1;let l=o&&we(Is),m=o&&(typeof ks=="function"?(u=>h=>u.encode(h))(new ks):async u=>new Uint8Array(await new s(u).arrayBuffer())),p=i&&l&&$s(()=>{let u=!1,h=new s(R.origin,{body:new Is,method:"POST",get duplex(){return u=!0,"half"}}).headers.has("Content-Type");return u&&!h}),c=n&&l&&$s(()=>a.isReadableStream(new r("").body)),d={stream:c&&(u=>u.body)};o&&["text","arrayBuffer","blob","formData","stream"].forEach(u=>{!d[u]&&(d[u]=(h,f)=>{let E=h&&h[u];if(E)return E.call(h);throw new A(`Response type '${u}' is not supported`,A.ERR_NOT_SUPPORT,f)})});let P=async u=>{if(u==null)return 0;if(a.isBlob(u))return u.size;if(a.isSpecCompliantForm(u))return(await new s(R.origin,{method:"POST",body:u}).arrayBuffer()).byteLength;if(a.isArrayBufferView(u)||a.isArrayBuffer(u))return u.byteLength;if(a.isURLSearchParams(u)&&(u=u+""),a.isString(u))return(await m(u)).byteLength},w=async(u,h)=>{let f=a.toFiniteNumber(u.getContentLength());return f??P(h)};return async u=>{let{url:h,method:f,data:E,signal:O,cancelToken:F,timeout:S,onDownloadProgress:k,onUploadProgress:G,responseType:I,headers:ke,withCredentials:fe="same-origin",fetchOptions:nt}=Fe(u),it=t||fetch;I=I?(I+"").toLowerCase():"text";let de=Ts([O,F&&F.toAbortSignal()],S),Z=null,j=de&&de.unsubscribe&&(()=>{de.unsubscribe()}),at;try{if(G&&p&&f!=="get"&&f!=="head"&&(at=await w(ke,E))!==0){let N=new s(h,{method:"POST",body:E,duplex:"half"}),M;if(a.isFormData(E)&&(M=N.headers.get("content-type"))&&ke.setContentType(M),N.body){let[$e,he]=ve(at,K(Qe(G)));E=Ke(N.body,Os,$e,he)}}a.isString(fe)||(fe=fe?"include":"omit");let L=i&&"credentials"in s.prototype,lt={...nt,signal:de,method:f.toUpperCase(),headers:ke.normalize().toJSON(),body:E,duplex:"half",credentials:L?fe:void 0};Z=i&&new s(h,lt);let U=await(i?it(Z,nt):it(h,lt)),ct=c&&(I==="stream"||I==="response");if(c&&(k||ct&&j)){let N={};["status","statusText","headers"].forEach(ut=>{N[ut]=U[ut]});let M=a.toFiniteNumber(U.headers.get("content-length")),[$e,he]=k&&ve(M,K(Qe(k),!0))||[];U=new r(Ke(U.body,Os,$e,()=>{he&&he(),j&&j()}),N)}I=I||"text";let Ns=await d[a.findKey(d,I)||"text"](U,u);return!ct&&j&&j(),await new Promise((N,M)=>{le(N,M,{data:Ns,headers:C.from(U.headers),status:U.status,statusText:U.statusText,config:u,request:Z})})}catch(L){throw j&&j(),L&&L.name==="TypeError"&&/Load failed|fetch/i.test(L.message)?Object.assign(new A("Network Error",A.ERR_NETWORK,u,Z),{cause:L.cause||L}):A.from(L,L&&L.code,u,Z)}}},to=new Map,Xe=e=>{let t=e?e.env:{},{fetch:s,Request:r,Response:o}=t,i=[r,o,s],n=i.length,l=n,m,p,c=to;for(;l--;)m=i[l],p=c.get(m),p===void 0&&c.set(m,p=l?new Map:eo(t)),c=p;return p},Pi=Xe();var Ze={http:Re,xhr:Ss,fetch:{get:Xe}};a.forEach(Ze,(e,t)=>{if(e){try{Object.defineProperty(e,"name",{value:t})}catch{}Object.defineProperty(e,"adapterName",{value:t})}});var _s=e=>`- ${e}`,ro=e=>a.isFunction(e)||e===null||e===!1,Se={getAdapter:(e,t)=>{e=a.isArray(e)?e:[e];let{length:s}=e,r,o,i={};for(let n=0;n`adapter ${m} `+(p===!1?"is not supported by the environment":"is not available in the build")),l=s?n.length>1?`since : 4 | `+n.map(_s).join(` 5 | `):" "+_s(n[0]):"as no adapter specified";throw new A("There is no suitable adapter to dispatch the request "+l,"ERR_NOT_SUPPORT")}return o},adapters:Ze};function Ye(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new q(null,e)}function Te(e){return Ye(e),e.headers=C.from(e.headers),e.data=ie.call(e,e.transformRequest),["post","put","patch"].indexOf(e.method)!==-1&&e.headers.setContentType("application/x-www-form-urlencoded",!1),Se.getAdapter(e.adapter||z.adapter,e)(e).then(function(r){return Ye(e),r.data=ie.call(e,e.transformResponse,r),r.headers=C.from(r.headers),r},function(r){return ae(r)||(Ye(e),r&&r.response&&(r.response.data=ie.call(e,e.transformResponse,r.response),r.response.headers=C.from(r.response.headers))),Promise.reject(r)})}var Oe="1.12.2";var Ie={};["object","boolean","number","function","string","symbol"].forEach((e,t)=>{Ie[e]=function(r){return typeof r===e||"a"+(t<1?"n ":" ")+e}});var Ls={};Ie.transitional=function(t,s,r){function o(i,n){return"[Axios v"+Oe+"] Transitional option '"+i+"'"+n+(r?". "+r:"")}return(i,n,l)=>{if(t===!1)throw new A(o(n," has been removed"+(s?" in "+s:"")),A.ERR_DEPRECATED);return s&&!Ls[n]&&(Ls[n]=!0,console.warn(o(n," has been deprecated since v"+s+" and will be removed in the near future"))),t?t(i,n,l):!0}};Ie.spelling=function(t){return(s,r)=>(console.warn(`${r} is likely a misspelling of ${t}`),!0)};function oo(e,t,s){if(typeof e!="object")throw new A("options must be an object",A.ERR_BAD_OPTION_VALUE);let r=Object.keys(e),o=r.length;for(;o-- >0;){let i=r[o],n=t[i];if(n){let l=e[i],m=l===void 0||n(l,i,e);if(m!==!0)throw new A("option "+i+" must be "+m,A.ERR_BAD_OPTION_VALUE);continue}if(s!==!0)throw new A("Unknown option "+i,A.ERR_BAD_OPTION)}}var ue={assertOptions:oo,validators:Ie};var D=ue.validators,X=class{constructor(t){this.defaults=t||{},this.interceptors={request:new Ne,response:new Ne}}async request(t,s){try{return await this._request(t,s)}catch(r){if(r instanceof Error){let o={};Error.captureStackTrace?Error.captureStackTrace(o):o=new Error;let i=o.stack?o.stack.replace(/^.+\n/,""):"";try{r.stack?i&&!String(r.stack).endsWith(i.replace(/^.+\n.+\n/,""))&&(r.stack+=` 6 | `+i):r.stack=i}catch{}}throw r}}_request(t,s){typeof t=="string"?(s=s||{},s.url=t):s=t||{},s=_(this.defaults,s);let{transitional:r,paramsSerializer:o,headers:i}=s;r!==void 0&&ue.assertOptions(r,{silentJSONParsing:D.transitional(D.boolean),forcedJSONParsing:D.transitional(D.boolean),clarifyTimeoutError:D.transitional(D.boolean)},!1),o!=null&&(a.isFunction(o)?s.paramsSerializer={serialize:o}:ue.assertOptions(o,{encode:D.function,serialize:D.function},!0)),s.allowAbsoluteUrls!==void 0||(this.defaults.allowAbsoluteUrls!==void 0?s.allowAbsoluteUrls=this.defaults.allowAbsoluteUrls:s.allowAbsoluteUrls=!0),ue.assertOptions(s,{baseUrl:D.spelling("baseURL"),withXsrfToken:D.spelling("withXSRFToken")},!0),s.method=(s.method||this.defaults.method||"get").toLowerCase();let n=i&&a.merge(i.common,i[s.method]);i&&a.forEach(["delete","get","head","post","put","patch","common"],u=>{delete i[u]}),s.headers=C.concat(n,i);let l=[],m=!0;this.interceptors.request.forEach(function(h){typeof h.runWhen=="function"&&h.runWhen(s)===!1||(m=m&&h.synchronous,l.unshift(h.fulfilled,h.rejected))});let p=[];this.interceptors.response.forEach(function(h){p.push(h.fulfilled,h.rejected)});let c,d=0,P;if(!m){let u=[Te.bind(this),void 0];for(u.unshift(...l),u.push(...p),P=u.length,c=Promise.resolve(s);d{if(!r._listeners)return;let i=r._listeners.length;for(;i-- >0;)r._listeners[i](o);r._listeners=null}),this.promise.then=o=>{let i,n=new Promise(l=>{r.subscribe(l),i=l}).then(o);return n.cancel=function(){r.unsubscribe(i)},n},t(function(i,n,l){r.reason||(r.reason=new q(i,n,l),s(r.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(t){if(this.reason){t(this.reason);return}this._listeners?this._listeners.push(t):this._listeners=[t]}unsubscribe(t){if(!this._listeners)return;let s=this._listeners.indexOf(t);s!==-1&&this._listeners.splice(s,1)}toAbortSignal(){let t=new AbortController,s=r=>{t.abort(r)};return this.subscribe(s),t.signal.unsubscribe=()=>this.unsubscribe(s),t.signal}static source(){let t;return{token:new e(function(o){t=o}),cancel:t}}},qs=et;function tt(e){return function(s){return e.apply(null,s)}}function st(e){return a.isObject(e)&&e.isAxiosError===!0}var rt={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511};Object.entries(rt).forEach(([e,t])=>{rt[t]=e});var Ds=rt;function Us(e){let t=new pe(e),s=ee(pe.prototype.request,t);return a.extend(s,pe.prototype,t,{allOwnKeys:!0}),a.extend(s,t,null,{allOwnKeys:!0}),s.create=function(o){return Us(_(e,o))},s}var b=Us(z);b.Axios=pe;b.CanceledError=q;b.CancelToken=qs;b.isCancel=ae;b.VERSION=Oe;b.toFormData=B;b.AxiosError=A;b.Cancel=b.CanceledError;b.all=function(t){return Promise.all(t)};b.spread=tt;b.isAxiosError=st;b.mergeConfig=_;b.AxiosHeaders=C;b.formToJSON=e=>xe(a.isHTMLForm(e)?new FormData(e):e);b.getAdapter=Se.getAdapter;b.HttpStatusCode=Ds;b.default=b;var me=b;var{Axios:Aa,AxiosError:ga,CanceledError:ya,isCancel:Pa,CancelToken:Ra,VERSION:ba,all:Ea,Cancel:xa,isAxiosError:Ca,spread:Fa,toFormData:wa,AxiosHeaders:Sa,HttpStatusCode:Ta,formToJSON:Oa,getAdapter:Ia,mergeConfig:ka}=me;var ot=class{constructor(t){this.appendHeaders=t=>{this.personalAccessToken&&(t["X-Figma-Token"]=this.personalAccessToken),this.oAuthToken&&(t.Authorization=`Bearer ${this.oAuthToken}`)};this.request=(t,s)=>{let r={};this.appendHeaders(r);let o={url:t,...s,headers:r};return me(o).then(i=>i.data).catch(i=>{throw new Y(i)})};this.getFile=mt;this.getFileNodes=ft;this.getImages=dt;this.getImageFills=ht;this.getComments=At;this.postComment=gt;this.deleteComment=yt;this.getCommentReactions=Pt;this.postCommentReaction=Rt;this.deleteCommentReactions=bt;this.getUserMe=Et;this.getFileVersions=xt;this.getTeamProjects=Ct;this.getProjectFiles=Ft;this.getTeamComponents=wt;this.getFileComponents=St;this.getComponent=Tt;this.getTeamComponentSets=Ot;this.getFileComponentSets=It;this.getComponentSet=kt;this.getTeamStyles=$t;this.getFileStyles=_t;this.getStyle=Lt;this.getWebhook=qt;this.postWebhook=Dt;this.putWebhook=Ut;this.deleteWebhook=Nt;this.getTeamWebhooks=Bt;this.getWebhookRequests=Gt;this.getLocalVariables=jt;this.getPublishedVariables=Vt;this.postVariables=Ht;this.getDevResources=Mt;this.postDevResources=Wt;this.putDevResources=vt;this.deleteDevResources=Qt;this.getLibraryAnalyticsComponentActions=zt;this.getLibraryAnalyticsComponentUsages=Jt;this.getLibraryAnalyticsStyleActions=Kt;this.getLibraryAnalyticsStyleUsages=Xt;this.getLibraryAnalyticsVariableActions=Zt;this.getLibraryAnalyticsVariableUsages=Yt;"personalAccessToken"in t&&(this.personalAccessToken=t.personalAccessToken),"oAuthToken"in t&&(this.oAuthToken=t.oAuthToken)}};function no(e,t,s,r,o){return`https://www.figma.com/oauth?${x({client_id:e,redirect_uri:t,scope:s,state:r,response_type:o})}`}async function io(e,t,s,r,o){let i={Authorization:`Basic ${Buffer.from(`${e}:${t}`).toString("base64")}`},l=`https://api.figma.com/v1/oauth/token?${x({redirect_uri:s,code:r,grant_type:o})}`,m=await me.post(l,null,{headers:i});if(m.status!==200)throw new Y(m);return m.data}return Hs(ao);})(); 7 | --------------------------------------------------------------------------------