feat(client): openChat
method
This commit is contained in:
parent
2728166727
commit
69f59ab97e
6 changed files with 254 additions and 15 deletions
|
@ -67,6 +67,7 @@ import { joinChat } from './methods/chats/join-chat.js'
|
||||||
import { kickChatMember } from './methods/chats/kick-chat-member.js'
|
import { kickChatMember } from './methods/chats/kick-chat-member.js'
|
||||||
import { leaveChat } from './methods/chats/leave-chat.js'
|
import { leaveChat } from './methods/chats/leave-chat.js'
|
||||||
import { markChatUnread } from './methods/chats/mark-chat-unread.js'
|
import { markChatUnread } from './methods/chats/mark-chat-unread.js'
|
||||||
|
import { openChat } from './methods/chats/open-chat.js'
|
||||||
import { reorderUsernames } from './methods/chats/reorder-usernames.js'
|
import { reorderUsernames } from './methods/chats/reorder-usernames.js'
|
||||||
import { restrictChatMember } from './methods/chats/restrict-chat-member.js'
|
import { restrictChatMember } from './methods/chats/restrict-chat-member.js'
|
||||||
import { saveDraft } from './methods/chats/save-draft.js'
|
import { saveDraft } from './methods/chats/save-draft.js'
|
||||||
|
@ -223,6 +224,8 @@ import {
|
||||||
enableRps,
|
enableRps,
|
||||||
getCurrentRpsIncoming,
|
getCurrentRpsIncoming,
|
||||||
getCurrentRpsProcessing,
|
getCurrentRpsProcessing,
|
||||||
|
notifyChannelClosed,
|
||||||
|
notifyChannelOpened,
|
||||||
startUpdatesLoop,
|
startUpdatesLoop,
|
||||||
stopUpdatesLoop,
|
stopUpdatesLoop,
|
||||||
} from './methods/updates/manager.js'
|
} from './methods/updates/manager.js'
|
||||||
|
@ -274,6 +277,7 @@ import {
|
||||||
ForumTopic,
|
ForumTopic,
|
||||||
GameHighScore,
|
GameHighScore,
|
||||||
HistoryReadUpdate,
|
HistoryReadUpdate,
|
||||||
|
InlineCallbackQuery,
|
||||||
InlineQuery,
|
InlineQuery,
|
||||||
InputChatEventFilters,
|
InputChatEventFilters,
|
||||||
InputDialogFolder,
|
InputDialogFolder,
|
||||||
|
@ -415,6 +419,13 @@ export interface TelegramClient extends BaseTelegramClient {
|
||||||
* @param handler Callback query handler
|
* @param handler Callback query handler
|
||||||
*/
|
*/
|
||||||
on(name: 'callback_query', handler: (upd: CallbackQuery) => void): this
|
on(name: 'callback_query', handler: (upd: CallbackQuery) => void): this
|
||||||
|
/**
|
||||||
|
* Register an inline callback query handler
|
||||||
|
*
|
||||||
|
* @param name Event name
|
||||||
|
* @param handler Inline callback query handler
|
||||||
|
*/
|
||||||
|
on(name: 'inline_callback_query', handler: (upd: InlineCallbackQuery) => void): this
|
||||||
/**
|
/**
|
||||||
* Register a poll update handler
|
* Register a poll update handler
|
||||||
*
|
*
|
||||||
|
@ -1661,6 +1672,17 @@ export interface TelegramClient extends BaseTelegramClient {
|
||||||
* @param chatId Chat ID
|
* @param chatId Chat ID
|
||||||
*/
|
*/
|
||||||
markChatUnread(chatId: InputPeerLike): Promise<void>
|
markChatUnread(chatId: InputPeerLike): Promise<void>
|
||||||
|
/**
|
||||||
|
* Inform the library that the user has opened a chat.
|
||||||
|
*
|
||||||
|
* Some library logic depends on this, for example, the library will
|
||||||
|
* periodically ping the server to keep the updates flowing.
|
||||||
|
*
|
||||||
|
* **Available**: ✅ both users and bots
|
||||||
|
*
|
||||||
|
* @param chat Chat to open
|
||||||
|
*/
|
||||||
|
openChat(chat: InputPeerLike): Promise<void>
|
||||||
/**
|
/**
|
||||||
* Reorder usernames
|
* Reorder usernames
|
||||||
*
|
*
|
||||||
|
@ -4863,6 +4885,36 @@ export interface TelegramClient extends BaseTelegramClient {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
catchUp(): void
|
catchUp(): void
|
||||||
|
/**
|
||||||
|
* **ADVANCED**
|
||||||
|
*
|
||||||
|
* Notify the updates manager that some channel was "opened".
|
||||||
|
* Channel difference for "opened" channels will be fetched on a regular basis.
|
||||||
|
* This is a low-level method, prefer using {@link openChat} instead.
|
||||||
|
*
|
||||||
|
* Channel must be resolve-able with `resolvePeer` method (i.e. be in cache);
|
||||||
|
* base chat PTS must either be passed (e.g. from {@link Dialog}), or cached in storage.
|
||||||
|
*
|
||||||
|
* **Available**: ✅ both users and bots
|
||||||
|
*
|
||||||
|
* @param channelId Bare ID of the channel
|
||||||
|
* @param pts PTS of the channel, if known (e.g. from {@link Dialog})
|
||||||
|
* @returns `true` if the channel was opened for the first time, `false` if it is already opened
|
||||||
|
*/
|
||||||
|
notifyChannelOpened(channelId: number, pts?: number): boolean
|
||||||
|
/**
|
||||||
|
* **ADVANCED**
|
||||||
|
*
|
||||||
|
* Notify the updates manager that some channel was "closed".
|
||||||
|
* Basically the opposite of {@link notifyChannelOpened}.
|
||||||
|
* This is a low-level method, prefer using {@link closeChat} instead.
|
||||||
|
*
|
||||||
|
* **Available**: ✅ both users and bots
|
||||||
|
*
|
||||||
|
* @param channelId Bare channel ID
|
||||||
|
* @returns `true` if the chat was closed for the last time, `false` otherwise
|
||||||
|
*/
|
||||||
|
notifyChannelClosed(channelId: number): boolean
|
||||||
/**
|
/**
|
||||||
* Block a user
|
* Block a user
|
||||||
*
|
*
|
||||||
|
@ -5377,6 +5429,10 @@ TelegramClient.prototype.markChatUnread = function (...args) {
|
||||||
return markChatUnread(this, ...args)
|
return markChatUnread(this, ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TelegramClient.prototype.openChat = function (...args) {
|
||||||
|
return openChat(this, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
TelegramClient.prototype.reorderUsernames = function (...args) {
|
TelegramClient.prototype.reorderUsernames = function (...args) {
|
||||||
return reorderUsernames(this, ...args)
|
return reorderUsernames(this, ...args)
|
||||||
}
|
}
|
||||||
|
@ -6037,6 +6093,14 @@ TelegramClient.prototype.catchUp = function (...args) {
|
||||||
return catchUp(this, ...args)
|
return catchUp(this, ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TelegramClient.prototype.notifyChannelOpened = function (...args) {
|
||||||
|
return notifyChannelOpened(this, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
TelegramClient.prototype.notifyChannelClosed = function (...args) {
|
||||||
|
return notifyChannelClosed(this, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
TelegramClient.prototype.blockUser = function (...args) {
|
TelegramClient.prototype.blockUser = function (...args) {
|
||||||
return blockUser(this, ...args)
|
return blockUser(this, ...args)
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ import {
|
||||||
ForumTopic,
|
ForumTopic,
|
||||||
GameHighScore,
|
GameHighScore,
|
||||||
HistoryReadUpdate,
|
HistoryReadUpdate,
|
||||||
|
InlineCallbackQuery,
|
||||||
InlineQuery,
|
InlineQuery,
|
||||||
InputChatEventFilters,
|
InputChatEventFilters,
|
||||||
InputDialogFolder,
|
InputDialogFolder,
|
||||||
|
|
50
packages/client/src/methods/chats/open-chat.ts
Normal file
50
packages/client/src/methods/chats/open-chat.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import { BaseTelegramClient } from '@mtcute/core'
|
||||||
|
|
||||||
|
import { InputPeerLike } from '../../types/peers/index.js'
|
||||||
|
import { isInputPeerChannel } from '../../utils/peer-utils.js'
|
||||||
|
import { getPeerDialogs } from '../dialogs/get-peer-dialogs.js'
|
||||||
|
import { notifyChannelClosed, notifyChannelOpened } from '../updates/manager.js'
|
||||||
|
import { resolvePeer } from '../users/resolve-peer.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the library that the user has opened a chat.
|
||||||
|
*
|
||||||
|
* Some library logic depends on this, for example, the library will
|
||||||
|
* periodically ping the server to keep the updates flowing.
|
||||||
|
*
|
||||||
|
* @param chat Chat to open
|
||||||
|
*/
|
||||||
|
export async function openChat(client: BaseTelegramClient, chat: InputPeerLike): Promise<void> {
|
||||||
|
const peer = await resolvePeer(client, chat)
|
||||||
|
|
||||||
|
if (isInputPeerChannel(peer)) {
|
||||||
|
const [dialog] = await getPeerDialogs(client, peer)
|
||||||
|
|
||||||
|
if (!client.network.params.disableUpdates) {
|
||||||
|
notifyChannelOpened(client, peer.channelId, dialog.raw.pts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: once we have proper dialogs/peers db, we should also
|
||||||
|
// update full info here and fetch auxillary info (like channel members etc)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the library that the user has closed a chat.
|
||||||
|
* Un-does the effect of {@link openChat}.
|
||||||
|
*
|
||||||
|
* Some library logic depends on this, for example, the library will
|
||||||
|
* periodically ping the server to keep the updates flowing.
|
||||||
|
*
|
||||||
|
* @param chat Chat to open
|
||||||
|
*/
|
||||||
|
export async function closeChat(client: BaseTelegramClient, chat: InputPeerLike): Promise<void> {
|
||||||
|
const peer = await resolvePeer(client, chat)
|
||||||
|
|
||||||
|
if (isInputPeerChannel(peer) && !client.network.params.disableUpdates) {
|
||||||
|
notifyChannelClosed(client, peer.channelId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: once we have proper dialogs/peers db, we should also
|
||||||
|
// update full info here and fetch auxillary info (like channel members etc)
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import { getBarePeerId, getMarkedPeerId, markedPeerIdToBare, toggleChannelIdMark
|
||||||
import { PeersIndex } from '../../types/index.js'
|
import { PeersIndex } from '../../types/index.js'
|
||||||
import { isInputPeerChannel, isInputPeerUser, toInputChannel, toInputUser } from '../../utils/peer-utils.js'
|
import { isInputPeerChannel, isInputPeerUser, toInputChannel, toInputUser } from '../../utils/peer-utils.js'
|
||||||
import { RpsMeter } from '../../utils/rps-meter.js'
|
import { RpsMeter } from '../../utils/rps-meter.js'
|
||||||
|
import { createDummyUpdatesContainer } from '../../utils/updates-utils.js'
|
||||||
import { getAuthState } from '../auth/_state.js'
|
import { getAuthState } from '../auth/_state.js'
|
||||||
import { _getChannelsBatched, _getUsersBatched } from '../chats/batched-queries.js'
|
import { _getChannelsBatched, _getUsersBatched } from '../chats/batched-queries.js'
|
||||||
import { resolvePeer } from '../users/resolve-peer.js'
|
import { resolvePeer } from '../users/resolve-peer.js'
|
||||||
|
@ -200,6 +201,11 @@ export function stopUpdatesLoop(client: BaseTelegramClient): void {
|
||||||
const state = getState(client)
|
const state = getState(client)
|
||||||
if (!state.updatesLoopActive) return
|
if (!state.updatesLoopActive) return
|
||||||
|
|
||||||
|
for (const timer of state.channelDiffTimeouts.values()) {
|
||||||
|
clearTimeout(timer)
|
||||||
|
}
|
||||||
|
state.channelDiffTimeouts.clear()
|
||||||
|
|
||||||
state.updatesLoopActive = false
|
state.updatesLoopActive = false
|
||||||
state.pendingUpdateContainers.clear()
|
state.pendingUpdateContainers.clear()
|
||||||
state.pendingUnorderedUpdates.clear()
|
state.pendingUnorderedUpdates.clear()
|
||||||
|
@ -227,6 +233,81 @@ export function catchUp(client: BaseTelegramClient): void {
|
||||||
handleUpdate(state, { _: 'updatesTooLong' })
|
handleUpdate(state, { _: 'updatesTooLong' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **ADVANCED**
|
||||||
|
*
|
||||||
|
* Notify the updates manager that some channel was "opened".
|
||||||
|
* Channel difference for "opened" channels will be fetched on a regular basis.
|
||||||
|
* This is a low-level method, prefer using {@link openChat} instead.
|
||||||
|
*
|
||||||
|
* Channel must be resolve-able with `resolvePeer` method (i.e. be in cache);
|
||||||
|
* base chat PTS must either be passed (e.g. from {@link Dialog}), or cached in storage.
|
||||||
|
*
|
||||||
|
* @param channelId Bare ID of the channel
|
||||||
|
* @param pts PTS of the channel, if known (e.g. from {@link Dialog})
|
||||||
|
* @returns `true` if the channel was opened for the first time, `false` if it is already opened
|
||||||
|
*/
|
||||||
|
export function notifyChannelOpened(client: BaseTelegramClient, channelId: number, pts?: number): boolean {
|
||||||
|
// this method is intentionally very dumb to avoid making this file even more unreadable
|
||||||
|
|
||||||
|
const state = getState(client)
|
||||||
|
|
||||||
|
if (!state) {
|
||||||
|
throw new MtArgumentError('Updates processing is not enabled, use enableUpdatesProcessing() first')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.channelsOpened.has(channelId)) {
|
||||||
|
state.log.debug('channel %d opened again', channelId)
|
||||||
|
state.channelsOpened.set(channelId, state.channelsOpened.get(channelId)! + 1)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
state.channelsOpened.set(channelId, 1)
|
||||||
|
state.log.debug('channel %d opened (pts=%d)', channelId, pts)
|
||||||
|
|
||||||
|
// force fetch channel difference
|
||||||
|
fetchChannelDifferenceViaUpdate(state, channelId, pts)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **ADVANCED**
|
||||||
|
*
|
||||||
|
* Notify the updates manager that some channel was "closed".
|
||||||
|
* Basically the opposite of {@link notifyChannelOpened}.
|
||||||
|
* This is a low-level method, prefer using {@link closeChat} instead.
|
||||||
|
*
|
||||||
|
* @param channelId Bare channel ID
|
||||||
|
* @returns `true` if the chat was closed for the last time, `false` otherwise
|
||||||
|
*/
|
||||||
|
export function notifyChannelClosed(client: BaseTelegramClient, channelId: number): boolean {
|
||||||
|
const state = getState(client)
|
||||||
|
|
||||||
|
if (!state) {
|
||||||
|
throw new MtArgumentError('Updates processing is not enabled, use enableUpdatesProcessing() first')
|
||||||
|
}
|
||||||
|
|
||||||
|
const opened = state.channelsOpened.get(channelId)!
|
||||||
|
|
||||||
|
if (opened === undefined) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opened > 1) {
|
||||||
|
state.log.debug('channel %d closed, but is opened %d more times', channelId, opened - 1)
|
||||||
|
state.channelsOpened.set(channelId, opened - 1)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
state.channelsOpened.delete(channelId)
|
||||||
|
state.log.debug('channel %d closed', channelId)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////// IMPLEMENTATION //////////////////////////////////////////////
|
////////////////////////////////////////////// IMPLEMENTATION //////////////////////////////////////////////
|
||||||
|
|
||||||
const STATE_SYMBOL = Symbol('updatesState')
|
const STATE_SYMBOL = Symbol('updatesState')
|
||||||
|
@ -670,6 +751,12 @@ async function fetchChannelDifference(
|
||||||
channelId: number,
|
channelId: number,
|
||||||
fallbackPts?: number,
|
fallbackPts?: number,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
|
// clear timeout if any
|
||||||
|
if (state.channelDiffTimeouts.has(channelId)) {
|
||||||
|
clearTimeout(state.channelDiffTimeouts.get(channelId))
|
||||||
|
state.channelDiffTimeouts.delete(channelId)
|
||||||
|
}
|
||||||
|
|
||||||
let _pts: number | null | undefined = state.cpts.get(channelId)
|
let _pts: number | null | undefined = state.cpts.get(channelId)
|
||||||
|
|
||||||
if (!_pts && state.catchUpChannels) {
|
if (!_pts && state.catchUpChannels) {
|
||||||
|
@ -700,6 +787,8 @@ async function fetchChannelDifference(
|
||||||
limit = 1
|
limit = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let lastTimeout = 0
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
const diff = await client.call({
|
const diff = await client.call({
|
||||||
_: 'updates.getChannelDifference',
|
_: 'updates.getChannelDifference',
|
||||||
|
@ -710,6 +799,8 @@ async function fetchChannelDifference(
|
||||||
filter: { _: 'channelMessagesFilterEmpty' },
|
filter: { _: 'channelMessagesFilterEmpty' },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (diff.timeout) lastTimeout = diff.timeout
|
||||||
|
|
||||||
if (diff._ === 'updates.channelDifferenceEmpty') {
|
if (diff._ === 'updates.channelDifferenceEmpty') {
|
||||||
state.log.debug('getChannelDifference (cid = %d) returned channelDifferenceEmpty', channelId)
|
state.log.debug('getChannelDifference (cid = %d) returned channelDifferenceEmpty', channelId)
|
||||||
break
|
break
|
||||||
|
@ -785,6 +876,15 @@ async function fetchChannelDifference(
|
||||||
state.cpts.set(channelId, pts)
|
state.cpts.set(channelId, pts)
|
||||||
state.cptsMod.set(channelId, pts)
|
state.cptsMod.set(channelId, pts)
|
||||||
|
|
||||||
|
// schedule next fetch
|
||||||
|
if (lastTimeout !== 0 && state.channelsOpened.has(channelId)) {
|
||||||
|
state.log.debug('scheduling next fetch for channel %d in %d seconds', channelId, lastTimeout)
|
||||||
|
state.channelDiffTimeouts.set(
|
||||||
|
channelId,
|
||||||
|
setTimeout(() => fetchChannelDifferenceViaUpdate(state, channelId), lastTimeout * 1000),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -814,6 +914,19 @@ function fetchChannelDifferenceLater(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fetchChannelDifferenceViaUpdate(state: UpdatesState, channelId: number, pts?: number): void {
|
||||||
|
handleUpdate(
|
||||||
|
state,
|
||||||
|
createDummyUpdatesContainer([
|
||||||
|
{
|
||||||
|
_: 'updateChannelTooLong',
|
||||||
|
channelId,
|
||||||
|
pts,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchDifference(
|
async function fetchDifference(
|
||||||
client: BaseTelegramClient,
|
client: BaseTelegramClient,
|
||||||
state: UpdatesState,
|
state: UpdatesState,
|
||||||
|
|
|
@ -128,6 +128,8 @@ export interface UpdatesState {
|
||||||
|
|
||||||
cpts: Map<number, number>
|
cpts: Map<number, number>
|
||||||
cptsMod: Map<number, number>
|
cptsMod: Map<number, number>
|
||||||
|
channelDiffTimeouts: Map<number, NodeJS.Timeout>
|
||||||
|
channelsOpened: Map<number, number>
|
||||||
|
|
||||||
log: Logger
|
log: Logger
|
||||||
stop: () => void
|
stop: () => void
|
||||||
|
@ -175,6 +177,8 @@ export function createUpdatesState(
|
||||||
catchUpOnStart: opts.catchUp ?? false,
|
catchUpOnStart: opts.catchUp ?? false,
|
||||||
cpts: new Map(),
|
cpts: new Map(),
|
||||||
cptsMod: new Map(),
|
cptsMod: new Map(),
|
||||||
|
channelDiffTimeouts: new Map(),
|
||||||
|
channelsOpened: new Map(),
|
||||||
log: client.log.create('updates'),
|
log: client.log.create('updates'),
|
||||||
stop: () => {}, // will be set later
|
stop: () => {}, // will be set later
|
||||||
handler: opts.onUpdate,
|
handler: opts.onUpdate,
|
||||||
|
|
|
@ -3,6 +3,20 @@ import { MtTypeAssertionError, tl } from '@mtcute/core'
|
||||||
// dummy updates which are used for methods that return messages.affectedHistory.
|
// dummy updates which are used for methods that return messages.affectedHistory.
|
||||||
// that is not an update, but it carries info about pts, and we need to handle it
|
// that is not an update, but it carries info about pts, and we need to handle it
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a dummy `updates` container with given updates.
|
||||||
|
*/
|
||||||
|
export function createDummyUpdatesContainer(updates: tl.TypeUpdate[], seq = 0): tl.TypeUpdates {
|
||||||
|
return {
|
||||||
|
_: 'updates',
|
||||||
|
seq,
|
||||||
|
date: 0,
|
||||||
|
chats: [],
|
||||||
|
users: [],
|
||||||
|
updates,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a dummy update from PTS and PTS count.
|
* Create a dummy update from PTS and PTS count.
|
||||||
*
|
*
|
||||||
|
@ -11,21 +25,14 @@ import { MtTypeAssertionError, tl } from '@mtcute/core'
|
||||||
* @param channelId Channel ID (bare), if applicable
|
* @param channelId Channel ID (bare), if applicable
|
||||||
*/
|
*/
|
||||||
export function createDummyUpdate(pts: number, ptsCount: number, channelId = 0): tl.TypeUpdates {
|
export function createDummyUpdate(pts: number, ptsCount: number, channelId = 0): tl.TypeUpdates {
|
||||||
return {
|
return createDummyUpdatesContainer([
|
||||||
_: 'updates',
|
{
|
||||||
seq: 0,
|
_: 'mtcute.dummyUpdate',
|
||||||
date: 0,
|
channelId,
|
||||||
chats: [],
|
pts,
|
||||||
users: [],
|
ptsCount,
|
||||||
updates: [
|
},
|
||||||
{
|
])
|
||||||
_: 'mtcute.dummyUpdate',
|
|
||||||
channelId,
|
|
||||||
pts,
|
|
||||||
ptsCount,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
|
Loading…
Reference in a new issue