Розробка REST-застосунків Бітрікс24 на TypeScript
Bitrix24 REST API — багатий, але заплутаний. Автодоповнення в IDE для методів crm.deal.list, tasks.task.add, im.message.send працює лише якщо є типи. Без TypeScript розробка REST-застосунку Бітрікс24 — це постійне переключення в документацію та ловіння undefined у рантаймі.
Розробка REST-застосунків Бітрікс24 на TypeScript
Архітектура REST-застосунку Бітрікс24
Три типи застосунків в екосистемі Бітрікс24:
-
Веб-застосунок (iframe) — завантажується всередині інтерфейсу Бітрікс24 в iframe. JavaScript/TypeScript з доступом до
BX24.jsSDK. - Серверний застосунок — PHP/Node.js, працює незалежно, обмінюється з Бітрікс24 через REST поверх OAuth.
- Віджет — компактний застосунок у sidebar або CRM.
TypeScript застосовний у всіх трьох випадках, але з різними точками входу.
Типізація BX24 SDK
Офіційного TypeScript-пакета для BX24 SDK немає. Пишемо декларацію самостійно:
// types/bx24.d.ts
declare global {
const BX24: {
init(callback: () => void): void;
isAdmin(): boolean;
getAuth(): BX24Auth;
refreshAuth(callback: (auth: BX24Auth) => void): void;
callMethod(
method: string,
params?: Record<string, unknown>,
callback?: (result: BX24CallResult) => void
): void;
callBatch(
calls: Record<string, [string, Record<string, unknown>?]>,
callback: (result: Record<string, BX24CallResult>) => void,
bHaltOnError?: boolean
): void;
resizeWindow(width: number, height: number): void;
closeApplication(): void;
placement: {
info(): BX24PlacementInfo;
call(command: string, params?: Record<string, unknown>): void;
};
};
}
interface BX24Auth {
access_token: string;
refresh_token: string;
expires_in: number;
domain: string;
member_id: string;
}
interface BX24CallResult {
status(): number;
data(): unknown;
error(): string | false;
more(): boolean;
next(): void;
total(): number;
}
interface BX24PlacementInfo {
placement: string;
options: Record<string, string>;
}
export {};
Типи для CRM-даних
// types/crm.ts
export interface BX24Deal {
ID: string;
TITLE: string;
STAGE_ID: string;
OPPORTUNITY: string;
CURRENCY_ID: string;
ASSIGNED_BY_ID: string;
DATE_CREATE: string;
DATE_MODIFY: string;
CONTACT_ID: string | null;
COMPANY_ID: string | null;
COMMENTS: string | null;
UF_CRM_CUSTOM_FIELD?: string; // користувацькі поля через UF_
[key: string]: unknown; // додаткові поля
}
export interface BX24Task {
id: string;
title: string;
description: string;
status: string;
responsible: { id: string; name: string };
deadline: string | null;
createdDate: string;
ufTaskWebdavFiles?: string[]; // користувацькі поля завдань
}
export type StageId =
| 'NEW' | 'PREPARATION' | 'PREPAYMENT_INVOICE'
| 'EXECUTING' | 'FINAL_INVOICE' | 'WON' | 'LOSE';
Обгортка над BX24 callMethod з типами
// api/bx24client.ts
export function callMethod<T>(
method: string,
params: Record<string, unknown> = {}
): Promise<T[]> {
return new Promise((resolve, reject) => {
const results: T[] = [];
const handleResult = (result: ReturnType<typeof BX24.callMethod extends (...args: unknown[]) => infer R ? R : never>) => {
if (result.error()) {
reject(new Error(String(result.error())));
return;
}
const data = result.data() as T[];
results.push(...(Array.isArray(data) ? data : [data as T]));
if (result.more()) {
result.next(); // автоматична пагінація
} else {
resolve(results);
}
};
BX24.callMethod(method, params, handleResult);
});
}
// Використання
import type { BX24Deal } from '@/types/crm';
const deals = await callMethod<BX24Deal>('crm.deal.list', {
filter: { STAGE_ID: 'NEW' },
select: ['ID', 'TITLE', 'OPPORTUNITY', 'ASSIGNED_BY_ID'],
order: { DATE_CREATE: 'DESC' },
});
result.more() + result.next() — механізм пагінації BX24 SDK. Обгортка автоматично обходить всі сторінки та повертає повний масив.
Batch-запити для продуктивності
Кожен callMethod — окремий HTTP-запит. Для застосунків з високим навантаженням на API — використовуємо callBatch:
export function callBatch<T extends Record<string, unknown>>(
calls: Record<string, [string, Record<string, unknown>?]>
): Promise<T> {
return new Promise((resolve, reject) => {
BX24.callBatch(calls, (results) => {
const output = {} as T;
let hasError = false;
for (const [key, result] of Object.entries(results)) {
if (result.error()) {
hasError = true;
console.error(`Batch error for "${key}":`, result.error());
} else {
(output as Record<string, unknown>)[key] = result.data();
}
}
if (hasError) reject(new Error('Batch had errors'));
else resolve(output);
});
});
}
// Завантаження угоди з пов'язаними даними за один запит
const data = await callBatch<{
deal: BX24Deal;
contact: BX24Contact;
history: BX24Activity[];
}>({
deal: ['crm.deal.get', { id: dealId }],
contact: ['crm.contact.get', { id: contactId }],
history: ['crm.activity.list', { filter: { OWNER_ID: dealId, OWNER_TYPE_ID: '2' } }],
});
React + TypeScript застосунок в iframe Бітрікс24
// main.tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App';
BX24.init(() => {
const container = document.getElementById('app');
if (!container) return;
const root = createRoot(container);
root.render(<App />);
// Автопідбір висоти iframe
const resizeObserver = new ResizeObserver(() => {
BX24.resizeWindow(
document.body.scrollWidth,
document.body.scrollHeight
);
});
resizeObserver.observe(document.body);
});
Терміни
| Завдання | Терміни |
|---|---|
| Налаштування TypeScript, типи BX24 SDK та CRM-сутностей | 1–2 дні |
| Простий iframe-застосунок (перегляд/редагування даних CRM) | 3–5 днів |
| Повнофункціональний React-застосунок у Бітрікс24 | 2–4 тижні |
| Серверний Node.js/TypeScript застосунок з OAuth | 1–2 тижні |







