This commit is contained in:
rosetta
2026-01-30 05:01:05 +02:00
commit 83f38dc63f
327 changed files with 18725 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,28 @@
import Stream from "./stream";
/**
* Packet abstract class
*/
export default abstract class Packet {
/**
* Get the packet ID
* @returns packet ID
*/
public abstract getPacketId(): number;
/**
* Use the stream to read the packet and fill structure
* @param stream stream
*/
public abstract _receive(stream: Stream): void;
/**
* Use the stream to write the packet and return the stream
* @returns stream
*/
public abstract _send(): Promise<Stream> | Stream;
public clone(): Packet {
return new (this as any).constructor();
}
}

View File

@@ -0,0 +1,54 @@
import Packet from "../packet";
import Stream from "../stream";
export class PacketAppUpdate extends Packet {
private url: string = "";
private version: string = "";
private kernelVersionRequired: string = "";
public getPacketId(): number {
return 0x0E;
}
public _receive(stream: Stream): void {
this.url = stream.readString();
this.version = stream.readString();
this.kernelVersionRequired = stream.readString();
}
public _send(): Promise<Stream> | Stream {
let stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.url);
stream.writeString(this.version);
stream.writeString(this.kernelVersionRequired);
return stream;
}
public getUrl(): string {
return this.url;
}
public setUrl(url: string): void {
this.url = url;
}
public getVersion(): string {
return this.version;
}
public setVersion(version: string): void {
this.version = version;
}
public getKernelVersionRequired(): string {
return this.kernelVersionRequired;
}
public setKernelVersionRequired(kernelVersionRequired: string): void {
this.kernelVersionRequired = kernelVersionRequired;
}
}

View File

@@ -0,0 +1,77 @@
import Packet from "../packet";
import Stream from "../stream";
export class PacketAvatar extends Packet {
private privateKey : string = "";
private fromPublicKey: string = "";
private toPublicKey: string = "";
private blob: string = "";
private chachaKey: string = "";
public getPacketId(): number {
return 0x0C;
}
public _receive(stream: Stream): void {
this.privateKey = stream.readString();
this.fromPublicKey = stream.readString();
this.toPublicKey = stream.readString();
this.chachaKey = stream.readString();
this.blob = stream.readString();
}
public _send(): Promise<Stream> | Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.privateKey);
stream.writeString(this.fromPublicKey);
stream.writeString(this.toPublicKey);
stream.writeString(this.chachaKey);
stream.writeString(this.blob);
return stream;
}
public setFromPublicKey(fromPublicKey: string): void {
this.fromPublicKey = fromPublicKey;
}
public setToPublicKey(toPublicKey: string): void {
this.toPublicKey = toPublicKey;
}
public getFromPublicKey(): string {
return this.fromPublicKey;
}
public getToPublicKey(): string {
return this.toPublicKey;
}
public setPrivateKey(hash: string): void {
this.privateKey = hash;
}
public getPrivateKey(): string {
return this.privateKey;
}
public setBlob(blob: string): void {
this.blob = blob;
}
public getBlob(): string {
return this.blob;
}
public setChachaKey(key: string): void {
this.chachaKey = key;
}
public getChachaKey(): string {
return this.chachaKey;
}
}

View File

@@ -0,0 +1,31 @@
import Packet from "../packet";
import Stream from "../stream";
export class PacketCreateGroup extends Packet {
private groupId: string = "";
public getPacketId(): number {
return 0x11;
}
public _receive(stream: Stream): void {
this.groupId = stream.readString();
}
public _send(): Promise<Stream> | Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.groupId);
return stream;
}
public setGroupId(groupId: string) {
this.groupId = groupId;
}
public getGroupId(): string {
return this.groupId;
}
}

View File

@@ -0,0 +1,42 @@
import Packet from "../packet";
import Stream from "../stream";
export class PacketDelivery extends Packet {
private messageId: string = "";
private toPublicKey: string = "";
public getPacketId(): number {
return 0x08;
}
public _receive(stream: Stream): void {
this.toPublicKey = stream.readString();
this.messageId = stream.readString();
}
public _send(): Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.toPublicKey);
stream.writeString(this.messageId);
return stream;
}
public setMessageId(messageId: string) {
this.messageId = messageId;
}
public getMessageId(): string {
return this.messageId;
}
public setToPublicKey(toPublicKey: string) {
this.toPublicKey = toPublicKey;
}
public getToPublicKey(): string {
return this.toPublicKey;
}
}

View File

@@ -0,0 +1,42 @@
import Packet from "../packet";
import Stream from "../stream";
export class PacketGroupBan extends Packet {
private groupId: string = "";
private publicKey: string = "";
public getPacketId(): number {
return 0x16;
}
public _receive(stream: Stream): void {
this.groupId = stream.readString();
this.publicKey = stream.readString();
}
public _send(): Promise<Stream> | Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.groupId);
stream.writeString(this.publicKey);
return stream;
}
public getGroupId(): string {
return this.groupId;
}
public getPublicKey(): string {
return this.publicKey;
}
public setGroupId(groupId: string): void {
this.groupId = groupId;
}
public setPublicKey(publicKey: string): void {
this.publicKey = publicKey;
}
}

View File

@@ -0,0 +1,50 @@
import Packet from "../packet";
import Stream from "../stream";
export class PacketGroupInfo extends Packet {
private groupId: string = "";
private members: string[] = [];
public getPacketId(): number {
return 0x12;
}
public _receive(stream: Stream): void {
this.groupId = stream.readString();
const membersCount = stream.readInt16();
this.members = [];
for(let i = 0; i < membersCount; i++) {
this.members.push(stream.readString());
}
}
public _send(): Promise<Stream> | Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.groupId);
stream.writeInt16(this.members.length);
this.members.forEach((member) => {
stream.writeString(member);
});
return stream;
}
public getGroupId(): string {
return this.groupId;
}
public setGroupId(groupId: string): void {
this.groupId = groupId;
}
public setMembers(members: string[]): void {
this.members = members;
}
public getMembers(): string[] {
return this.members;
}
}

View File

@@ -0,0 +1,60 @@
import Packet from "../packet"
import Stream from "../stream";
export enum GroupStatus {
JOINED = 0,
INVALID = 1,
NOT_JOINED = 2,
BANNED = 3
}
export class PacketGroupInviteInfo extends Packet {
private groupId: string = "";
private membersCount = 0;
private groupStatus: GroupStatus = GroupStatus.NOT_JOINED;
public getPacketId(): number {
return 0x13;
}
public _receive(stream: Stream): void {
this.groupId = stream.readString();
this.membersCount = stream.readInt16();
this.groupStatus = stream.readInt8();
}
public _send(): Promise<Stream> | Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.groupId);
stream.writeInt16(this.membersCount);
stream.writeInt8(this.groupStatus);
return stream;
}
public setGroupId(groupId: string) {
this.groupId = groupId;
}
public getGroupId(): string {
return this.groupId;
}
public setMembersCount(count: number) {
this.membersCount = count;
}
public getMembersCount(): number {
return this.membersCount;
}
public setGroupStatus(status: GroupStatus) {
this.groupStatus = status;
}
public getGroupStatus(): GroupStatus {
return this.groupStatus;
}
}

View File

@@ -0,0 +1,49 @@
import Packet from "../packet";
import Stream from "../stream";
export enum GroupStatus {
JOINED = 0,
INVALID = 1,
NOT_JOINED = 2,
BANNED = 3
}
export class PacketGroupJoin extends Packet {
private groupId: string = "";
private groupStatus: GroupStatus = GroupStatus.NOT_JOINED;
public getPacketId(): number {
return 0x14;
}
public _receive(stream: Stream): void {
this.groupId = stream.readString();
this.groupStatus = stream.readInt8();
}
public _send(): Promise<Stream> | Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.groupId);
stream.writeInt8(this.groupStatus);
return stream;
}
public setGroupId(groupId: string) {
this.groupId = groupId;
}
public getGroupId(): string {
return this.groupId;
}
public setGroupStatus(groupStatus: GroupStatus) {
this.groupStatus = groupStatus;
}
public getGroupStatus(): GroupStatus {
return this.groupStatus;
}
}

View File

@@ -0,0 +1,31 @@
import Packet from "../packet";
import Stream from "../stream";
export class PacketGroupLeave extends Packet {
private groupId: string = "";
public getPacketId(): number {
return 0x15;
}
public _receive(stream: Stream): void {
this.groupId = stream.readString();
}
public _send(): Promise<Stream> | Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.groupId);
return stream;
}
public setGroupId(groupId: string) {
this.groupId = groupId;
}
public getGroupId(): string {
return this.groupId;
}
}

View File

@@ -0,0 +1,113 @@
import Packet from "../packet";
import Stream from "../stream";
export enum HandshakeState {
COMPLETED,
NEED_DEVICE_VERIFICATION
}
export interface Device {
deviceId: string;
deviceName: string;
}
/**
* Hadshake packet
* ID: 0x00
*
* The handshake packet is the first packet sent by the client to the server.
* It contains the hash of the client's public key and the public key itself.
*/
export default class PacketHandshake extends Packet {
private privateKey: string = "";
private publicKey: string = "";
private protocolVersion: number = 1;
/**
* Interval seconds
*/
private heartbeatInterval : number = 15;
private device: Device = {
deviceId: "",
deviceName: ""
};
private handshakeState:
HandshakeState = HandshakeState.NEED_DEVICE_VERIFICATION;
public getPacketId(): number {
return 0x00;
}
public _receive(stream: Stream): void {
this.privateKey = stream.readString();
this.publicKey = stream.readString();
this.protocolVersion = stream.readInt8();
this.heartbeatInterval = stream.readInt8();
this.device = {
deviceId: stream.readString(),
deviceName: stream.readString()
}
this.handshakeState = stream.readInt8();
}
public _send(): Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.privateKey);
stream.writeString(this.publicKey);
stream.writeInt8(this.protocolVersion);
stream.writeInt8(this.heartbeatInterval);
stream.writeString(this.device.deviceId);
stream.writeString(this.device.deviceName);
stream.writeInt8(this.handshakeState);
return stream;
}
public getPrivateKey(): string {
return this.privateKey;
}
public getPublicKey(): string {
return this.publicKey;
}
public setPrivateKey(privateKey: string): void {
this.privateKey = privateKey;
}
public setPublicKey(publicKey: string): void {
this.publicKey = publicKey;
}
public getProtocolVersion(): number {
return this.protocolVersion;
}
public setProtocolVersion(protocolVersion: number): void {
this.protocolVersion = protocolVersion;
}
public getHeartbeatInterval(): number {
return this.heartbeatInterval;
}
public setHeartbeatInterval(heartbeatInterval: number): void {
this.heartbeatInterval = heartbeatInterval;
}
public getDevice(): Device {
return this.device;
}
public setDevice(device: Device): void {
this.device = device;
}
public getHandshakeState(): HandshakeState {
return this.handshakeState;
}
public setHandshakeState(handshakeState: HandshakeState): void {
this.handshakeState = handshakeState;
}
}

View File

@@ -0,0 +1,43 @@
import Packet from "../packet";
import Stream from "../stream";
export class PacketKernelUpdate extends Packet {
private url: string = "";
private version: string = "";
public getPacketId(): number {
return 0x0D;
}
public _receive(stream: Stream): void {
this.url = stream.readString();
this.version = stream.readString();
}
public _send(): Promise<Stream> | Stream {
let stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.url);
stream.writeString(this.version);
return stream;
}
public getUrl(): string {
return this.url;
}
public setUrl(url: string): void {
this.url = url;
}
public getVersion(): string {
return this.version;
}
public setVersion(version: string): void {
this.version = version;
}
}

View File

@@ -0,0 +1,150 @@
import Packet from "../packet";
import Stream from "../stream";
export enum AttachmentType {
IMAGE = 0,
MESSAGES = 1,
FILE = 2,
AVATAR = 3
}
export interface Attachment {
id: string;
blob: string;
type: AttachmentType;
preview: string;
}
export class PacketMessage extends Packet {
private fromPublicKey: string = "";
private toPublicKey: string = "";
private content: string = "";
private chachaKey: string = "";
private timestamp: number = 0;
private privateKey: string = "";
private messageId: string = "";
/**
* Закодированный с помощью AES ключ chacha, нужен
* для последующей синхронизации своих же сообщений
*/
private aesChachaKey: string = "";
private attachments: Attachment[] = [];
public getPacketId(): number {
return 0x06;
}
public _receive(stream: Stream): void {
this.fromPublicKey = stream.readString();
this.toPublicKey = stream.readString();
this.content = stream.readString();
this.chachaKey = stream.readString();
this.timestamp = stream.readInt64();
this.privateKey = stream.readString();
this.messageId = stream.readString();
let attachmentsCount = stream.readInt8();
for(let i = 0; i < attachmentsCount; i++){
let id = stream.readString();
let preview = stream.readString();
let blob = stream.readString();
let type = stream.readInt8() as AttachmentType;
this.attachments.push({id, preview, type, blob});
}
this.aesChachaKey = stream.readString();
}
public async _send(): Promise<Stream> {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.fromPublicKey);
stream.writeString(this.toPublicKey);
stream.writeString(this.content);
stream.writeString(this.chachaKey);
stream.writeInt64(this.timestamp);
stream.writeString(this.privateKey);
stream.writeString(this.messageId);
stream.writeInt8(this.attachments.length);
for(let i = 0; i < this.attachments.length; i++){
stream.writeString(this.attachments[i].id);
stream.writeString(this.attachments[i].preview);
stream.writeString(this.attachments[i].blob);
stream.writeInt8(this.attachments[i].type);
}
stream.writeString(this.aesChachaKey);
return stream;
}
public setAttachments(attachments: Attachment[]): void {
this.attachments = attachments;
}
public getAttachments(): Attachment[] {
return this.attachments;
}
public setMessageId(messageId: string): void {
this.messageId = messageId;
}
public getMessageId(): string {
return this.messageId;
}
public setTimestamp(timestamp: number): void {
this.timestamp = timestamp;
}
public getTimestamp() : number {
return this.timestamp;
}
public getFromPublicKey(): string {
return this.fromPublicKey;
}
public getToPublicKey(): string {
return this.toPublicKey;
}
public getContent(): string {
return this.content;
}
public getChachaKey(): string {
return this.chachaKey;
}
public setFromPublicKey(fromPublicKey: string): void {
this.fromPublicKey = fromPublicKey;
}
public setToPublicKey(toPublicKey: string): void {
this.toPublicKey = toPublicKey;
}
public setContent(content: string): void {
this.content = content;
}
public setChachaKey(chachaKey: string): void {
this.chachaKey = chachaKey;
}
public setPrivateKey(privateKey: string): void {
this.privateKey = privateKey;
}
public getPrivateKey(): string {
return this.privateKey;
}
public getAesChachaKey() : string {
return this.aesChachaKey;
}
public setAesChachaKey(aesChachaKey: string) {
this.aesChachaKey = aesChachaKey;
}
}

View File

@@ -0,0 +1,58 @@
import Packet from "../packet";
import Stream from "../stream";
export enum OnlineState {
ONLINE,
OFFLINE
}
export interface PublicKeyOnlineState {
publicKey: string;
state: OnlineState;
}
export class PacketOnlineState extends Packet {
private publicKeysState: PublicKeyOnlineState[] = [];
public getPacketId(): number {
return 0x05;
}
public _receive(stream: Stream): void {
const publicKeyCount = stream.readInt8();
for (let i = 0; i < publicKeyCount; i++) {
const publicKey = stream.readString();
const state = stream.readBoolean() ? OnlineState.ONLINE : OnlineState.OFFLINE;
this.publicKeysState.push({ publicKey, state });
}
}
public _send(): Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeInt8(this.publicKeysState.length);
for (const publicKeyState of this.publicKeysState) {
stream.writeString(publicKeyState.publicKey);
stream.writeBoolean(publicKeyState.state === OnlineState.ONLINE);
}
return stream;
}
public addPublicKeyState(publicKey: string, state: OnlineState): void {
this.publicKeysState.push({ publicKey, state });
}
public getPublicKeysState(): PublicKeyOnlineState[] {
return this.publicKeysState;
}
public setPublicKeysState(publicKeysState: PublicKeyOnlineState[]): void {
this.publicKeysState = publicKeysState;
}
}

View File

@@ -0,0 +1,59 @@
import Packet from "../packet";
import Stream from "../stream";
export class PacketOnlineSubscribe extends Packet {
private privateKey : string = "";
private publicKeys: string[] = [];
public getPacketId(): number {
return 0x04;
}
public _receive(stream: Stream): void {
this.privateKey = stream.readString();
const keysCount = stream.readInt16();
for (let i = 0; i < keysCount; i++) {
this.publicKeys.push(stream.readString());
}
}
public _send(): Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.privateKey);
stream.writeInt16(this.publicKeys.length);
for (const key of this.publicKeys) {
stream.writeString(key);
}
return stream;
}
public getPrivateKey(): string {
return this.privateKey;
}
public setPrivateKey(privateKey: string): void {
this.privateKey = privateKey;
}
public getPublicKeys(): string[] {
return this.publicKeys;
}
public setPublicKeys(publicKeys: string[]): void {
this.publicKeys = publicKeys;
}
public addPublicKey(publicKey: string): void {
this.publicKeys.push(publicKey);
}
public removePublicKey(publicKey: string): void {
const index = this.publicKeys.indexOf(publicKey);
if (index > -1) {
this.publicKeys.splice(index, 1);
}
}
}

View File

@@ -0,0 +1,60 @@
import Packet from "../packet";
import Stream from "../stream";
/**
* Push Notification actions
* SUBSCRIBE - подписаться на push-уведомления
* UNSUBSCRIBE - отписаться от push-уведомлений
*/
export enum PushNotificationAction {
SUBSCRIBE = 0,
UNSUBSCRIBE = 1,
}
/**
* Push Notification packet
* ID: 0x10
*
* Этот пакет отправляется клиентом для подписки на push-уведомления.
* Отправлять можно только в том случае, если пользователь уже прошел
* рукопожатие и установил соединение.
*/
export class PacketPushNotification extends Packet {
private notificationsToken: string = "";
private action: PushNotificationAction = PushNotificationAction.SUBSCRIBE;
public getPacketId(): number {
return 0x10;
}
public _receive(stream: Stream): void {
this.notificationsToken = stream.readString();
this.action = stream.readInt8();
}
public _send(): Promise<Stream> | Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.notificationsToken);
stream.writeInt8(this.action);
return stream;
}
public getNotificationsToken(): string {
return this.notificationsToken;
}
public setNotificationsToken(notificationsToken: string): void {
this.notificationsToken = notificationsToken;
}
public getAction(): PushNotificationAction {
return this.action;
}
public setAction(action: PushNotificationAction): void {
this.action = action;
}
}

View File

@@ -0,0 +1,52 @@
import Packet from "../packet";
import Stream from "../stream";
export class PacketRead extends Packet {
private privateKey : string = "";
private fromPublicKey: string = "";
private toPublicKey: string = "";
public getPacketId(): number {
return 0x07;
}
public _receive(stream: Stream): void {
this.privateKey = stream.readString();
this.fromPublicKey = stream.readString();
this.toPublicKey = stream.readString();
}
public _send(): Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.privateKey);
stream.writeString(this.fromPublicKey);
stream.writeString(this.toPublicKey);
return stream;
}
public setFromPublicKey(fromPublicKey: string): void {
this.fromPublicKey = fromPublicKey;
}
public setToPublicKey(toPublicKey: string): void {
this.toPublicKey = toPublicKey;
}
public getFromPublicKey(): string {
return this.fromPublicKey;
}
public getToPublicKey(): string {
return this.toPublicKey;
}
public setPrivateKey(privateKey: string): void {
this.privateKey = privateKey;
}
public getPrivateKey(): string {
return this.privateKey;
}
}

View File

@@ -0,0 +1,32 @@
import Packet from "../packet";
import Stream from "../stream";
export class PacketRequestTransport extends Packet {
private transportServer: string = "";
public getPacketId(): number {
return 0x0F;
}
public _receive(stream: Stream): void {
this.transportServer = stream.readString();
}
public _send(): Promise<Stream> | Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.transportServer);
return stream;
}
public getTransportServer(): string {
return this.transportServer;
}
public setTransportServer(transportServer: string): void {
this.transportServer = transportServer;
}
}

View File

@@ -0,0 +1,65 @@
import Packet from "../packet";
import Stream from "../stream";
export class PacketRequestUpdate extends Packet {
private kernelVersion: string = "";
private appVersion: string = "";
private arch: string = "";
private platform: string = "";
public getPacketId(): number {
return 0xA;
}
public _receive(stream: Stream): void {
this.kernelVersion = stream.readString();
this.appVersion = stream.readString();
this.arch = stream.readString();
this.platform = stream.readString();
}
public _send(): Promise<Stream> | Stream {
let stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.kernelVersion);
stream.writeString(this.appVersion);
stream.writeString(this.arch);
stream.writeString(this.platform);
return stream;
}
public setKernelVersion(version: string): void {
this.kernelVersion = version;
}
public getKernelVersion(): string {
return this.kernelVersion;
}
public setAppVersion(version: string): void {
this.appVersion = version;
}
public getAppVersion(): string {
return this.appVersion;
}
public setArch(arch: string): void {
this.arch = arch;
}
public getArch(): string {
return this.arch;
}
public setPlatform(platform: string): void {
this.platform = platform;
}
public getPlatform(): string {
return this.platform;
}
}

View File

@@ -0,0 +1,38 @@
import Packet from "../packet";
import Stream from "../stream";
export enum ResultCode {
SUCCESS = 0,
ERROR = 1,
INVALID = 2,
USERNAME_TAKEN = 3,
}
export class PacketResult extends Packet {
private resultCode: ResultCode = 0;
public getPacketId(): number {
return 0x02;
}
public _receive(stream: Stream): void {
this.resultCode = stream.readInt16();
}
public _send(): Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeInt16(this.resultCode);
return stream;
}
public getResultCode(): ResultCode {
return this.resultCode;
}
public setResultCode(resultCode: ResultCode): void {
this.resultCode = resultCode;
}
}

View File

@@ -0,0 +1,77 @@
import Packet from "../packet";
import Stream from "../stream";
import { OnlineState } from "./packet.onlinestate";
export interface PacketSearchUser {
username: string;
title: string;
publicKey: string;
verified: number;
online: OnlineState;
}
export class PacketSearch extends Packet {
private privateKey : string = "";
private search : string = "";
private users : PacketSearchUser[] = [];
public getPacketId(): number {
return 0x03;
}
public _receive(stream: Stream): void {
this.privateKey = stream.readString();
this.search = stream.readString();
const userCount = stream.readInt16();
for (let i = 0; i < userCount; i++) {
const username = stream.readString();
const title = stream.readString();
const publicKey = stream.readString();
const verified = stream.readInt8();
const online = stream.readInt8();
this.users.push({ username, title, publicKey, online, verified });
}
}
public _send(): Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.privateKey);
stream.writeString(this.search);
stream.writeInt16(this.users.length);
for (const user of this.users) {
stream.writeString(user.username);
stream.writeString(user.title);
stream.writeString(user.publicKey);
stream.writeInt8(user.verified);
stream.writeInt8(user.online);
}
return stream;
}
public getPrivateKey(): string {
return this.privateKey;
}
public setPrivateKey(privateKey: string): void {
this.privateKey = privateKey;
}
public getSearch(): string {
return this.search;
}
public setSearch(search: string): void {
this.search = search;
}
public addUser(searchUser: PacketSearchUser): void {
this.users.push(searchUser);
}
public getUsers(): PacketSearchUser[] {
return this.users;
}
}

View File

@@ -0,0 +1,53 @@
import Packet from "../packet";
import Stream from "../stream";
export class PacketTyping extends Packet {
private privateKey: string = "";
private fromPublicKey: string = "";
private toPublicKey: string = "";
public getPacketId(): number {
return 0x0B;
}
public _receive(stream: Stream): void {
this.privateKey = stream.readString();
this.fromPublicKey = stream.readString();
this.toPublicKey = stream.readString();
}
public _send(): Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.privateKey);
stream.writeString(this.fromPublicKey);
stream.writeString(this.toPublicKey);
return stream;
}
public setFromPublicKey(fromPublicKey: string): void {
this.fromPublicKey = fromPublicKey;
}
public setToPublicKey(toPublicKey: string): void {
this.toPublicKey = toPublicKey;
}
public getFromPublicKey(): string {
return this.fromPublicKey;
}
public getToPublicKey(): string {
return this.toPublicKey;
}
public setPrivateKey(privateKey: string): void {
this.privateKey = privateKey;
}
public getPrivateKey(): string {
return this.privateKey;
}
}

View File

@@ -0,0 +1,51 @@
import Packet from "../packet";
import Stream from "../stream";
export class PacketUserInfo extends Packet {
private privateKey : string = "";
private username: string = "";
private title: string = "";
public getPacketId(): number {
return 0x01;
}
public _receive(stream: Stream): void {
this.username = stream.readString();
this.title = stream.readString();
this.privateKey = stream.readString();
}
public _send(): Stream {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeString(this.username);
stream.writeString(this.title);
stream.writeString(this.privateKey);
return stream;
}
public getUsername(): string {
return this.username;
}
public setUsername(username: string): void {
this.username = username;
}
public getTitle(): string {
return this.title;
}
public setTitle(title: string): void {
this.title = title;
}
public setPrivateKey(privateKey: string): void {
this.privateKey = privateKey;
}
public getPrivateKey(): string {
return this.privateKey;
}
}

View File

@@ -0,0 +1,269 @@
import { EventEmitter } from "events";
import Packet from "./packet";
import PacketHandshake, { Device, HandshakeState } from "./packets/packet.handshake";
import { PacketMessage } from "./packets/packet.message";
import { PacketOnlineState } from "./packets/packet.onlinestate";
import { PacketOnlineSubscribe } from "./packets/packet.onlinesubscribe";
import { PacketRead } from "./packets/packet.read";
import { PacketResult } from "./packets/packet.result";
import { PacketSearch } from "./packets/packet.search";
import { PacketUserInfo } from "./packets/packet.userinfo";
import Stream from "./stream";
import { PacketDelivery } from "./packets/packet.delivery";
import { PacketRequestUpdate } from "./packets/packet.requestupdate";
import { RECONNECTING_INTERVAL } from "@/app/constants";
import { PacketTyping } from "./packets/packet.typeing";
import { PacketAvatar } from "./packets/packet.avatar";
import { PacketKernelUpdate } from "./packets/packet.kernelupdate";
import { PacketAppUpdate } from "./packets/packet.appupdate";
import { PacketRequestTransport } from "./packets/packet.requesttransport";
import { PacketPushNotification } from "./packets/packet.push.notification";
import { PacketCreateGroup } from "./packets/packet.create.group";
import { PacketGroupInfo } from "./packets/packet.group.info";
import { PacketGroupInviteInfo } from "./packets/packet.group.invite.info";
import { PacketGroupJoin } from "./packets/packet.group.join";
import { PacketGroupLeave } from "./packets/packet.group.leave";
import { PacketGroupBan } from "./packets/packet.group.ban";
export default class Protocol extends EventEmitter {
private serverAddress: string;
private socket: WebSocket | null = null;
private reconnectInterval: number = RECONNECTING_INTERVAL * 1000;
private isManuallyClosed: boolean = false;
private _supportedPackets: Map<number, Packet> = new Map();
private _packetWaiters: Map<number, ((packet: any) => void)[]> = new Map();
private _packetQueue: Packet[] = []; // Очередь для пакетов
private handshakeExchangeComplete : boolean = false;
private heartbeatIntervalTimer : NodeJS.Timeout | null = null;
constructor(serverAddress: string) {
super();
this.serverAddress = serverAddress;
this.loadAllSupportedPackets();
this.connect();
let _this = this;
this.waitPacket(0x00, (packet : PacketHandshake) => {
if(packet.getHandshakeState() == HandshakeState.COMPLETED) {
console.info('[protocol] %chandshake exchange complete', 'color: #12c456;');
_this.emit('handshake_complete');
_this.handshakeExchangeComplete = true;
_this._flushPacketQueue();
this.startHeartbeat(packet.getHeartbeatInterval());
}
if(packet.getHandshakeState() == HandshakeState.NEED_DEVICE_VERIFICATION) {
console.info('[protocol] %chandshake exchange need device verification', 'color: orange;');
_this.emit('handshake_need_device_verification');
_this._packetQueue = [];
this.startHeartbeat(packet.getHeartbeatInterval());
}
});
}
public startHeartbeat(intervalS : number) {
const heartbeat = () => {
if(this.socket && this.socket.readyState === WebSocket.OPEN){
this.socket?.send('heartbeat');
}
}
if(this.heartbeatIntervalTimer){
clearInterval(this.heartbeatIntervalTimer);
}
console.info(`[protocol] %cstarting heartbeat with interval: %c${intervalS} seconds`, 'color: #12c456;', 'color: orange;');
heartbeat();
this.heartbeatIntervalTimer = setInterval(() => {
heartbeat();
}, ((intervalS * 1000) / 2));
}
public startHandshakeExchange(publicKey: string, privateKey: string,
device: Device
) {
console.info(
`[protocol] %cstarting handshake exchange with server, public key: %c${publicKey}%c, private key hash: %c${privateKey}`,
'color: #deadcd;',
'color: #12c456;',
'color: #deadcd;',
'color: #12c456;'
);
let handshake = new PacketHandshake();
handshake.setPublicKey(publicKey);
handshake.setPrivateKey(privateKey);
handshake.setDevice(device);
this.sendPacket(handshake);
this.emit('handshake_start');
}
private loadAllSupportedPackets() {
this._supportedPackets.set(0x00, new PacketHandshake());
this._supportedPackets.set(0x01, new PacketUserInfo());
this._supportedPackets.set(0x02, new PacketResult());
this._supportedPackets.set(0x03, new PacketSearch());
this._supportedPackets.set(0x04, new PacketOnlineSubscribe());
this._supportedPackets.set(0x05, new PacketOnlineState());
this._supportedPackets.set(0x06, new PacketMessage());
this._supportedPackets.set(0x07, new PacketRead());
this._supportedPackets.set(0x08, new PacketDelivery());
//TODO: 0x09
this._supportedPackets.set(0x0A, new PacketRequestUpdate());
this._supportedPackets.set(0x0B, new PacketTyping());
this._supportedPackets.set(0x0C, new PacketAvatar());
this._supportedPackets.set(0x0D, new PacketKernelUpdate());
this._supportedPackets.set(0x0E, new PacketAppUpdate());
this._supportedPackets.set(0x0F, new PacketRequestTransport());
this._supportedPackets.set(0x10, new PacketPushNotification());
this._supportedPackets.set(0x11, new PacketCreateGroup());
this._supportedPackets.set(0x12, new PacketGroupInfo());
this._supportedPackets.set(0x13, new PacketGroupInviteInfo());
this._supportedPackets.set(0x14, new PacketGroupJoin());
this._supportedPackets.set(0x15, new PacketGroupLeave());
this._supportedPackets.set(0x16, new PacketGroupBan());
}
private _findWaiters(packetId: number): ((packet: Packet) => void)[] {
if (!this._packetWaiters.has(packetId)) {
return [];
}
return this._packetWaiters.get(packetId)!;
}
private connect() {
this.socket = new WebSocket(this.serverAddress);
this.socket.addEventListener('open', () => {
//this.reconnectTryings = 0;
this.emit('connect');
this._flushPacketQueue(); // Отправляем все пакеты из очереди
});
this.socket.addEventListener('message', async (event: MessageEvent) => {
let stream = new Stream();
const data = await event.data.arrayBuffer();
const numbers = Array.from(new Uint8Array(data));
stream.setStream(numbers);
const packetId = stream.readInt16();
this._supportedPackets.get(packetId);
if (!this._supportedPackets.get(packetId)) {
console.error(`Unsupported packet ID: ${packetId}`);
return;
}
//чек безопасность
const packet = this._supportedPackets.get(packetId)!.clone();
packet._receive(stream);
const waiters = this._findWaiters(packetId);
if (waiters.length === 0) {
console.error(`No waiters found for packet ID: ${packetId}`);
return;
}
for (const waiter of waiters) {
waiter(packet);
}
});
this.socket.addEventListener('close', (e: CloseEvent) => {
console.log(`Connection reset by peer`, e.code);
this.handshakeExchangeComplete = false;
if (!this.isManuallyClosed) {
console.log(`Attempting to reconnect...`);
this.emit('reconnect');
setTimeout(() => {
this.connect();
if(this.socket?.readyState == WebSocket.OPEN){
return;
}
}, this.reconnectInterval);
}
});
}
private _flushPacketQueue() {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
for(let i = this._packetQueue.length - 1; i >= 0; i--){
let packet : Packet = this._packetQueue[i];
if(!this.handshakeExchangeComplete && packet.getPacketId() != 0x00){
/**
* Если рукопожатие еще не выполнено и текущий пакет для отправки из очереди -
* не рукопожатие то пропускаем, отправим в следующее очищение очереди
*/
continue;
}
this.sendPacket(packet);
this._packetQueue.splice(i, 1);
}
}
}
public async sendPacket(packet: Packet) {
if(!this.socket){
this.addPacketToQueue(packet);
return;
}
if(this.socket.readyState !== WebSocket.OPEN){
this.addPacketToQueue(packet);
return;
}
if(!this.handshakeExchangeComplete && packet.getPacketId() != 0x00){
this.addPacketToQueue(packet);
return;
}
const stream = await packet._send();
const packetName = packet.constructor.name;
const pIdHex = packet.getPacketId().toString(16).toLocaleUpperCase();
const pIdHexPadded = pIdHex.length === 1 ? '0' + pIdHex : pIdHex;
console.info(`[protocol] %csending packet: %c${packetName} (ID: 0x${pIdHexPadded})`, 'color: #deadcd;', 'color: orange;');
/**
* Если пакет больше максимально допустимого размера, то разбиваем его на чанки
* и отправляем по частям
*/
this.socket.send(Buffer.from(stream.getStream()));
}
public addPacketToQueue(packet : Packet) {
this._packetQueue.push(packet);
}
public close() {
this.isManuallyClosed = true;
if (this.socket) {
this.socket.close();
}
}
public unwaitPacket<T>(packet: number, callback: (packet: T) => void) {
if (!this._packetWaiters.has(packet)) {
return;
}
const waiters = this._packetWaiters.get(packet)!;
const index = waiters.indexOf(callback);
if (index !== -1) {
waiters.splice(index, 1);
}
if (waiters.length === 0) {
this._packetWaiters.delete(packet);
}
}
public waitPacket<T>(packet: number, callback: (packet: T) => void) : number {
if (!this._packetWaiters.has(packet)) {
this._packetWaiters.set(packet, []);
}
return this._packetWaiters.get(packet)!.push(callback);
}
/**
* Wait for a single packet to be received.
* @param packet packet number to wait
* @param callback callback to execute once the packet is received
*/
public waitPacketOnce<T>(packet: number, callback: (packet: T) => void) {
let wrapper = (receivedPacket: T) => {
callback(receivedPacket);
this.unwaitPacket(packet, wrapper);
};
this.waitPacket(packet, wrapper);
}
}

View File

@@ -0,0 +1,143 @@
export default class Stream {
private _stream: number[];
private _readPoiner: number = 0;
private _writePointer: number = 0;
constructor(stream : number[] = []) {
this._stream = stream;
}
public getStream(): number[] {
return this._stream;
}
public setStream(stream: number[]) {
this._stream = stream;
}
public writeInt8(value: number) {
const negationBit = value < 0 ? 1 : 0;
const int8Value = Math.abs(value) & 0xFF;
this._stream[this._writePointer >> 3] |= negationBit << (7 - (this._writePointer & 7));
this._writePointer++;
for (let i = 0; i < 8; i++) {
const bit = (int8Value >> (7 - i)) & 1;
this._stream[this._writePointer >> 3] |= bit << (7 - (this._writePointer & 7));
this._writePointer++;
}
}
public readInt8(): number {
let value = 0;
const negationBit = (this._stream[this._readPoiner >> 3] >> (7 - (this._readPoiner & 7))) & 1;
this._readPoiner++;
for (let i = 0; i < 8; i++) {
const bit = (this._stream[this._readPoiner >> 3] >> (7 - (this._readPoiner & 7))) & 1;
value |= bit << (7 - i);
this._readPoiner++;
}
return negationBit ? -value : value;
}
public writeBit(value: number) {
const bit = value & 1;
this._stream[this._writePointer >> 3] |= bit << (7 - (this._writePointer & 7));
this._writePointer++;
}
public readBit(): number {
const bit = (this._stream[this._readPoiner >> 3] >> (7 - (this._readPoiner & 7))) & 1;
this._readPoiner++;
return bit;
}
public writeBoolean(value: boolean) {
this.writeBit(value ? 1 : 0);
}
public readBoolean(): boolean {
return this.readBit() === 1;
}
public writeInt16(value: number) {
this.writeInt8(value >> 8);
this.writeInt8(value & 0xFF);
}
public readInt16(): number {
const value = this.readInt8() << 8;
return value | this.readInt8();
}
public writeInt32(value: number) {
this.writeInt16(value >> 16);
this.writeInt16(value & 0xFFFF);
}
public readInt32(): number {
const value = this.readInt16() << 16;
return value | this.readInt16();
}
public writeFloat32(value: number) {
const buffer = new ArrayBuffer(4);
new DataView(buffer).setFloat32(0, value, true);
const float32Value = new Uint32Array(buffer)[0];
this.writeInt32(float32Value);
}
public readFloat32(): number {
const float32Value = this.readInt32();
const buffer = new ArrayBuffer(4);
new Uint32Array(buffer)[0] = float32Value;
return new DataView(buffer).getFloat32(0, true);
}
public writeInt64(value: number) {
const high = Math.floor(value / 0x100000000);
const low = value >>> 0;
this.writeInt32(high);
this.writeInt32(low);
}
public readInt64(): number {
const high = this.readInt32();
const low = this.readInt32() >>> 0;
return high * 0x100000000 + low;
}
public writeString(value: string) {
let length = value.length;
this.writeInt32(length);
for (let i = 0; i < value.length; i++) {
this.writeInt16(value.charCodeAt(i));
}
}
public readString(): string {
let length = this.readInt32();
let value = "";
for (let i = 0; i < length; i++) {
value += String.fromCharCode(this.readInt16());
}
return value;
}
public writeBytes(value: number[]) {
this.writeInt32(value.length);
for (let i = 0; i < value.length; i++) {
this.writeInt8(value[i]);
}
}
public readBytes(): number[] {
let length = this.readInt32();
let value : any = [];
for (let i = 0; i < length; i++) {
value.push(this.readInt8());
}
return value;
}
}