Каналы: типы 0/1/100/200, CreateChannel v3, state для chat200, новые API и деплой на prod

This commit is contained in:
AidarKC
2026-05-13 01:17:23 +03:00
parent 97d37a2eb6
commit e95f65ac78
36 changed files with 1764 additions and 187 deletions
+34 -10
View File
@@ -42,7 +42,12 @@ const MSG_SUBTYPE_REACTION_LIKE = 1;
const MSG_SUBTYPE_REACTION_UNLIKE = 2;
const MSG_SUBTYPE_CONNECTION_FOLLOW = 30;
const MSG_SUBTYPE_CONNECTION_UNFOLLOW = 31;
const CREATE_CHANNEL_BODY_VERSION = 2;
const CREATE_CHANNEL_BODY_VERSION = 3;
const CHANNEL_TYPE_STORIES = 0;
const CHANNEL_TYPE_PUBLIC = 1;
const CHANNEL_TYPE_PERSONAL = 100;
const CHANNEL_TYPE_GROUP = 200;
const CHANNEL_TYPE_VERSION_DEFAULT = 1;
const CONNECTION_SUBTYPES = Object.freeze({
// Legacy alias: friend == close_friend. Оба ключа ведут на один и тот же код 10/11.
@@ -404,13 +409,15 @@ function normalizeChannelDescription(value) {
return text;
}
function makeCreateChannelBodyV2Bytes({
function makeCreateChannelBodyV3Bytes({
lineCode,
prevLineNumber,
prevLineHashHex,
thisLineNumber,
channelName,
channelDescription = '',
channelType = CHANNEL_TYPE_PUBLIC,
channelTypeVersion = CHANNEL_TYPE_VERSION_DEFAULT,
}) {
const check = validateChannelDisplayName(channelName);
if (!check.ok) throw new Error(channelNameErrorText(check.code));
@@ -426,6 +433,14 @@ function makeCreateChannelBodyV2Bytes({
if (descriptionBytes.length > 200) {
throw new Error('Описание канала слишком длинное: максимум 200 символов.');
}
const typeCode = Number(channelType);
const typeVer = Number(channelTypeVersion);
if (!Number.isFinite(typeCode) || typeCode < 0 || typeCode > 65535) {
throw new Error('Некорректный тип канала.');
}
if (!Number.isFinite(typeVer) || typeVer < 0 || typeVer > 65535) {
throw new Error('Некорректная версия типа канала.');
}
return concatBytes(
int32Bytes(lineCode),
@@ -436,6 +451,8 @@ function makeCreateChannelBodyV2Bytes({
nameBytes,
int16Bytes(descriptionBytes.length),
descriptionBytes,
uint16Bytes(typeCode),
uint16Bytes(typeVer),
);
}
@@ -1004,11 +1021,19 @@ export class AuthService {
rootBlockNumber: Number(item?.channel?.channelRoot?.blockNumber),
rootBlockHash: normalizeHex32(item?.channel?.channelRoot?.blockHash, ZERO64),
channelName: String(item?.channel?.channelName || ''),
channelTypeCode: Number(item?.channel?.channelTypeCode ?? CHANNEL_TYPE_PUBLIC),
}))
.filter((item) => Number.isFinite(item.rootBlockNumber) && item.rootBlockNumber >= 0);
}
async addBlockCreateChannel({ login, channelName, channelDescription = '', storagePwd }) {
async addBlockCreateChannel({
login,
channelName,
channelDescription = '',
channelType = CHANNEL_TYPE_PUBLIC,
channelTypeVersion = CHANNEL_TYPE_VERSION_DEFAULT,
storagePwd,
}) {
const cleanLogin = (login || '').trim();
if (!cleanLogin) throw new Error('Missing login');
@@ -1018,7 +1043,9 @@ export class AuthService {
const cleanChannelDescription = normalizeChannelDescription(channelDescription);
const channelSlug = toCanonicalChannelSlug(cleanChannelName);
const key = `create-channel:${cleanLogin}:${channelSlug || cleanChannelName.toLowerCase()}`;
const typeCode = Number(channelType);
const typeVersion = Number(channelTypeVersion);
const key = `create-channel:${cleanLogin}:${typeCode}:${channelSlug || cleanChannelName.toLowerCase()}`;
return this.runWriteLocked(key, async () => {
const user = await this.ensureChainInitializedForLineOps(cleanLogin, storagePwd);
const blockchainName = String(user?.blockchainName || `${cleanLogin}-${BCH_SUFFIX}`).trim();
@@ -1053,13 +1080,15 @@ export class AuthService {
msgType: MSG_TYPE_TECH,
msgSubType: MSG_SUBTYPE_TECH_CREATE_CHANNEL,
msgVersion: CREATE_CHANNEL_BODY_VERSION,
bodyBytes: makeCreateChannelBodyV2Bytes({
bodyBytes: makeCreateChannelBodyV3Bytes({
lineCode: 0,
prevLineNumber,
prevLineHashHex,
thisLineNumber,
channelName: cleanChannelName,
channelDescription: cleanChannelDescription,
channelType: typeCode,
channelTypeVersion: typeVersion,
}),
});
@@ -1099,11 +1128,6 @@ export class AuthService {
if (ownerBlockchainName !== blockchainName) {
throw new Error('Posting is allowed only to your own channels');
}
// Канал 0 оставляем как технический root-поток.
// Контент-публикации в него временно отключены (пишем только в именованные каналы).
if (lineCode === 0) {
throw new Error('Публикации в канал 0 временно отключены. Создайте отдельный канал.');
}
let rootHashHex = normalizeHex32(selector?.channelRootBlockHash, ZERO64);
if (rootHashHex === ZERO64) {