Цель
Один Vue 3-компонент, демонстрирующий четыре вещи, нужные каждому iframe-приложению Bitrix24: инициализировать рукопожатие postMessage, задать заголовок родительского окна, сохранить часть состояния между перезагрузками через опции приложения и открыть другую страницу портала в слайдере.
Стек и предпосылки
- Vue 3 + Vite (или Nuxt 3 с
@bitrix24/b24jssdk-nuxt) @bitrix24/b24jssdk@^1.0.0- Страница должна отдаваться с URL, зарегистрированного как обработчик placement в карточке вашего приложения Bitrix24. Вне iframe
initializeB24Frame()завершается reject.
pnpm add @bitrix24/b24jssdk
Полный пример
src/components/AppShell.vue:
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue'
import {
initializeB24Frame,
type B24Frame,
LoggerFactory
} from '@bitrix24/b24jssdk'
const logger = LoggerFactory.createForBrowser('AppShell', import.meta.env.DEV)
const ready = ref(false)
const userTheme = ref<'light' | 'dark'>('light')
const portalDomain = ref('')
let $b24: B24Frame | null = null
onMounted(async () => {
try {
$b24 = await initializeB24Frame()
}
catch (error) {
logger.error('initializeB24Frame failed', error)
return
}
$b24.parent.setTitle('My Bitrix24 App')
// Авто-ресайз iframe под содержимое; вызывайте снова, когда высота содержимого
// меняется (например, после рендера ответа сети).
await $b24.parent.resizeWindowAuto()
// Чтение сохранённых настроек. Возвращает `null`, если ничего ещё не задано.
const stored = $b24.options.appGet('theme') as 'light' | 'dark' | null
if (stored) userTheme.value = stored
const appInfo = await $b24.actions.v2.call.make({ method: 'app.info' })
portalDomain.value = appInfo.getData().result.DOMAIN ?? ''
ready.value = true
})
onBeforeUnmount(() => {
$b24 = null
})
async function setTheme(value: 'light' | 'dark') {
userTheme.value = value
// Опция уровня приложения: хранится на портале под приложением, не под пользователем.
await $b24?.options.appSet('theme', value)
}
async function openCrmDeals() {
if (!$b24) return
const url = new URL('/crm/deal/list/', `https://${portalDomain.value}`)
await $b24.slider.openPath(url, 1200)
}
async function close() {
await $b24?.parent.closeApplication()
}
</script>
<template>
<section v-if="ready" class="app-shell" :data-theme="userTheme">
<header>
<h1>Hello from {{ portalDomain }}</h1>
</header>
<main>
<button @click="setTheme(userTheme === 'light' ? 'dark' : 'light')">
Switch theme (current: {{ userTheme }})
</button>
<button @click="openCrmDeals">
Open deals in slider
</button>
</main>
<footer>
<button @click="close">Close app</button>
</footer>
</section>
<p v-else>Loading…</p>
</template>
src/main.ts:
// @check-ignore: vue .vue module import — .vue files are not in tsconfig
import { createApp } from 'vue'
import AppShell from './components/AppShell.vue'
createApp(AppShell).mount('#app')
Запуск
# 1. Локальный dev-сервер (Vite)
pnpm dev
# → http://localhost:5173/ — запуск этого URL напрямую завершится reject:
# initializeB24Frame() нужно родительское окно Bitrix24.
# 2. Пробросьте dev-сервер (ngrok / cloudflared / ваш туннель)
ngrok http 5173
# → возьмите https-URL.
# 3. В Bitrix24: Приложения → Ресурсы разработчика → Локальное приложение →
# укажите URL обработчика placement равным URL туннеля и сохраните.
# 4. Откройте placement внутри Bitrix24.
После загрузки iframe вы увидите: заголовок родительского окна станет «My Bitrix24 App», iframe подстроится под содержимое, а в теле появится Hello from <your-portal>.bitrix24.com и две кнопки. Переключение темы сохраняется между перезагрузками (значение живёт на портале под приложением, а не в localStorage). Клик по «Open deals in slider» открывает /crm/deal/list/ того же портала в слайдере в стиле Bitrix24.
Как это работает
initializeB24Frame()— единственный правильный способ создатьB24Frame. Он устраняет дублирование параллельных вызовов, разбирает параметры рукопожатия портала изwindow.nameи ждёт, пока родительское окно подтвердит payload авторизации.parent.setTitle()иparent.resizeWindowAuto()пересекают границу iframe черезpostMessage. Оба идемпотентны — вызывайте их всякий раз при изменении вёрстки.options.appGet()синхронный, потому что значения загружаются один раз во время рукопожатия.options.appSet()асинхронный, потому что делает round-trip к серверу.slider.openPath(url, width)принимает экземплярURL(не строку) и возвращаетPromise<StatusClose>, который разрешается, когда пользователь закрывает слайдер. CSP портала разрешает открывать URL только на домене того же портала.
Ограничения
- Компонент должен монтироваться внутри iframe Bitrix24; запуск отдельно (например, в Storybook) сразу падает, потому что родительского окна нет. Защищайтесь проверкой
window.parent === window, если нужен мягкий фолбэк. $b24.options.appSet()записывает по одной опции за раз. Несколько записей в одном рендере — нормально (SDK их сериализует), но каждый вызов — это сетевой round-trip.parent.closeApplication()закрывает слайдер приложения только когда placement поддерживает закрытие. Встроенные placement (виджеты сайдбара) игнорируют запрос.- Опции уровня приложения видны каждому пользователю приложения на портале. Для пользовательских настроек используйте
userGet/userSet.
См. также
initializeB24Frame()B24Frame.parent— заголовок, ресайз, закрытие, IM-хелперы.B24Frame.sliderB24Frame.options— хранилище опций приложения и пользователя.
Установка OAuth
Express-сервер, который обрабатывает события ONAPPINSTALL / ONAPPUPDATE / ONAPPUNINSTALL для маркетплейс-приложения Bitrix24, сохраняет токены по порталам и создаёт клиент B24OAuth по требованию с callback обновления, подключённым к хранилищу.
Массовые рассылки
Фильтрует контакты в CRM, персонализирует шаблон, отправляет IM-уведомления ответственным менеджерам.