Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions integrations/figma/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Figma Integration for CORE

Connect your Figma workspace to CORE to track file updates, comments, version history, and design activity.

## Features

- OAuth2 Authorization Code flow (no API key required)
- Webhook-driven activity feed for real-time events
- MCP tool support for querying Figma data programmatically
- Scheduled sync to keep credentials validated

## OAuth2 Scopes

| Scope | Purpose |
|---|---|
| `file_content:read` | Read file document structure and metadata |
| `file_comments:read` | Read comments on files |
| `file_comments:write` | Post comments on files |
| `file_dev_resources:read` | Read dev resources attached to files |
| `webhooks:write` | Register and manage webhooks |

## MCP Tools

| Tool | Description |
|---|---|
| `figma_get_team_projects` | List all projects in a Figma team |
| `figma_get_project_files` | List all files in a project |
| `figma_get_file` | Fetch full document tree for a file |
| `figma_get_file_comments` | List all comments on a file |
| `figma_get_file_versions` | List version history of a file |
| `figma_create_webhook` | Register a webhook for real-time events |

## Setup

### Environment variables

```bash
FIGMA_CLIENT_ID=<your Figma OAuth2 client ID>
FIGMA_CLIENT_SECRET=<your Figma OAuth2 client secret>
DATABASE_URL=<PostgreSQL connection string>
```

### Register the integration

```bash
bun run scripts/register.ts
```

### Build

```bash
bun run build
```

## Webhook Events

The integration handles the following Figma webhook event types via `PROCESS`:

- `FILE_UPDATE` - A file was edited
- `FILE_VERSION_UPDATE` - A named version was saved
- `FILE_COMMENT` - A comment was posted
- `FILE_DELETE` - A file was deleted
- `LIBRARY_PUBLISH` - A library was published

> **TODO**: Register the webhook endpoint URL in `scripts/register.ts` once the CORE webhook receiver URL is known.
65 changes: 65 additions & 0 deletions integrations/figma/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"name": "@core/figma",
"version": "0.1.0",
"description": "Figma extension for CORE",
"main": "./bin/index.js",
"module": "./bin/index.mjs",
"type": "module",
"files": [
"figma",
"bin"
],
"bin": {
"figma": "./bin/index.js"
},
"scripts": {
"build": "rimraf dist && bun build src/index.ts --outfile dist/index.js --target node --minify",
"lint": "eslint --ext js,ts,tsx backend/ frontend/ --fix",
"prettier": "prettier --config .prettierrc --write ."
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/preset-typescript": "^7.26.0",
"@types/node": "^18.0.20",
"eslint": "^9.24.0",
"eslint-config-prettier": "^10.1.2",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jest": "^27.9.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-unused-imports": "^2.0.0",
"prettier": "^3.4.2",
"rimraf": "^3.0.2",
"tslib": "^2.8.1",
"typescript": "^4.7.2",
"tsup": "^8.0.1",
"ncc": "0.3.6"
},
"publishConfig": {
"access": "public"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"dependencies": {
"axios": "^1.7.9",
"commander": "^12.0.0",
"openai": "^4.0.0",
"react-query": "^3.39.3",
"@redplanethq/sdk": "0.1.10",
"zod": "^3.25.4",
"zod-to-json-schema": "^3.25.1"
}
}
92 changes: 92 additions & 0 deletions integrations/figma/scripts/register.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import pg from 'pg';

const { Client } = pg;

async function main() {
const connectionString = process.env.DATABASE_URL;
if (!connectionString) {
console.error('DATABASE_URL environment variable is required');
process.exit(1);
}

const clientId = process.env.FIGMA_CLIENT_ID;
const clientSecret = process.env.FIGMA_CLIENT_SECRET;

if (!clientId || !clientSecret) {
console.error('FIGMA_CLIENT_ID and FIGMA_CLIENT_SECRET environment variables are required');
process.exit(1);
}

const client = new Client({ connectionString });

const spec = {
name: 'Figma',
key: 'figma',
description:
'Connect your Figma workspace to track file updates, comments, version history, and design activity in CORE.',
icon: 'figma',
schedule: {
frequency: '*/15 * * * *',
},
mcp: {
type: 'cli',
},
auth: {
OAuth2: {
token_url: 'https://www.figma.com/api/oauth/token',
authorization_url: 'https://www.figma.com/oauth',
scopes: [
'file_content:read',
'file_comments:read',
'file_comments:write',
'file_dev_resources:read',
'webhooks:write',
],
scope_separator: ',',
fields: [
{
name: 'access_token',
label: 'Access Token',
placeholder: '',
description: 'OAuth2 access token issued by Figma after authorization.',
},
],
},
},
};

try {
await client.connect();

await client.query(
`
INSERT INTO core."IntegrationDefinitionV2" ("id", "name", "slug", "description", "icon", "spec", "config", "version", "url", "updatedAt", "createdAt")
VALUES (gen_random_uuid(), 'Figma', 'figma', $4, 'figma', $1, $2, '0.1.0', $3, NOW(), NOW())
ON CONFLICT (name) DO UPDATE SET
"slug" = EXCLUDED."slug",
"description" = EXCLUDED."description",
"icon" = EXCLUDED."icon",
"spec" = EXCLUDED."spec",
"config" = EXCLUDED."config",
"version" = EXCLUDED."version",
"url" = EXCLUDED."url",
"updatedAt" = NOW()
RETURNING *;
`,
[
JSON.stringify(spec),
JSON.stringify({ clientId, clientSecret }),
'../../integrations/figma/bin/index.js',
spec.description,
],
);

console.log('Figma integration registered successfully in the database.');
} catch (error) {
console.error('Error registering Figma integration:', error);
} finally {
await client.end();
}
}

main().catch(console.error);
43 changes: 43 additions & 0 deletions integrations/figma/src/account-create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import axios from 'axios';

/**
* Called after the OAuth2 Authorization Code flow completes.
* Fetches the authenticated Figma user and returns account + config records.
*/
export async function integrationCreate(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any,
) {
const { oauthResponse } = data;

const integrationConfiguration = {
access_token: oauthResponse.access_token,
refresh_token: oauthResponse.refresh_token,
};

// Fetch the authenticated user from the Figma REST API.
const userResponse = await axios.get('https://api.figma.com/v1/me', {
headers: {
Authorization: `Bearer ${integrationConfiguration.access_token}`,
},
});

const user = userResponse.data;

return [
{
type: 'account',
data: {
settings: {
handle: user.handle,
email: user.email,
},
accountId: user.id.toString(),
config: {
...integrationConfiguration,
mcp: { tokens: { access_token: integrationConfiguration.access_token } },
},
},
},
];
}
91 changes: 91 additions & 0 deletions integrations/figma/src/create-activity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
interface FigmaActivityCreateParams {
text: string;
sourceURL: string;
}

/**
* Creates a standardised activity message from Figma webhook event data.
*/
export function createActivityMessage(params: FigmaActivityCreateParams) {
return {
type: 'activity',
data: {
text: params.text,
sourceURL: params.sourceURL,
},
};
}

/**
* Processes a raw Figma webhook event payload and returns zero or more
* activity messages to be persisted in CORE.
*
* TODO: Expand this handler to cover all Figma webhook event types:
* - FILE_UPDATE
* - FILE_VERSION_UPDATE
* - FILE_COMMENT
* - FILE_DELETE
* - LIBRARY_PUBLISH
*/
export function createActivityEvent(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
eventData: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_config: any,
): ReturnType<typeof createActivityMessage>[] {
if (!eventData || !eventData.event_type) {
return [];
}

const { event_type, file_name, file_key, triggered_by } = eventData;
const actor: string = triggered_by?.handle ?? 'Someone';
const fileURL = file_key ? `https://www.figma.com/file/${file_key}` : '';

switch (event_type) {
case 'FILE_UPDATE':
return [
createActivityMessage({
text: `${actor} updated Figma file "${file_name ?? file_key}"`,
sourceURL: fileURL,
}),
];

case 'FILE_VERSION_UPDATE':
return [
createActivityMessage({
text: `${actor} saved a new version of Figma file "${file_name ?? file_key}"`,
sourceURL: fileURL,
}),
];

case 'FILE_COMMENT': {
const comment: string = eventData.comment?.[0]?.text ?? '';
return [
createActivityMessage({
text: `${actor} commented on Figma file "${file_name ?? file_key}": ${comment}`,
sourceURL: fileURL,
}),
];
}

case 'FILE_DELETE':
return [
createActivityMessage({
text: `${actor} deleted Figma file "${file_name ?? file_key}"`,
sourceURL: fileURL,
}),
];

case 'LIBRARY_PUBLISH':
return [
createActivityMessage({
text: `${actor} published a library update for "${file_name ?? file_key}"`,
sourceURL: fileURL,
}),
];

default:
// TODO: Handle additional event types as Figma expands its webhook API.
return [];
}
}
Loading