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
2 changes: 2 additions & 0 deletions apps/webapp/app/components/icon-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { Resend } from "./icons/resend";
import { Ynab } from "./icons/ynab";
import { Jira } from "./icons/jira";
import { Confluence } from "./icons/confluence";
import { Intercom } from "./icons/intercom";
import { BacklogLine } from "./icons/backlog";
import { TodoLine } from "./icons/todo";
import { InProgressLine } from "./icons/in-progress";
Expand Down Expand Up @@ -82,6 +83,7 @@ export const ICON_MAPPING = {
ynab: Ynab,
jira: Jira,
confluence: Confluence,
intercom: Intercom,
cli: Code,
"core-extension": Chromium,
task: Task,
Expand Down
36 changes: 36 additions & 0 deletions apps/webapp/app/components/icons/intercom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { IconProps } from './types';

export function Intercom({ size = 18, className }: IconProps) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
className={className}
viewBox="0 0 32 32"
fill="none"
>
<rect width="32" height="32" rx="6" fill="#1F8DED" />
<path
d="M24 20.308C24 21.243 23.243 22 22.308 22H9.692C8.757 22 8 21.243 8 20.308V11.692C8 10.757 8.757 10 9.692 10H22.308C23.243 10 24 10.757 24 11.692V20.308Z"
fill="white"
/>
<path
d="M16 18C14.343 18 13 16.657 13 15C13 13.343 14.343 12 16 12C17.657 12 19 13.343 19 15C19 16.657 17.657 18 16 18Z"
fill="#1F8DED"
/>
<path
d="M13 22L11 25"
stroke="white"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M19 22L21 25"
stroke="white"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
);
}
4 changes: 4 additions & 0 deletions integrations/intercom/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
bin/
dist/
*.js.map
7 changes: 7 additions & 0 deletions integrations/intercom/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"semi": true,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2
}
47 changes: 47 additions & 0 deletions integrations/intercom/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Intercom Integration for CORE

Connects your Intercom workspace to CORE, syncing conversations, contacts, and events as activities.

## Features

- **Conversations**: Syncs new and updated support conversations with contact info, state, assignee, and message preview.
- **Contacts**: Tracks newly created or updated contacts (users and leads).
- **Events**: Captures custom events triggered by contacts.

## Authentication

Uses **OAuth2**. Users authorize via Intercom's OAuth flow:

- Authorization URL: `https://app.intercom.com/oauth`
- Token URL: `https://api.intercom.io/auth/eagle/token`
- Scopes: `read_users read_conversations`

## Setup

### Prerequisites

- An Intercom account with admin access.
- An OAuth app registered in the [Intercom Developer Hub](https://developers.intercom.com/).

### Build

```bash
cd integrations/intercom
pnpm install
pnpm build
```

### Register to Database

```bash
DATABASE_URL=<your-database-url> npx ts-node scripts/register.ts
```

## Sync Schedule

Runs every 15 minutes (`*/15 * * * *`).

## API Reference

- [Intercom REST API](https://developers.intercom.com/docs/references/rest-api/overview/)
- [OAuth Setup](https://developers.intercom.com/docs/build-an-integration/getting-started/)
98 changes: 98 additions & 0 deletions integrations/intercom/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
const eslint = require('@eslint/js');
const tseslint = require('typescript-eslint');
const reactPlugin = require('eslint-plugin-react');
const jestPlugin = require('eslint-plugin-jest');
const importPlugin = require('eslint-plugin-import');
const prettierPlugin = require('eslint-plugin-prettier');
const unusedImportsPlugin = require('eslint-plugin-unused-imports');
const jsxA11yPlugin = require('eslint-plugin-jsx-a11y');

module.exports = [
eslint.configs.recommended,
...tseslint.configs.recommended,
{
files: ['**/*.{js,jsx,ts,tsx}'],
plugins: {
react: reactPlugin,
jest: jestPlugin,
import: importPlugin,
prettier: prettierPlugin,
'unused-imports': unusedImportsPlugin,
'jsx-a11y': jsxA11yPlugin,
},
languageOptions: {
ecmaVersion: 2020,
sourceType: 'module',
parser: tseslint.parser,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
rules: {
'jsx-a11y/label-has-associated-control': 'error',
curly: 'warn',
'dot-location': 'warn',
eqeqeq: 'error',
'prettier/prettier': 'warn',
'unused-imports/no-unused-imports': 'warn',
'no-else-return': 'warn',
'no-lonely-if': 'warn',
'no-inner-declarations': 'off',
'no-unused-vars': 'off',
'no-useless-computed-key': 'warn',
'no-useless-return': 'warn',
'no-var': 'warn',
'object-shorthand': ['warn', 'always'],
'prefer-arrow-callback': 'warn',
'prefer-const': 'warn',
'prefer-destructuring': ['warn', { AssignmentExpression: { array: true } }],
'prefer-object-spread': 'warn',
'prefer-template': 'warn',
'spaced-comment': ['warn', 'always', { markers: ['/'] }],
yoda: 'warn',
'import/order': [
'warn',
{
'newlines-between': 'always',
groups: ['type', 'builtin', 'external', 'internal', ['parent', 'sibling'], 'index'],
pathGroupsExcludedImportTypes: ['builtin'],
pathGroups: [],
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
},
],
'@typescript-eslint/array-type': ['warn', { default: 'array-simple' }],
'@typescript-eslint/ban-ts-comment': [
'warn',
{
'ts-expect-error': 'allow-with-description',
},
],
'@typescript-eslint/consistent-indexed-object-style': ['warn', 'record'],
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
'@typescript-eslint/no-unused-vars': 'warn',
'react/function-component-definition': [
'warn',
{
namedComponents: 'arrow-function',
unnamedComponents: 'arrow-function',
},
],
'react/jsx-boolean-value': 'warn',
'react/jsx-curly-brace-presence': 'warn',
'react/jsx-fragments': 'warn',
'react/jsx-no-useless-fragment': ['warn', { allowExpressions: true }],
'react/self-closing-comp': 'warn',
},
},
{
files: ['scripts/**/*'],
rules: {
'@typescript-eslint/no-var-requires': 'off',
},
},
];
45 changes: 45 additions & 0 deletions integrations/intercom/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "@core/intercom",
"version": "0.1.0",
"description": "Intercom integration for CORE - sync conversations, contacts, and events",
"main": "./bin/index.cjs",
"type": "module",
"files": [
"intercom",
"bin"
],
"bin": {
"intercom": "./bin/index.cjs"
},
"scripts": {
"build": "tsup",
"lint": "eslint --ext js,ts,tsx src/ --fix",
"prettier": "prettier --config .prettierrc --write ."
},
"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"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"axios": "^1.7.9",
"commander": "^12.0.0",
"@redplanethq/sdk": "0.1.14",
"zod": "^3.22.4",
"zod-to-json-schema": "^3.22.4"
}
}
65 changes: 65 additions & 0 deletions integrations/intercom/scripts/register.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
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 client = new Client({ connectionString });

const spec = {
name: 'Intercom',
key: 'intercom',
description:
'Connect your Intercom workspace to CORE. Sync conversations, contacts, and events — stay on top of customer support and engagement directly from your workspace.',
icon: 'intercom',
schedule: {
frequency: '*/15 * * * *',
},
auth: {
OAuth2: {
token_url: 'https://api.intercom.io/auth/eagle/token',
authorization_url: 'https://app.intercom.com/oauth',
scopes: ['read_users', 'read_conversations'],
scope_separator: ' ',
},
},
};

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(), 'Intercom', 'intercom', 'Connect your Intercom workspace to CORE. Sync conversations, contacts, and events — stay on top of customer support and engagement directly from your workspace.', 'intercom', $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({}),
'../../integrations/intercom/bin/index.cjs',
],
);

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

main().catch(console.error);
40 changes: 40 additions & 0 deletions integrations/intercom/src/account-create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { getIntercomClient } from './utils';

export async function integrationCreate(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any,
) {
const { oauthResponse } = data;
const integrationConfiguration = {
refresh_token: oauthResponse.refresh_token,
access_token: oauthResponse.access_token,
};

const client = getIntercomClient(integrationConfiguration.access_token);

// Fetch the authenticated admin/user info
const meResponse = await client.get('/me');
const me = meResponse.data;

const accountId = me.id?.toString() ?? me.email ?? 'intercom-account';
const email = me.email ?? '';
const name = me.name ?? '';

return [
{
type: 'account',
data: {
settings: {
email,
name,
app_id: me.app?.id_code ?? '',
app_name: me.app?.name ?? '',
},
accountId,
config: {
...integrationConfiguration,
},
},
},
];
}
14 changes: 14 additions & 0 deletions integrations/intercom/src/create-activity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface ActivityParams {
text: string;
sourceURL: string;
}

export function createActivity(params: ActivityParams) {
return {
type: 'activity',
data: {
text: params.text,
sourceURL: params.sourceURL,
},
};
}
Loading