fix(file-id): updated file id computation for layer 130
This commit is contained in:
parent
090f42e559
commit
f520470fad
8 changed files with 365 additions and 33 deletions
|
@ -28,6 +28,8 @@ export class Photo extends FileLocation {
|
|||
/** Biggest available photo height */
|
||||
readonly height: number
|
||||
|
||||
private _bestSize?: tl.RawPhotoSize | tl.RawPhotoSizeProgressive
|
||||
|
||||
constructor(client: TelegramClient, raw: tl.RawPhoto) {
|
||||
const location = {
|
||||
_: 'inputPhotoFileLocation',
|
||||
|
@ -38,6 +40,8 @@ export class Photo extends FileLocation {
|
|||
} as tl.Mutable<tl.RawInputPhotoFileLocation>
|
||||
let size, width, height: number
|
||||
|
||||
let bestSize: tl.RawPhotoSize | tl.RawPhotoSizeProgressive
|
||||
|
||||
const progressive = raw.sizes.find(
|
||||
(it) => it._ === 'photoSizeProgressive'
|
||||
) as tl.RawPhotoSizeProgressive | undefined
|
||||
|
@ -46,6 +50,8 @@ export class Photo extends FileLocation {
|
|||
size = Math.max(...progressive.sizes)
|
||||
width = progressive.w
|
||||
height = progressive.h
|
||||
|
||||
bestSize = progressive
|
||||
} else {
|
||||
let max: tl.RawPhotoSize | null = null
|
||||
for (const sz of raw.sizes) {
|
||||
|
@ -59,6 +65,8 @@ export class Photo extends FileLocation {
|
|||
size = max.size
|
||||
width = max.w
|
||||
height = max.h
|
||||
|
||||
bestSize = max
|
||||
} else {
|
||||
// does this happen at all?
|
||||
throw new MtCuteArgumentError('Photo does not have any sizes')
|
||||
|
@ -66,6 +74,7 @@ export class Photo extends FileLocation {
|
|||
}
|
||||
|
||||
super(client, location, size, raw.dcId)
|
||||
this._bestSize = bestSize
|
||||
this.raw = raw
|
||||
this.width = width
|
||||
this.height = height
|
||||
|
@ -106,6 +115,44 @@ export class Photo extends FileLocation {
|
|||
return this.thumbnails.find((it) => it.raw.type === type) ?? null
|
||||
}
|
||||
|
||||
private _fileId?: string
|
||||
/**
|
||||
* Get TDLib and Bot API compatible File ID
|
||||
* representing this photo's best thumbnail.
|
||||
*/
|
||||
get fileId(): string {
|
||||
if (!this._fileId) {
|
||||
if (!this._bestSize) {
|
||||
throw new MtCuteArgumentError(
|
||||
'Cannot get File ID for this photo'
|
||||
)
|
||||
}
|
||||
|
||||
this._fileId = this.getThumbnail(this._bestSize.type)!.fileId
|
||||
}
|
||||
|
||||
return this._fileId
|
||||
}
|
||||
|
||||
private _uniqueFileId?: string
|
||||
/**
|
||||
* Get TDLib and Bot API compatible Unique File ID
|
||||
* representing this photo's best thumbnail.
|
||||
*/
|
||||
get uniqueFileId(): string {
|
||||
if (!this._uniqueFileId) {
|
||||
if (!this._bestSize) {
|
||||
throw new MtCuteArgumentError(
|
||||
'Cannot get File ID for this photo'
|
||||
)
|
||||
}
|
||||
|
||||
this._uniqueFileId = this.getThumbnail(this._bestSize.type)!.uniqueFileId
|
||||
}
|
||||
|
||||
return this._uniqueFileId
|
||||
}
|
||||
|
||||
/**
|
||||
* Input media generated from this object,
|
||||
* to be used in {@link InputMediaLike} and
|
||||
|
|
|
@ -168,8 +168,6 @@ export class Thumbnail extends FileLocation {
|
|||
_: 'photo',
|
||||
id: this._media.id,
|
||||
accessHash: this._media.accessHash,
|
||||
volumeId: bigInt.zero,
|
||||
localId: 0,
|
||||
source: {
|
||||
_: 'thumbnail',
|
||||
fileType: td.FileType.Photo,
|
||||
|
@ -202,8 +200,13 @@ export class Thumbnail extends FileLocation {
|
|||
? td.FileType.Photo
|
||||
: td.FileType.Thumbnail,
|
||||
{
|
||||
_: 'common',
|
||||
_: 'photo',
|
||||
id: this._media.id,
|
||||
source: {
|
||||
_: 'thumbnail',
|
||||
fileType: td.FileType.Photo,
|
||||
thumbnailType: this.raw.type,
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -76,8 +76,6 @@ export class ChatPhotoSize extends FileLocation {
|
|||
_: 'photo',
|
||||
id: this.obj.photoId,
|
||||
accessHash: bigInt.zero,
|
||||
volumeId: bigInt.zero,
|
||||
localId: 0,
|
||||
source: {
|
||||
_: 'dialogPhoto',
|
||||
big: this.big,
|
||||
|
@ -97,12 +95,15 @@ export class ChatPhotoSize extends FileLocation {
|
|||
*/
|
||||
get uniqueFileId(): string {
|
||||
if (!this._uniqueFileId) {
|
||||
// todo: check how tdlib handles big/small photos here
|
||||
this._uniqueFileId = toUniqueFileId(
|
||||
tdFileId.FileType.ProfilePhoto,
|
||||
{
|
||||
_: 'common',
|
||||
_: 'photo',
|
||||
id: this.obj.photoId,
|
||||
source: {
|
||||
_: 'dialogPhoto',
|
||||
big: this.big
|
||||
} as any
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,13 +3,14 @@ import { tdFileId, tdFileId as td } from './types'
|
|||
import { parseFileId } from './parse'
|
||||
import { getBasicPeerType, markedPeerIdToBare } from '@mtcute/core'
|
||||
import FileType = tdFileId.FileType
|
||||
import bigInt from 'big-integer'
|
||||
|
||||
const EMPTY_BUFFER = Buffer.alloc(0)
|
||||
|
||||
type FileId = td.RawFullRemoteFileLocation
|
||||
|
||||
function dialogPhotoToInputPeer(
|
||||
dialog: td.RawPhotoSizeSourceDialogPhoto
|
||||
dialog: td.RawPhotoSizeSourceDialogPhoto | td.RawPhotoSizeSourceDialogPhotoLegacy
|
||||
): tl.TypeInputPeer {
|
||||
const markedPeerId = dialog.id.toJSNumber()
|
||||
const peerType = getBasicPeerType(markedPeerId)
|
||||
|
@ -78,13 +79,16 @@ export function fileIdToInputFileLocation(
|
|||
'Expected legacy photo to have file reference'
|
||||
)
|
||||
|
||||
// for some reason tdlib removed this thing altogether
|
||||
// https://github.com/tdlib/td/commit/4bb76a7b6f47bf9e0d0d01a72aac579ec73557ee#diff-8cc4f7c60a8261a8cf782e7fb51b95105bfb08710a1c2b63f80a48263ae0fb9bL401
|
||||
// still leaving this, but not sure if passing 0 as volume_id and local_id is correct at all lol
|
||||
return {
|
||||
_: 'inputPhotoLegacyFileLocation',
|
||||
fileReference: fileId.fileReference,
|
||||
id: loc.id,
|
||||
accessHash: loc.accessHash,
|
||||
volumeId: loc.volumeId,
|
||||
localId: loc.localId,
|
||||
volumeId: bigInt.zero,
|
||||
localId: 0,
|
||||
secret: loc.source.secret,
|
||||
}
|
||||
case 'thumbnail':
|
||||
|
@ -119,6 +123,9 @@ export function fileIdToInputFileLocation(
|
|||
photoId: loc.id,
|
||||
}
|
||||
case 'stickerSetThumbnail':
|
||||
// this was also removed from tdlib:
|
||||
// https://github.com/tdlib/td/commit/4bb76a7b6f47bf9e0d0d01a72aac579ec73557ee#diff-8cc4f7c60a8261a8cf782e7fb51b95105bfb08710a1c2b63f80a48263ae0fb9bR432
|
||||
// also leaving this one though.
|
||||
return {
|
||||
_: 'inputStickerSetThumb',
|
||||
stickerset: {
|
||||
|
@ -126,7 +133,51 @@ export function fileIdToInputFileLocation(
|
|||
id: loc.source.id,
|
||||
accessHash: loc.source.accessHash,
|
||||
},
|
||||
thumbVersion: 0, // todo: check how tdlib stores this
|
||||
thumbVersion: 0,
|
||||
}
|
||||
case 'fullLegacy':
|
||||
if (!fileId.fileReference)
|
||||
throw new td.InvalidFileIdError(
|
||||
'Expected legacy photo to have file reference'
|
||||
)
|
||||
|
||||
return {
|
||||
_: 'inputPhotoLegacyFileLocation',
|
||||
fileReference: fileId.fileReference,
|
||||
id: loc.id,
|
||||
accessHash: loc.accessHash,
|
||||
volumeId: loc.source.volumeId,
|
||||
localId: loc.source.localId,
|
||||
secret: loc.source.secret,
|
||||
}
|
||||
case 'dialogPhotoLegacy':
|
||||
return {
|
||||
_: 'inputPeerPhotoFileLocationLegacy',
|
||||
big: loc.source.big,
|
||||
peer: dialogPhotoToInputPeer(loc.source),
|
||||
volumeId: loc.source.volumeId,
|
||||
localId: loc.source.localId,
|
||||
}
|
||||
case 'stickerSetThumbnailLegacy':
|
||||
return {
|
||||
_: 'inputStickerSetThumbLegacy',
|
||||
stickerset: {
|
||||
_: 'inputStickerSetID',
|
||||
id: loc.source.id,
|
||||
accessHash: loc.source.accessHash,
|
||||
},
|
||||
volumeId: loc.source.volumeId,
|
||||
localId: loc.source.localId,
|
||||
}
|
||||
case 'stickerSetThumbnailVersion':
|
||||
return {
|
||||
_: 'inputStickerSetThumb',
|
||||
stickerset: {
|
||||
_: 'inputStickerSetID',
|
||||
id: loc.source.id,
|
||||
accessHash: loc.source.accessHash,
|
||||
},
|
||||
thumbVersion: loc.source.version
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,59 @@ function parsePhotoSizeSource(reader: BinaryReader): td.TypePhotoSizeSource {
|
|||
id: reader.long(),
|
||||
accessHash: reader.long(),
|
||||
}
|
||||
case 5 /* FULL_LEGACY */: {
|
||||
const res: td.RawPhotoSizeSourceFullLegacy = {
|
||||
_: 'fullLegacy',
|
||||
volumeId: reader.long(),
|
||||
secret: reader.long(),
|
||||
localId: reader.int32()
|
||||
}
|
||||
|
||||
if (res.localId < 0) {
|
||||
throw new td.InvalidFileIdError('Wrong local_id (< 0)')
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
case 6 /* DIALOG_PHOTO_SMALL_LEGACY */:
|
||||
case 7 /* DIALOG_PHOTO_BIG_LEGACY */: {
|
||||
const res: td.RawPhotoSizeSourceDialogPhotoLegacy = {
|
||||
_: 'dialogPhotoLegacy',
|
||||
big: variant === 7,
|
||||
id: reader.long(),
|
||||
accessHash: reader.long(),
|
||||
volumeId: reader.long(),
|
||||
localId: reader.int32()
|
||||
}
|
||||
|
||||
if (res.localId < 0) {
|
||||
throw new td.InvalidFileIdError('Wrong local_id (< 0)')
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
case 8 /* STICKERSET_THUMBNAIL_LEGACY */: {
|
||||
const res: td.RawPhotoSizeSourceStickerSetThumbnailLegacy = {
|
||||
_: 'stickerSetThumbnailLegacy',
|
||||
id: reader.long(),
|
||||
accessHash: reader.long(),
|
||||
volumeId: reader.long(),
|
||||
localId: reader.int32()
|
||||
}
|
||||
|
||||
if (res.localId < 0) {
|
||||
throw new td.InvalidFileIdError('Wrong local_id (< 0)')
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
case 9 /* STICKERSET_THUMBNAIL_VERSION */:
|
||||
return {
|
||||
_: 'stickerSetThumbnailVersion',
|
||||
id: reader.long(),
|
||||
accessHash: reader.long(),
|
||||
version: reader.int32()
|
||||
}
|
||||
default:
|
||||
throw new td.UnsupportedError(
|
||||
`Unsupported photo size source ${variant} (${reader.data.toString(
|
||||
|
@ -72,19 +125,70 @@ function parsePhotoFileLocation(
|
|||
version: number
|
||||
): td.RawPhotoRemoteFileLocation {
|
||||
// todo: check how tdlib handles volume ids
|
||||
|
||||
const id = reader.long()
|
||||
const accessHash = reader.long()
|
||||
let source: td.TypePhotoSizeSource
|
||||
|
||||
if (version >= 32) {
|
||||
source = parsePhotoSizeSource(reader)
|
||||
} else {
|
||||
const volumeId = reader.long()
|
||||
let localId = 0
|
||||
|
||||
if (version >= 22) {
|
||||
source = parsePhotoSizeSource(reader)
|
||||
localId = reader.int32()
|
||||
} else {
|
||||
source = {
|
||||
_: 'fullLegacy',
|
||||
secret: reader.long(),
|
||||
localId: reader.int32(),
|
||||
volumeId
|
||||
}
|
||||
}
|
||||
|
||||
switch (source._) {
|
||||
case 'legacy':
|
||||
source = {
|
||||
_: 'fullLegacy',
|
||||
secret: reader.long(),
|
||||
localId: reader.int32(),
|
||||
volumeId
|
||||
}
|
||||
break
|
||||
case 'fullLegacy':
|
||||
case 'thumbnail':
|
||||
break
|
||||
case 'dialogPhoto':
|
||||
source = {
|
||||
_: 'dialogPhotoLegacy',
|
||||
id: source.id,
|
||||
accessHash: source.accessHash,
|
||||
big: source.big,
|
||||
localId,
|
||||
volumeId
|
||||
}
|
||||
break
|
||||
case 'stickerSetThumbnail':
|
||||
source = {
|
||||
_: 'stickerSetThumbnailLegacy',
|
||||
id: source.id,
|
||||
accessHash: source.accessHash,
|
||||
localId,
|
||||
volumeId
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new td.InvalidFileIdError('Invalid PhotoSizeSource in legacy PhotoRemoteFileLocation')
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
_: 'photo',
|
||||
id: reader.long(),
|
||||
accessHash: reader.long(),
|
||||
volumeId: reader.long(),
|
||||
source:
|
||||
version >= 22
|
||||
? parsePhotoSizeSource(reader)
|
||||
: {
|
||||
_: 'legacy',
|
||||
secret: reader.long(),
|
||||
},
|
||||
localId: reader.int32(),
|
||||
id,
|
||||
accessHash,
|
||||
source
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,6 +270,7 @@ function fromPersistentIdV23(
|
|||
}
|
||||
break
|
||||
case 'dialogPhoto':
|
||||
case 'dialogPhotoLegacy':
|
||||
if (fileType !== td.FileType.ProfilePhoto) {
|
||||
throw new td.InvalidFileIdError(
|
||||
'Invalid FileType in PhotoRemoteFileLocation DialogPhoto'
|
||||
|
@ -173,6 +278,8 @@ function fromPersistentIdV23(
|
|||
}
|
||||
break
|
||||
case 'stickerSetThumbnail':
|
||||
case 'stickerSetThumbnailLegacy':
|
||||
case 'stickerSetThumbnailVersion':
|
||||
if (fileType !== td.FileType.Thumbnail) {
|
||||
throw new td.InvalidFileIdError(
|
||||
'Invalid FileType in PhotoRemoteFileLocation StickerSetThumbnail'
|
||||
|
|
|
@ -5,7 +5,7 @@ import FileType = tdFileId.FileType
|
|||
|
||||
type InputUniqueLocation =
|
||||
| Pick<td.RawWebRemoteFileLocation, '_' | 'url'>
|
||||
| Pick<td.RawPhotoRemoteFileLocation, '_' | 'volumeId' | 'localId'>
|
||||
| Pick<td.RawPhotoRemoteFileLocation, '_' | 'id' | 'source'>
|
||||
| Pick<td.RawCommonRemoteFileLocation, '_' | 'id'>
|
||||
|
||||
/**
|
||||
|
@ -76,12 +76,69 @@ export function toUniqueFileId(
|
|||
|
||||
let writer: BinaryWriter
|
||||
switch (inputLocation._) {
|
||||
case 'photo':
|
||||
writer = BinaryWriter.alloc(16)
|
||||
writer.int32(type)
|
||||
writer.long(inputLocation.volumeId)
|
||||
writer.int32(inputLocation.localId)
|
||||
case 'photo': {
|
||||
const source = inputLocation.source
|
||||
switch (source._) {
|
||||
case 'legacy': {
|
||||
// tdlib does not implement this
|
||||
writer = BinaryWriter.alloc(16)
|
||||
writer.int32(type)
|
||||
writer.int32(100)
|
||||
writer.long(source.secret)
|
||||
break
|
||||
}
|
||||
case 'stickerSetThumbnail': {
|
||||
// tdlib does not implement this
|
||||
writer = BinaryWriter.alloc(24)
|
||||
writer.int32(type)
|
||||
writer.int32(150)
|
||||
writer.long(source.id)
|
||||
writer.long(source.accessHash)
|
||||
break
|
||||
}
|
||||
case 'dialogPhoto': {
|
||||
writer = BinaryWriter.alloc(13)
|
||||
writer.int32(type)
|
||||
writer.long(inputLocation.id)
|
||||
writer.raw(Buffer.from([+source.big]))
|
||||
// it doesn't matter to which Dialog the photo belongs
|
||||
break
|
||||
}
|
||||
case 'thumbnail': {
|
||||
writer = BinaryWriter.alloc(13)
|
||||
|
||||
let thumbType = source.thumbnailType.charCodeAt(0)
|
||||
if (thumbType === 97 /* 'a' */) {
|
||||
thumbType = 0
|
||||
} else if (thumbType === 99 /* 'c' */) {
|
||||
thumbType = 1
|
||||
} else {
|
||||
thumbType += 5
|
||||
}
|
||||
|
||||
writer.int32(type)
|
||||
writer.long(inputLocation.id)
|
||||
writer.raw(Buffer.from([thumbType]))
|
||||
break
|
||||
}
|
||||
case 'fullLegacy':
|
||||
case 'dialogPhotoLegacy':
|
||||
case 'stickerSetThumbnailLegacy':
|
||||
writer = BinaryWriter.alloc(16)
|
||||
writer.int32(type)
|
||||
writer.long(source.volumeId)
|
||||
writer.int32(source.localId)
|
||||
break
|
||||
case 'stickerSetThumbnailVersion':
|
||||
writer = BinaryWriter.alloc(17)
|
||||
writer.int32(type)
|
||||
writer.raw(Buffer.from([2]))
|
||||
writer.long(source.id)
|
||||
writer.int32(source.version)
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'web':
|
||||
writer = BinaryWriter.alloc(
|
||||
Buffer.byteLength(inputLocation.url, 'utf-8') + 8
|
||||
|
|
|
@ -46,7 +46,6 @@ export function toFileId(
|
|||
// todo: check how tdlib handles volume ids
|
||||
writer.long(loc.id)
|
||||
writer.long(loc.accessHash)
|
||||
writer.long(loc.volumeId)
|
||||
|
||||
switch (loc.source._) {
|
||||
case 'legacy':
|
||||
|
@ -68,9 +67,34 @@ export function toFileId(
|
|||
writer.long(loc.source.id)
|
||||
writer.long(loc.source.accessHash)
|
||||
break
|
||||
case 'fullLegacy':
|
||||
writer.int32(5)
|
||||
writer.long(loc.source.volumeId)
|
||||
writer.long(loc.source.secret)
|
||||
writer.int32(loc.source.localId)
|
||||
break
|
||||
case 'dialogPhotoLegacy':
|
||||
writer.int32(loc.source.big ? 7 : 6)
|
||||
writer.long(loc.source.id)
|
||||
writer.long(loc.source.accessHash)
|
||||
writer.long(loc.source.volumeId)
|
||||
writer.int32(loc.source.localId)
|
||||
break
|
||||
case 'stickerSetThumbnailLegacy':
|
||||
writer.int32(8)
|
||||
writer.long(loc.source.id)
|
||||
writer.long(loc.source.accessHash)
|
||||
writer.long(loc.source.volumeId)
|
||||
writer.int32(loc.source.localId)
|
||||
break
|
||||
case 'stickerSetThumbnailVersion':
|
||||
writer.int32(8)
|
||||
writer.long(loc.source.id)
|
||||
writer.long(loc.source.accessHash)
|
||||
writer.int32(loc.source.version)
|
||||
break
|
||||
}
|
||||
|
||||
writer.int32(loc.localId)
|
||||
break
|
||||
case 'common':
|
||||
writer.long(loc.id)
|
||||
|
|
|
@ -9,7 +9,7 @@ export namespace tdFileId {
|
|||
export const WEB_LOCATION_FLAG = 1 << 24
|
||||
export const FILE_REFERENCE_FLAG = 1 << 25
|
||||
|
||||
export const CURRENT_VERSION = 31
|
||||
export const CURRENT_VERSION = 32
|
||||
|
||||
/**
|
||||
* An error occurred while parsing or serializing a File ID
|
||||
|
@ -114,11 +114,55 @@ export namespace tdFileId {
|
|||
readonly accessHash: Long
|
||||
}
|
||||
|
||||
/**
|
||||
* This photo is a legacy photo containing
|
||||
* volume_id, local_id and secret
|
||||
*/
|
||||
export interface RawPhotoSizeSourceFullLegacy {
|
||||
readonly _: 'fullLegacy'
|
||||
readonly volumeId: Long
|
||||
readonly localId: number
|
||||
readonly secret: Long
|
||||
}
|
||||
|
||||
/**
|
||||
* This photo is a legacy dialog photo
|
||||
*/
|
||||
export interface RawPhotoSizeSourceDialogPhotoLegacy
|
||||
extends Omit<RawPhotoSizeSourceDialogPhoto, '_'> {
|
||||
readonly _: 'dialogPhotoLegacy'
|
||||
readonly volumeId: Long
|
||||
readonly localId: number
|
||||
}
|
||||
|
||||
/**
|
||||
* This photo is a legacy sticker set thumbnail
|
||||
*/
|
||||
export interface RawPhotoSizeSourceStickerSetThumbnailLegacy
|
||||
extends Omit<RawPhotoSizeSourceStickerSetThumbnail, '_'> {
|
||||
readonly _: 'stickerSetThumbnailLegacy'
|
||||
readonly volumeId: Long
|
||||
readonly localId: number
|
||||
}
|
||||
|
||||
/**
|
||||
* This photo is a legacy sticker set identified by a version
|
||||
*/
|
||||
export interface RawPhotoSizeSourceStickerSetThumbnailVersion
|
||||
extends Omit<RawPhotoSizeSourceStickerSetThumbnail, '_'> {
|
||||
readonly _: 'stickerSetThumbnailVersion'
|
||||
readonly version: number
|
||||
}
|
||||
|
||||
export type TypePhotoSizeSource =
|
||||
| RawPhotoSizeSourceLegacy
|
||||
| RawPhotoSizeSourceThumbnail
|
||||
| RawPhotoSizeSourceDialogPhoto
|
||||
| RawPhotoSizeSourceStickerSetThumbnail
|
||||
| RawPhotoSizeSourceFullLegacy
|
||||
| RawPhotoSizeSourceDialogPhotoLegacy
|
||||
| RawPhotoSizeSourceStickerSetThumbnailLegacy
|
||||
| RawPhotoSizeSourceStickerSetThumbnailVersion
|
||||
|
||||
/**
|
||||
* An external web file
|
||||
|
@ -139,9 +183,7 @@ export namespace tdFileId {
|
|||
readonly _: 'photo'
|
||||
readonly id: Long
|
||||
readonly accessHash: Long
|
||||
readonly volumeId: Long
|
||||
readonly source: TypePhotoSizeSource
|
||||
readonly localId: number
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue