Add Activepieces integration for workflow automation
- Add Activepieces fork with SmoothSchedule custom piece - Create integrations app with Activepieces service layer - Add embed token endpoint for iframe integration - Create Automations page with embedded workflow builder - Add sidebar visibility fix for embed mode - Add list inactive customers endpoint to Public API - Include SmoothSchedule triggers: event created/updated/cancelled - Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { browseAiApiCall } from '../common/client';
|
||||
import { browseAiAuth } from '../common/auth';
|
||||
import { robotIdDropdown, taskIdDropdown } from '../common/props';
|
||||
|
||||
export const getTaskDetailsAction = createAction({
|
||||
name: 'get-task-details',
|
||||
auth: browseAiAuth,
|
||||
displayName: 'Get Task Details',
|
||||
description:
|
||||
'Retrieves the details of a specific task executed by a Browse AI robot.',
|
||||
props: {
|
||||
robotId: robotIdDropdown,
|
||||
taskId: taskIdDropdown,
|
||||
},
|
||||
async run(context) {
|
||||
const { robotId, taskId } = context.propsValue;
|
||||
|
||||
try {
|
||||
const response = await browseAiApiCall({
|
||||
auth: { apiKey: context.auth.secret_text },
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: `/robots/${robotId}/tasks/${taskId}`,
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 404) {
|
||||
throw new Error(
|
||||
'Task not found. Please verify the Robot ID and Task ID.'
|
||||
);
|
||||
}
|
||||
|
||||
if (error.response?.status === 401) {
|
||||
throw new Error('Authentication failed. Please check your API key.');
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Failed to fetch task details: ${
|
||||
error.message || 'Unknown error occurred'
|
||||
}`
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { browseAiApiCall } from '../common/client';
|
||||
import { browseAiAuth } from '../common/auth';
|
||||
|
||||
export const listRobotsAction = createAction({
|
||||
name: 'list-robots',
|
||||
auth: browseAiAuth,
|
||||
displayName: 'List Robots',
|
||||
description: 'Retrieves all robots available in your account.',
|
||||
props: {},
|
||||
async run(context) {
|
||||
try {
|
||||
const response = await browseAiApiCall({
|
||||
auth: { apiKey: context.auth.secret_text },
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: '/robots',
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 401) {
|
||||
throw new Error('Authentication failed. Please check your API key.');
|
||||
}
|
||||
|
||||
if (error.response?.status === 429) {
|
||||
throw new Error('Rate limit exceeded. Please wait before retrying.');
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Failed to fetch robots: ${error.message || 'Unknown error occurred'}`
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { browseAiApiCall } from '../common/client';
|
||||
import { browseAiAuth } from '../common/auth';
|
||||
import { robotIdDropdown, robotParameters } from '../common/props';
|
||||
|
||||
export const runRobotAction = createAction({
|
||||
name: 'run-robot',
|
||||
auth: browseAiAuth,
|
||||
displayName: 'Run a Robot',
|
||||
description:
|
||||
'Runs a robot on-demand with custom input parameters.',
|
||||
props: {
|
||||
robotId: robotIdDropdown,
|
||||
recordVideo: Property.Checkbox({
|
||||
displayName: 'Record Video',
|
||||
description:
|
||||
'Try to record a video while running the task.',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
robotParams: robotParameters,
|
||||
},
|
||||
async run(context) {
|
||||
const { robotId, recordVideo } = context.propsValue;
|
||||
|
||||
const inputParameters = context.propsValue.robotParams ?? {};
|
||||
|
||||
try {
|
||||
const response = await browseAiApiCall({
|
||||
method: HttpMethod.POST,
|
||||
resourceUri: `/robots/${robotId}/tasks`,
|
||||
auth: { apiKey: context.auth.secret_text },
|
||||
body: {
|
||||
recordVideo: recordVideo || false,
|
||||
inputParameters,
|
||||
},
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 401) {
|
||||
throw new Error('Authentication failed. Please check your API key.');
|
||||
}
|
||||
|
||||
if (error.response?.status === 404) {
|
||||
throw new Error('Robot not found. Please verify the robot ID.');
|
||||
}
|
||||
|
||||
if (error.response?.status === 429) {
|
||||
throw new Error('Rate limit exceeded. Please wait before retrying.');
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Failed to run robot: ${error.message || 'Unknown error occurred'}`
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { PieceAuth } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { browseAiApiCall } from './client';
|
||||
|
||||
export const browseAiAuth = PieceAuth.SecretText({
|
||||
displayName: 'API Key',
|
||||
description: 'You can find your Browse AI API key on the dashboard under Settings → API Key.',
|
||||
required: true,
|
||||
validate: async ({ auth }) => {
|
||||
try {
|
||||
await browseAiApiCall({
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: '/status',
|
||||
auth: { apiKey: auth },
|
||||
});
|
||||
return { valid: true };
|
||||
} catch {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Invalid API Key. Please check your Browse AI credentials.',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
import {
|
||||
httpClient,
|
||||
HttpMethod,
|
||||
HttpRequest,
|
||||
HttpMessageBody,
|
||||
QueryParams,
|
||||
} from '@activepieces/pieces-common';
|
||||
|
||||
export type BrowseAiAuthProps = {
|
||||
apiKey: string;
|
||||
};
|
||||
|
||||
export type BrowseAiApiCallParams = {
|
||||
method: HttpMethod;
|
||||
resourceUri: string;
|
||||
query?: Record<string, string | number | string[] | undefined>;
|
||||
body?: unknown;
|
||||
auth: BrowseAiAuthProps;
|
||||
};
|
||||
|
||||
export async function browseAiApiCall<T extends HttpMessageBody>({
|
||||
method,
|
||||
resourceUri,
|
||||
query,
|
||||
body,
|
||||
auth,
|
||||
}: BrowseAiApiCallParams): Promise<T> {
|
||||
const { apiKey } = auth;
|
||||
|
||||
if (!apiKey) {
|
||||
throw new Error('Browse AI API key is required for authentication');
|
||||
}
|
||||
|
||||
const queryParams: QueryParams = {};
|
||||
|
||||
if (query) {
|
||||
for (const [key, value] of Object.entries(query)) {
|
||||
if (value !== null && value !== undefined) {
|
||||
queryParams[key] = String(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const baseUrl = `https://api.browse.ai/v2`;
|
||||
|
||||
const request: HttpRequest = {
|
||||
method,
|
||||
url: `${baseUrl}${resourceUri}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
queryParams,
|
||||
body,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await httpClient.sendRequest<T>(request);
|
||||
return response.body;
|
||||
} catch (error: any) {
|
||||
const statusCode = error.response?.status;
|
||||
const errorData = error.response?.data;
|
||||
|
||||
switch (statusCode) {
|
||||
case 400:
|
||||
throw new Error(`Bad Request: ${errorData?.message || 'Invalid parameters'}`);
|
||||
case 401:
|
||||
throw new Error('Unauthorized: Invalid API key. Please check your credentials.');
|
||||
case 403:
|
||||
throw new Error('Forbidden: You do not have permission to access this resource.');
|
||||
case 404:
|
||||
throw new Error('Not Found: The requested resource does not exist.');
|
||||
case 429:
|
||||
throw new Error('Rate Limit Exceeded: Please slow down your requests.');
|
||||
case 500:
|
||||
throw new Error('Internal Server Error: Something went wrong on Browse AI’s side.');
|
||||
default:
|
||||
{
|
||||
const message = errorData?.message || error.message || 'Unknown error';
|
||||
throw new Error(`Browse AI API Error (${statusCode || 'Unknown'}): ${message}`);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
import { DynamicPropsValue, Property } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { browseAiApiCall } from './client';
|
||||
import { browseAiAuth } from './auth';
|
||||
|
||||
interface BrowseAiRobot {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface BrowseAiTask {
|
||||
id: string;
|
||||
status: string;
|
||||
createdAt?: number;
|
||||
}
|
||||
|
||||
interface BrowseAiTasksResponse {
|
||||
statusCode: number;
|
||||
messageCode: string;
|
||||
result: {
|
||||
robotTasks: {
|
||||
totalCount: number;
|
||||
pageNumber: number;
|
||||
hasMore: boolean;
|
||||
items: BrowseAiTask[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface BrowseAiRobotResponse {
|
||||
robot: {
|
||||
id: string;
|
||||
name: string;
|
||||
inputParameters: {
|
||||
type: string;
|
||||
name: string;
|
||||
label: string;
|
||||
required: boolean;
|
||||
options?: { label: string; value: string }[];
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
export const robotIdDropdown = Property.Dropdown({
|
||||
auth: browseAiAuth,
|
||||
displayName: 'Robot',
|
||||
description: 'Select a robot from your Browse AI account',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please connect your Browse AI account first.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await browseAiApiCall<{
|
||||
robots: { items: BrowseAiRobot[] };
|
||||
}>({
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: '/robots',
|
||||
auth: { apiKey: auth.secret_text },
|
||||
});
|
||||
|
||||
const robots = response?.robots?.items ?? [];
|
||||
|
||||
if (robots.length === 0) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'No robots found in your account.',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options: robots.map((robot) => ({
|
||||
label: robot.name,
|
||||
value: robot.id,
|
||||
})),
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder:
|
||||
'Failed to load robots. Please check your API key and try again.',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const taskIdDropdown = Property.Dropdown({
|
||||
auth: browseAiAuth,
|
||||
displayName: 'Task',
|
||||
description: 'Select a task associated with the selected robot',
|
||||
required: true,
|
||||
refreshers: ['robotId'],
|
||||
options: async ({ auth, robotId }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please connect your Browse AI account.',
|
||||
};
|
||||
}
|
||||
|
||||
if (!robotId) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please select a robot first.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await browseAiApiCall<BrowseAiTasksResponse>({
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: `/robots/${robotId}/tasks`,
|
||||
auth: { apiKey: auth.secret_text },
|
||||
});
|
||||
|
||||
const tasks = response.result?.robotTasks?.items ?? [];
|
||||
|
||||
if (tasks.length === 0) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'No tasks found for the selected robot.',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options: tasks.map((task) => {
|
||||
const createdDate = task.createdAt
|
||||
? new Date(task.createdAt).toLocaleDateString()
|
||||
: 'Unknown date';
|
||||
return {
|
||||
label: `${task.id} - ${task.status} (${createdDate})`,
|
||||
value: task.id,
|
||||
};
|
||||
}),
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: `Error fetching tasks: ${
|
||||
e instanceof Error ? e.message : 'Unknown error'
|
||||
}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const robotParameters = Property.DynamicProperties({
|
||||
auth: browseAiAuth,
|
||||
displayName: 'Input Parameters',
|
||||
refreshers: ['robotId'],
|
||||
required: true,
|
||||
props: async ({ auth, robotId }) => {
|
||||
if (!auth || !robotId) return {};
|
||||
|
||||
try {
|
||||
const response = await browseAiApiCall<BrowseAiRobotResponse>({
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: `/robots/${robotId}`,
|
||||
auth: { apiKey: auth.secret_text },
|
||||
});
|
||||
|
||||
const props: DynamicPropsValue = {};
|
||||
|
||||
const params = response.robot.inputParameters ?? [];
|
||||
|
||||
for (const param of params) {
|
||||
switch (param.type) {
|
||||
case 'number':
|
||||
props[param.name] = Property.Number({
|
||||
displayName: param.label,
|
||||
required: param.required,
|
||||
});
|
||||
break;
|
||||
case 'url':
|
||||
case 'string':
|
||||
props[param.name] = Property.ShortText({
|
||||
displayName: param.label,
|
||||
required: param.required,
|
||||
});
|
||||
break;
|
||||
case 'select':
|
||||
props[param.name] = Property.StaticDropdown({
|
||||
displayName: param.label,
|
||||
required: param.required,
|
||||
options: {
|
||||
disabled: false,
|
||||
options: param.options ? param.options : [],
|
||||
},
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return props;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,135 @@
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { isNil } from '@activepieces/shared';
|
||||
import { browseAiAuth } from '../common/auth';
|
||||
import { browseAiApiCall } from '../common/client';
|
||||
import { robotIdDropdown } from '../common/props';
|
||||
|
||||
const TRIGGER_KEY = 'browse-ai-task_finished_successfully';
|
||||
|
||||
export const taskFinishedSuccessfullyTrigger = createTrigger({
|
||||
auth: browseAiAuth,
|
||||
name: 'task_finished_successfully',
|
||||
displayName: 'Task Finished Successfully',
|
||||
description:
|
||||
'Triggers when a robot finishes a task successfully.',
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
props: {
|
||||
robotId: robotIdDropdown,
|
||||
},
|
||||
|
||||
async onEnable(context) {
|
||||
const { robotId } = context.propsValue;
|
||||
const apiKey = context.auth.secret_text;
|
||||
|
||||
try {
|
||||
// Verify robot exists and we have access
|
||||
await browseAiApiCall({
|
||||
method: HttpMethod.GET,
|
||||
auth: { apiKey },
|
||||
resourceUri: `/robots/${robotId}`,
|
||||
});
|
||||
|
||||
const response = await browseAiApiCall<{
|
||||
webhook: { id: string; url: string; status: string };
|
||||
}>({
|
||||
method: HttpMethod.POST,
|
||||
auth: { apiKey },
|
||||
resourceUri: `/robots/${robotId}/webhooks`,
|
||||
body: {
|
||||
hookUrl: context.webhookUrl,
|
||||
eventType: 'taskFinishedSuccessfully',
|
||||
},
|
||||
});
|
||||
|
||||
await context.store.put<string>(TRIGGER_KEY, response.webhook.id);
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 404) {
|
||||
throw new Error(
|
||||
`Robot not found: The robot with ID "${robotId}" does not exist or you do not have access to it. Please verify the robot ID and your permissions.`
|
||||
);
|
||||
}
|
||||
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error(
|
||||
'Access denied: You do not have permission to set up webhooks for this robot. Please check your Browse AI account permissions and ensure you have webhook access.'
|
||||
);
|
||||
}
|
||||
|
||||
if (error.response?.status === 400) {
|
||||
throw new Error(
|
||||
`Invalid webhook configuration: ${
|
||||
error.response?.data?.message || error.message
|
||||
}. Please check your webhook URL and robot ID.`
|
||||
);
|
||||
}
|
||||
|
||||
if (error.response?.status === 429) {
|
||||
throw new Error(
|
||||
'Rate limit exceeded: Too many webhook requests. Please wait a moment and try again.'
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Failed to set up webhook: ${
|
||||
error.message || 'Unknown error occurred'
|
||||
}. Please check your robot ID and try again.`
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
async onDisable(context) {
|
||||
const { robotId } = context.propsValue;
|
||||
|
||||
const webhookId = await context.store.get<string>(TRIGGER_KEY);
|
||||
const apiKey = context.auth.secret_text;
|
||||
|
||||
if (!isNil(webhookId)) {
|
||||
try {
|
||||
await browseAiApiCall({
|
||||
method: HttpMethod.DELETE,
|
||||
auth: { apiKey },
|
||||
resourceUri: `/robots/${robotId}/webhooks/${webhookId}`,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.warn(
|
||||
`Warning: Failed to clean up webhook ${webhookId}:`,
|
||||
error.message
|
||||
);
|
||||
|
||||
// Clean up the stored webhook ID even if deletion failed
|
||||
await context.store.delete(TRIGGER_KEY);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const payload = context.payload.body as {
|
||||
task: Record<string, any>;
|
||||
event: string;
|
||||
};
|
||||
|
||||
if (payload.event !== 'task.finishedSuccessfully') return [];
|
||||
|
||||
return [payload.task];
|
||||
},
|
||||
|
||||
async test(context) {
|
||||
const { robotId } = context.propsValue;
|
||||
|
||||
const apiKey = context.auth.secret_text;
|
||||
|
||||
const response = await browseAiApiCall<{
|
||||
result: { robotTasks: { items: { id: string }[] } };
|
||||
}>({
|
||||
method: HttpMethod.GET,
|
||||
auth: { apiKey },
|
||||
resourceUri: `/robots/${robotId}/tasks`,
|
||||
query: { status: 'successful', sort: '-createdAt' },
|
||||
});
|
||||
|
||||
return response.result.robotTasks.items;
|
||||
},
|
||||
|
||||
sampleData: {},
|
||||
});
|
||||
@@ -0,0 +1,134 @@
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { isNil } from '@activepieces/shared';
|
||||
import { browseAiAuth } from '../common/auth';
|
||||
import { browseAiApiCall } from '../common/client';
|
||||
import { robotIdDropdown } from '../common/props';
|
||||
|
||||
const TRIGGER_KEY = 'browse-ai-task_finished_with_error';
|
||||
|
||||
export const taskFinishedWithErrorTrigger = createTrigger({
|
||||
auth: browseAiAuth,
|
||||
name: 'task_finished_with_error',
|
||||
displayName: 'Task Finished with Error',
|
||||
description: 'Triggers when a robot task run fails with an error.',
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
props: {
|
||||
robotId: robotIdDropdown,
|
||||
},
|
||||
|
||||
async onEnable(context) {
|
||||
const { robotId } = context.propsValue;
|
||||
const apiKey = context.auth.secret_text;
|
||||
|
||||
try {
|
||||
// Verify robot exists and we have access
|
||||
await browseAiApiCall({
|
||||
method: HttpMethod.GET,
|
||||
auth: { apiKey },
|
||||
resourceUri: `/robots/${robotId}`,
|
||||
});
|
||||
|
||||
const response = await browseAiApiCall<{
|
||||
webhook: { id: string; url: string; status: string };
|
||||
}>({
|
||||
method: HttpMethod.POST,
|
||||
auth: { apiKey },
|
||||
resourceUri: `/robots/${robotId}/webhooks`,
|
||||
body: {
|
||||
hookUrl: context.webhookUrl,
|
||||
eventType: 'taskFinishedWithError',
|
||||
},
|
||||
});
|
||||
|
||||
await context.store.put<string>(TRIGGER_KEY, response.webhook.id);
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 404) {
|
||||
throw new Error(
|
||||
`Robot not found: The robot with ID "${robotId}" does not exist or you do not have access to it. Please verify the robot ID and your permissions.`
|
||||
);
|
||||
}
|
||||
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error(
|
||||
'Access denied: You do not have permission to set up webhooks for this robot. Please check your Browse AI account permissions and ensure you have webhook access.'
|
||||
);
|
||||
}
|
||||
|
||||
if (error.response?.status === 400) {
|
||||
throw new Error(
|
||||
`Invalid webhook configuration: ${
|
||||
error.response?.data?.message || error.message
|
||||
}. Please check your webhook URL and robot ID.`
|
||||
);
|
||||
}
|
||||
|
||||
if (error.response?.status === 429) {
|
||||
throw new Error(
|
||||
'Rate limit exceeded: Too many webhook requests. Please wait a moment and try again.'
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Failed to set up webhook: ${
|
||||
error.message || 'Unknown error occurred'
|
||||
}. Please check your robot ID and try again.`
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
async onDisable(context) {
|
||||
const { robotId } = context.propsValue;
|
||||
|
||||
const webhookId = await context.store.get<string>(TRIGGER_KEY);
|
||||
const apiKey = context.auth.secret_text;
|
||||
|
||||
if (!isNil(webhookId)) {
|
||||
try {
|
||||
await browseAiApiCall({
|
||||
method: HttpMethod.DELETE,
|
||||
auth: { apiKey },
|
||||
resourceUri: `/robots/${robotId}/webhooks/${webhookId}`,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.warn(
|
||||
`Warning: Failed to clean up webhook ${webhookId}:`,
|
||||
error.message
|
||||
);
|
||||
|
||||
// Clean up the stored webhook ID even if deletion failed
|
||||
await context.store.delete(TRIGGER_KEY);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const payload = context.payload.body as {
|
||||
task: Record<string, any>;
|
||||
event: string;
|
||||
};
|
||||
|
||||
if (payload.event !== 'task.finishedWithError') return [];
|
||||
|
||||
return [payload.task];
|
||||
},
|
||||
|
||||
async test(context) {
|
||||
const { robotId } = context.propsValue;
|
||||
|
||||
const apiKey = context.auth.secret_text;
|
||||
|
||||
const response = await browseAiApiCall<{
|
||||
result: { robotTasks: { items: { id: string }[] } };
|
||||
}>({
|
||||
method: HttpMethod.GET,
|
||||
auth: { apiKey },
|
||||
resourceUri: `/robots/${robotId}/tasks`,
|
||||
query: { status: 'failed', sort: '-createdAt' },
|
||||
});
|
||||
|
||||
return response.result.robotTasks.items;
|
||||
},
|
||||
|
||||
sampleData: {},
|
||||
});
|
||||
Reference in New Issue
Block a user