Skip to content

Commit

Permalink
Отчеты для новых версий системы
Browse files Browse the repository at this point in the history
  • Loading branch information
lentryd committed Sep 16, 2024
1 parent 9f7e3d5 commit 5378bad
Show file tree
Hide file tree
Showing 3 changed files with 621 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/NetSchoolApi-safe.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Client, { InitRequest } from "@/classes/Client";
import Client, { InitRequest, requestHook } from "@/classes/Client";
import Session from "@/classes/Session";
import Context from "@/classes/Context";

Expand Down Expand Up @@ -57,6 +57,7 @@ export default class NetSchoolApiSafe {
constructor(credentials: Credentials) {
this.credentials = credentials;
this.client = new Client(credentials.origin);
this.client.onResponse(requestHook.bind(this.client));

this.client.path.set("webapi");
this.client.headers.set("at", () =>
Expand Down
276 changes: 276 additions & 0 deletions src/methods/reportFile/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
import NS from "@NS";
import Client from "@/classes/Client";
import { sessionValid } from "@/utils/checks";
import legacy from "./legacy";

//? Типы данных

/** Фильтры отчета */
export type Filter = {
filterId: string;
filterValue: string | number;
};
/** Параметры отчета */
export type Param = {
name: string;
value: string | number;
};
export type Message =
| {
type: 1;
target: "progress";
arguments: Array<{
taskId: number;
status: string;
}>;
}
| {
type: 1;
target: "complete";
arguments: Array<{
taskId: number;
data: string;
componentId: string;
}>;
}
| {
type: 3;
invocationId: string;
result: {
success: boolean;
message: any;
};
}
| { error: string };
/** Параметры для инициализации */
export interface Credentials {
/** Ссылка для запроса */
url: string;
/** Фильтры запроса */
filters: Filter[];
/** Идентификатор года отчета */
yearId?: number;
/** Время ожидания ответа */
timeout?: number;
/** Какой протокол использовать
*
* 0 - Web Sockets, 1 - Long Polling
*
* если отсутствует, то используется Web Sockets или Long Polling (в зависимости от версии сервера)
* @deprecated в новых версиях сервера не используется longPolling
*/
transport?: 0 | 1;
}

//? Функции помощники

/**
* Формируем задачу
* @param client клиент
* @param url ссылка для запроса
* @param params параметры запроса
* @param filters фильтры запроса
* @returns номер задачи
*/
export async function getTaskId(
client: Client,
url: string,
params: Param[],
filters: Filter[]
): Promise<number> {
return await client
.post(url, {
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ params, selectedData: filters }),
})
.then((res) => res.json())
.then(({ taskId }) => taskId);
}

/**
* Форматируем сообщение для WebSocket (с блядскими символами)
* @param message сообщение (объект)
* @param send нужно ли преобразовать в JSON
* @returns сообщение (строка или буфер)
*/
function formatMsg(message: any, send: boolean = false): Buffer | any {
if (send) return Buffer.from(JSON.stringify(message) + "");
else return JSON.parse(message.toString().replace("", ""));
}

/**
* Форматируем сообщение
* @param msg сообщение от сервера
*/
function messageHandler(msg: Message) {
// Обработка сообщения
const data = {
error: undefined as string | undefined,
fileCode: undefined as string | undefined,
};

// Если отчет готов
if ("type" in msg && msg.type === 1 && msg.target === "complete") {
data.fileCode = msg.arguments.find((arg) => {
const keys = Object.keys(arg);

return keys.includes("taskId") && keys.includes("data");
})?.data;
}
// Если произошла ошибка
if ("error" in msg) data.error = msg.error;

return data;
}

/**
* Получение отчета с помощью Web Sockets
* @param client клиент
* @param context контекст
* @param params параметры запроса
* @param taskId номер задачи
* @param timeout время ожидания ответа
* @returns идентификатор отчета
*/
async function webSocketsConnection(
client: Client,
at: string,
taskId: number,
timeout: number
) {
// Создаем WebSocket
const ws = client.ws("queueHub", { params: { at } });

// Обрабатывает сообщения
return new Promise<string>((resolve, reject) => {
let fileCode: string | undefined;
let timeoutId = 0 as unknown as NodeJS.Timeout;

// Открываем соединение
ws.once("open", async () => {
// Устанавливаем тайм-аут
if (timeout > 0) timeoutId = setTimeout(() => ws.close(4010), timeout);
// Открываем соединение
ws.send(formatMsg({ protocol: "json", version: 1 }, true));
// Уведомляем сервер о задаче
ws.send(
formatMsg(
{
arguments: [taskId, "report-v2"],
invocationId: "0",
target: "startTask",
type: 1,
},
true
)
);
});

// Слушаем сообщения
ws.on("message", (msg: Buffer) => {
// Пытаемся преобразовать сообщение в JSON
let data: { error?: string; fileCode?: string };
try {
data = messageHandler(formatMsg(msg));
} catch (e) {
return;
}

// Обрабатываем ошибку
if (data.error) {
console.error(data.error);
ws.close(4003, data.error);
}

// Обрабатываем
if (data.fileCode) {
fileCode = data.fileCode;
ws.close(4000);
}
});

// Обрабатываем ошибки соединения
ws.once("error", (err) => ws.close(4002, err.message));

// Закрываем соединение
ws.once("close", async (code, message) => {
clearTimeout(timeoutId);

switch (code) {
// Если соединение закрыто успешно
case 1000:
case 4000:
if (!fileCode) return reject(new Error("Server didn't respond"));
resolve(fileCode);
break;

// Если произошла ошибка инициализации
case 4001:
reject(new Error("Error during initialization"));
break;

// Если произошла ошибка с соединением
case 4002:
reject(new Error("Error in socket.\nError: " + message));
break;

// Если произошла ошибка с задачей
case 4003:
reject(new Error("Error in task.\nError: " + message));
break;

// Если вышло время ожидания
case 4010:
reject(new Error("Response time expired"));
break;

// Неизвестная ошибка
default:
reject(new Error("Unknown error.\nError: " + message));
}
});
});
}

export default async function reportFile(
this: NS,
credentials: Credentials
): Promise<string> {
const { url, filters, yearId, timeout = 6e4, transport } = credentials;
const { client, session, context } = await sessionValid.call(this);

// Параметры запроса
const params: Param[] = [
{ name: "DATEFORMAT", value: context.server.dateFormat },
{
name: "SCHOOLYEARID",
value: (yearId ? yearId : context.year.id).toString(),
},
{ name: "SERVERTIMEZONE", value: 3 },
{ name: "FULLSCHOOLNAME", value: context.school.fullName },
];

// Получаем идентификатор отчета
let fileCode: string | undefined;

// Если версия сервера меньше
if (context.compareServerVersion("5.29.0.0") == -1) {
fileCode = await legacy.call(this, params, credentials);
}
// Если версия сервера больше или равна
else {
// Получаем идентификатор задачи
const taskId = await getTaskId(client, url, params, filters);
// Получаем идентификатор отчета
fileCode = await webSocketsConnection(
client,
session.accessToken,
taskId,
timeout
);
}

// Возвращаем отчет
if (!fileCode) throw new Error("Server didn't respond");
return (await client.get("files/" + fileCode)).text();
}
Loading

0 comments on commit 5378bad

Please sign in to comment.