refactor: reorganize services

This commit is contained in:
divocat
2025-10-14 21:32:06 +03:00
parent de3e67f999
commit 33dfb8c3f0
13 changed files with 122 additions and 120 deletions

View File

@@ -1,5 +1,5 @@
import { TabServiceInstance } from './tab.service'; import { TabServiceInstance } from './tab.service';
import { store } from '../../store'; import { store } from './store.service';
export function coreService() { export function coreService() {
TabServiceInstance.onChange((activeId, tabs) => { TabServiceInstance.onChange((activeId, tabs) => {

View File

@@ -1,2 +1,4 @@
export * from './tab.service'; export * from './tab.service';
export * from './core.service'; export * from './core.service';
export * from './socket.service';
export * from './store.service';

View File

@@ -1,5 +1,5 @@
import { Podkop } from './podkop/types'; import { Podkop } from '../types';
import { initialDiagnosticStore } from './podkop/tabs/diagnostic/diagnostic.store'; import { initialDiagnosticStore } from '../tabs/diagnostic/diagnostic.store';
function jsonStableStringify<T, V>(obj: T): string { function jsonStableStringify<T, V>(obj: T): string {
return JSON.stringify(obj, (_, value) => { return JSON.stringify(obj, (_, value) => {
@@ -29,7 +29,7 @@ function jsonEqual<A, B>(a: A, b: B): boolean {
type Listener<T> = (next: T, prev: T, diff: Partial<T>) => void; type Listener<T> = (next: T, prev: T, diff: Partial<T>) => void;
// eslint-disable-next-line // eslint-disable-next-line
class Store<T extends Record<string, any>> { class StoreService<T extends Record<string, any>> {
private value: T; private value: T;
private readonly initial: T; private readonly initial: T;
private listeners = new Set<Listener<T>>(); private listeners = new Set<Listener<T>>();
@@ -207,4 +207,4 @@ const initialStore: StoreType = {
...initialDiagnosticStore, ...initialDiagnosticStore,
}; };
export const store = new Store<StoreType>(initialStore); export const store = new StoreService<StoreType>(initialStore);

View File

@@ -4,8 +4,6 @@ import {
onMount, onMount,
preserveScrollForPage, preserveScrollForPage,
} from '../../../helpers'; } from '../../../helpers';
import { store, StoreType } from '../../../store';
import { socket } from '../../../socket';
import { prettyBytes } from '../../../helpers/prettyBytes'; import { prettyBytes } from '../../../helpers/prettyBytes';
import { renderSections } from './renderSections'; import { renderSections } from './renderSections';
import { renderWidget } from './renderWidget'; import { renderWidget } from './renderWidget';
@@ -14,6 +12,7 @@ import {
CustomPodkopMethods, CustomPodkopMethods,
PodkopShellMethods, PodkopShellMethods,
} from '../../methods'; } from '../../methods';
import { socket, store, StoreType } from '../../services';
// Fetchers // Fetchers

View File

@@ -1,8 +1,8 @@
import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck';
import { insertIf } from '../../../../helpers'; import { insertIf } from '../../../../helpers';
import { IDiagnosticsChecksItem } from '../../../../store';
import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; import { DIAGNOSTICS_CHECKS_MAP } from './contstants';
import { PodkopShellMethods } from '../../../methods'; import { PodkopShellMethods } from '../../../methods';
import { IDiagnosticsChecksItem } from '../../../services';
export async function runDnsCheck() { export async function runDnsCheck() {
const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.DNS; const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.DNS;

View File

@@ -1,8 +1,8 @@
import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck';
import { insertIf } from '../../../../helpers'; import { insertIf } from '../../../../helpers';
import { IDiagnosticsChecksItem } from '../../../../store';
import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; import { DIAGNOSTICS_CHECKS_MAP } from './contstants';
import { PodkopShellMethods, RemoteFakeIPMethods } from '../../../methods'; import { PodkopShellMethods, RemoteFakeIPMethods } from '../../../methods';
import { IDiagnosticsChecksItem } from '../../../services';
export async function runFakeIPCheck() { export async function runFakeIPCheck() {
const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.FAKEIP; const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.FAKEIP;

View File

@@ -1,7 +1,6 @@
import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck';
import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; import { DIAGNOSTICS_CHECKS_MAP } from './contstants';
import { RemoteFakeIPMethods } from '../../../methods/fakeip'; import { RemoteFakeIPMethods, PodkopShellMethods } from '../../../methods';
import { PodkopShellMethods } from '../../../methods';
export async function runNftCheck() { export async function runNftCheck() {
const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.NFT; const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.NFT;

View File

@@ -2,7 +2,7 @@ import {
DIAGNOSTICS_CHECKS, DIAGNOSTICS_CHECKS,
DIAGNOSTICS_CHECKS_MAP, DIAGNOSTICS_CHECKS_MAP,
} from './checks/contstants'; } from './checks/contstants';
import { StoreType } from '../../../store'; import { StoreType } from '../../services';
export const initialDiagnosticStore: Pick< export const initialDiagnosticStore: Pick<
StoreType, StoreType,

View File

@@ -1,5 +1,4 @@
import { onMount, preserveScrollForPage } from '../../../helpers'; import { onMount, preserveScrollForPage } from '../../../helpers';
import { store, StoreType } from '../../../store';
import { renderCheckSection } from './renderCheckSection'; import { renderCheckSection } from './renderCheckSection';
import { runDnsCheck } from './checks/runDnsCheck'; import { runDnsCheck } from './checks/runDnsCheck';
import { runSingBoxCheck } from './checks/runSingBoxCheck'; import { runSingBoxCheck } from './checks/runSingBoxCheck';
@@ -9,6 +8,7 @@ import { renderDiagnosticRunAction } from './renderDiagnosticRunAction';
import { renderAvailableActions } from './renderAvailableActions'; import { renderAvailableActions } from './renderAvailableActions';
import { renderSystemInfo } from './renderSystemInfo'; import { renderSystemInfo } from './renderSystemInfo';
import { loadingDiagnosticsChecksStore } from './diagnostic.store'; import { loadingDiagnosticsChecksStore } from './diagnostic.store';
import { store, StoreType } from '../../services';
function renderDiagnosticsChecks() { function renderDiagnosticsChecks() {
console.log('renderDiagnosticsChecks'); console.log('renderDiagnosticsChecks');

View File

@@ -8,7 +8,7 @@ import {
renderTriangleAlertIcon24, renderTriangleAlertIcon24,
renderXIcon24, renderXIcon24,
} from '../../../icons'; } from '../../../icons';
import { IDiagnosticsChecksStoreItem } from '../../../store'; import { IDiagnosticsChecksStoreItem } from '../../services';
type IRenderCheckSectionProps = IDiagnosticsChecksStoreItem; type IRenderCheckSectionProps = IDiagnosticsChecksStoreItem;

View File

@@ -1,4 +1,4 @@
import { IDiagnosticsChecksStoreItem, store } from '../../../store'; import { IDiagnosticsChecksStoreItem, store } from '../../services';
export function updateDiagnosticsCheck( export function updateDiagnosticsCheck(
check: IDiagnosticsChecksStoreItem, check: IDiagnosticsChecksStoreItem,

View File

@@ -1477,7 +1477,7 @@ var loadingDiagnosticsChecksStore = {
] ]
}; };
// src/store.ts // src/podkop/services/store.service.ts
function jsonStableStringify(obj) { function jsonStableStringify(obj) {
return JSON.stringify(obj, (_2, value) => { return JSON.stringify(obj, (_2, value) => {
if (value && typeof value === "object" && !Array.isArray(value)) { if (value && typeof value === "object" && !Array.isArray(value)) {
@@ -1499,7 +1499,7 @@ function jsonEqual(a, b) {
return false; return false;
} }
} }
var Store = class { var StoreService = class {
constructor(initial) { constructor(initial) {
this.listeners = /* @__PURE__ */ new Set(); this.listeners = /* @__PURE__ */ new Set();
this.lastHash = ""; this.lastHash = "";
@@ -1600,7 +1600,7 @@ var initialStore = {
}, },
...initialDiagnosticStore ...initialDiagnosticStore
}; };
var store = new Store(initialStore); var store = new StoreService(initialStore);
// src/podkop/services/core.service.ts // src/podkop/services/core.service.ts
function coreService() { function coreService() {
@@ -1614,6 +1614,108 @@ function coreService() {
}); });
} }
// src/podkop/services/socket.service.ts
var SocketManager = class _SocketManager {
constructor() {
this.sockets = /* @__PURE__ */ new Map();
this.listeners = /* @__PURE__ */ new Map();
this.connected = /* @__PURE__ */ new Map();
this.errorListeners = /* @__PURE__ */ new Map();
}
static getInstance() {
if (!_SocketManager.instance) {
_SocketManager.instance = new _SocketManager();
}
return _SocketManager.instance;
}
connect(url) {
if (this.sockets.has(url)) return;
const ws = new WebSocket(url);
this.sockets.set(url, ws);
this.connected.set(url, false);
this.listeners.set(url, /* @__PURE__ */ new Set());
this.errorListeners.set(url, /* @__PURE__ */ new Set());
ws.addEventListener("open", () => {
this.connected.set(url, true);
console.info(`Connected: ${url}`);
});
ws.addEventListener("message", (event) => {
const handlers = this.listeners.get(url);
if (handlers) {
for (const handler of handlers) {
try {
handler(event.data);
} catch (err) {
console.error(`Handler error for ${url}:`, err);
}
}
}
});
ws.addEventListener("close", () => {
this.connected.set(url, false);
console.warn(`Disconnected: ${url}`);
this.triggerError(url, "Connection closed");
});
ws.addEventListener("error", (err) => {
console.error(`Socket error for ${url}:`, err);
this.triggerError(url, err);
});
}
subscribe(url, listener, onError) {
if (!this.sockets.has(url)) {
this.connect(url);
}
this.listeners.get(url)?.add(listener);
if (onError) {
this.errorListeners.get(url)?.add(onError);
}
}
unsubscribe(url, listener, onError) {
this.listeners.get(url)?.delete(listener);
if (onError) {
this.errorListeners.get(url)?.delete(onError);
}
}
// eslint-disable-next-line
send(url, data) {
const ws = this.sockets.get(url);
if (ws && this.connected.get(url)) {
ws.send(typeof data === "string" ? data : JSON.stringify(data));
} else {
console.warn(`Cannot send: not connected to ${url}`);
this.triggerError(url, "Not connected");
}
}
disconnect(url) {
const ws = this.sockets.get(url);
if (ws) {
ws.close();
this.sockets.delete(url);
this.listeners.delete(url);
this.errorListeners.delete(url);
this.connected.delete(url);
}
}
disconnectAll() {
for (const url of this.sockets.keys()) {
this.disconnect(url);
}
}
triggerError(url, err) {
const handlers = this.errorListeners.get(url);
if (handlers) {
for (const cb of handlers) {
try {
cb(err);
} catch (e) {
console.error(`Error handler threw for ${url}:`, e);
}
}
}
}
};
var socket = SocketManager.getInstance();
// src/podkop/tabs/dashboard/renderSections.ts // src/podkop/tabs/dashboard/renderSections.ts
function renderFailedState() { function renderFailedState() {
return E( return E(
@@ -1839,108 +1941,6 @@ function renderDashboard() {
); );
} }
// src/socket.ts
var SocketManager = class _SocketManager {
constructor() {
this.sockets = /* @__PURE__ */ new Map();
this.listeners = /* @__PURE__ */ new Map();
this.connected = /* @__PURE__ */ new Map();
this.errorListeners = /* @__PURE__ */ new Map();
}
static getInstance() {
if (!_SocketManager.instance) {
_SocketManager.instance = new _SocketManager();
}
return _SocketManager.instance;
}
connect(url) {
if (this.sockets.has(url)) return;
const ws = new WebSocket(url);
this.sockets.set(url, ws);
this.connected.set(url, false);
this.listeners.set(url, /* @__PURE__ */ new Set());
this.errorListeners.set(url, /* @__PURE__ */ new Set());
ws.addEventListener("open", () => {
this.connected.set(url, true);
console.info(`Connected: ${url}`);
});
ws.addEventListener("message", (event) => {
const handlers = this.listeners.get(url);
if (handlers) {
for (const handler of handlers) {
try {
handler(event.data);
} catch (err) {
console.error(`Handler error for ${url}:`, err);
}
}
}
});
ws.addEventListener("close", () => {
this.connected.set(url, false);
console.warn(`Disconnected: ${url}`);
this.triggerError(url, "Connection closed");
});
ws.addEventListener("error", (err) => {
console.error(`Socket error for ${url}:`, err);
this.triggerError(url, err);
});
}
subscribe(url, listener, onError) {
if (!this.sockets.has(url)) {
this.connect(url);
}
this.listeners.get(url)?.add(listener);
if (onError) {
this.errorListeners.get(url)?.add(onError);
}
}
unsubscribe(url, listener, onError) {
this.listeners.get(url)?.delete(listener);
if (onError) {
this.errorListeners.get(url)?.delete(onError);
}
}
// eslint-disable-next-line
send(url, data) {
const ws = this.sockets.get(url);
if (ws && this.connected.get(url)) {
ws.send(typeof data === "string" ? data : JSON.stringify(data));
} else {
console.warn(`Cannot send: not connected to ${url}`);
this.triggerError(url, "Not connected");
}
}
disconnect(url) {
const ws = this.sockets.get(url);
if (ws) {
ws.close();
this.sockets.delete(url);
this.listeners.delete(url);
this.errorListeners.delete(url);
this.connected.delete(url);
}
}
disconnectAll() {
for (const url of this.sockets.keys()) {
this.disconnect(url);
}
}
triggerError(url, err) {
const handlers = this.errorListeners.get(url);
if (handlers) {
for (const cb of handlers) {
try {
cb(err);
} catch (e) {
console.error(`Error handler threw for ${url}:`, e);
}
}
}
}
};
var socket = SocketManager.getInstance();
// src/helpers/prettyBytes.ts // src/helpers/prettyBytes.ts
function prettyBytes(n) { function prettyBytes(n) {
const UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; const UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
@@ -3232,7 +3232,9 @@ return baseclass.extend({
preserveScrollForPage, preserveScrollForPage,
renderDashboard, renderDashboard,
renderDiagnostic, renderDiagnostic,
socket,
splitProxyString, splitProxyString,
store,
svgEl, svgEl,
validateDNS, validateDNS,
validateDomain, validateDomain,