B24Frame) — it relies on the parent-window auth, channels, and member_id. It is not functional through B24Hook or B24OAuth on the server.Overview
Pull lets the front-end of your application receive events the back-end (or another front-end instance) emits via the pull.application.event.add REST method. The SDK ships a complete client (B24PullClientManager) with two connectors:
- WebSocket primary (
ConnectionType.WebSocket) - Long-polling fallback (
ConnectionType.LongPolling) - Protobuf-encoded message envelopes (decoders bundled, no extra dependency)
You normally drive Pull through useB24Helper — usePullClient / useSubscribePullClient / startPullClient are thin wrappers over the matching B24HelperManager methods.
Quick Start
import {
initializeB24Frame,
useB24Helper,
Text,
LoggerFactory,
type B24Frame,
type TypePullMessage
} from '@bitrix24/b24jssdk'
const $logger = LoggerFactory.createForBrowser('MyApp', import.meta.env?.DEV === true)
const {
initB24Helper,
getB24Helper,
usePullClient,
useSubscribePullClient,
startPullClient,
destroyB24Helper
} = useB24Helper()
let $b24: B24Frame
async function init() {
$b24 = await initializeB24Frame()
await initB24Helper($b24)
// 1. Spin up the underlying B24PullClientManager (one per helper instance)
usePullClient()
// 2. Subscribe — can be called multiple times for multiple moduleIds
useSubscribePullClient((message: TypePullMessage) => {
$logger.info(`${Text.getDateForLog()} << pull`, { message })
}, 'main')
// 3. Connect (WebSocket → long-polling fallback)
startPullClient()
}
async function emitPing(): Promise<void> {
await $b24.actions.v2.call.make({
method: 'pull.application.event.add',
params: {
COMMAND: 'ping',
PARAMS: { tick: Text.getDateForLog() },
MODULE_ID: getB24Helper().getModuleIdPullClient()
}
})
}
await init()
setInterval(emitPing, 5000)
When the host component unmounts, call destroyB24Helper() — that fully tears the Pull client down: closes the WebSocket, clears all subscriptions, removes its window listeners, and cancels all pending timers.
API via B24HelperManager
These four methods are the public Pull surface. See B24HelperManager.
usePullClient(prefix?: string, userId?: number): B24HelperManager
subscribePullClient(callback: (message: TypePullMessage) => void, moduleId?: string): B24HelperManager
startPullClient(): void
getModuleIdPullClient(): string
usePullClientconstructsB24PullClientManageronce.prefixis forwarded tob24.auth.getUniq(prefix)to deriverestApplication.userIddefaults to the loaded profile id.subscribePullClientreturns the helper for chaining; the unsubscribe handle is tracked internally and released ondestroy().startPullClienttriggersPullClient.start()and logs the failure (without throwing) if the connection cannot be established.getModuleIdPullClientreturns themoduleIdlast passed tosubscribePullClient. Use it as theMODULE_IDparameter ofpull.application.event.add.
API via B24PullClientManager Directly
If you need finer control, instantiate the client yourself:
// @check-ignore: partial snippet — B24PullClientManager SubscriptionType not exported publicly
import { B24PullClientManager } from '@bitrix24/b24jssdk'
const pull = new B24PullClientManager({
b24: $b24,
restApplication: $b24.auth.getUniq('myApp'),
userId: 1
})
const unsubscribe = pull.subscribe({
type: 'server', // 'server' | 'client' | 'online'
moduleId: 'main',
command: 'optionsChanged',
callback: (params) => console.log(params)
})
await pull.start()
// Later
unsubscribe()
pull.destroy()
subscribe() accepts either a full TypeSubscriptionOptions object (filter by type / moduleId / command) or a bare TypeSubscriptionCommandHandler function (catches any incoming command). It returns an unsubscribe callback.
Connection Types
The client picks the connector automatically:
- WebSocket — preferred when the portal exposes one.
- Long-polling — used when WebSocket is unavailable, blocked, or when the connection drops repeatedly.
ConnectionType (Undefined / WebSocket / LongPolling) and PullStatus (Online / Offline / Connecting) are exported from the package — useful if you want to reflect the connection in your UI.
Sending Messages
The Pull client only receives messages. To send, call pull.application.event.add over REST:
// @check-ignore: partial snippet — getB24Helper() not in scope
await $b24.actions.v2.call.make({
method: 'pull.application.event.add',
params: {
COMMAND: 'optionsChanged',
PARAMS: { theme: 'dark' },
MODULE_ID: getB24Helper().getModuleIdPullClient()
}
})
Subscribers attached via useSubscribePullClient(cb, 'main') will receive the matching TypePullMessage.
PullClient also exposes sendMessage() and sendMessageToChannels().sendMessage() is not the supported path in an application: it resolves
the recipients' channels through pull.channel.public.list, which is not part
of the application REST surface, and rejects with
JSSDK_PULL_PUBLIC_IDS_UNAVAILABLE when it has to make that
call. Use pull.application.event.add, as above.It does not always have to. The lookup is skipped when every recipient already
has an unexpired channel in the cache, which the startup config call prefills
from publicChannels — that map carries the current user's own channel, so a
send addressed to yourself often goes out with no lookup and no rejection. It
is still not a delivery receipt, and what comes back arrives on a
SubscriptionType.Client subscription rather than the
Server one subscribe() gives you by default.sendMessageToChannels() takes channel ids from the caller, so it never makes
that lookup and never raises that code.Both reject with JSSDK_PULL_SEND_REFUSED when the transport
will not take the frame, and JSSDK_PULL_PUBLISHING_DISABLED
when the portal has not enabled client publishing. Until 3.0.0 all of these
resolved as though the message had been sent — see
the migration note.