dbinbound.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import dayjs, { type Dayjs } from 'dayjs';
  2. import { ObjectUtil, NumberFormatter, SizeFormatter } from '@/utils';
  3. import { Protocols } from '@/schemas/primitives';
  4. export type RawJsonField = string | Record<string, unknown> | unknown[];
  5. export interface ClientStats {
  6. email: string;
  7. up: number;
  8. down: number;
  9. total: number;
  10. expiryTime: number;
  11. enable?: boolean;
  12. inboundId?: number;
  13. reset?: number;
  14. }
  15. export interface FallbackParentRef {
  16. masterId: number;
  17. path: string;
  18. }
  19. export type DBInboundInit = Partial<{
  20. id: number;
  21. userId: number;
  22. up: number;
  23. down: number;
  24. total: number;
  25. remark: string;
  26. enable: boolean;
  27. expiryTime: number;
  28. trafficReset: string;
  29. lastTrafficResetTime: number;
  30. listen: string;
  31. port: number;
  32. protocol: string;
  33. settings: RawJsonField;
  34. streamSettings: RawJsonField;
  35. tag: string;
  36. sniffing: RawJsonField;
  37. clientStats: ClientStats[];
  38. nodeId: number | null;
  39. fallbackParent: FallbackParentRef | null;
  40. }>;
  41. export function coerceInboundJsonField(value: unknown): Record<string, unknown> {
  42. if (value == null) return {};
  43. if (typeof value === 'object' && !Array.isArray(value)) {
  44. return value as Record<string, unknown>;
  45. }
  46. if (typeof value !== 'string') return {};
  47. const trimmed = value.trim();
  48. if (trimmed === '') return {};
  49. try {
  50. const parsed = JSON.parse(trimmed);
  51. if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
  52. return parsed as Record<string, unknown>;
  53. }
  54. return {};
  55. } catch {
  56. return {};
  57. }
  58. }
  59. export class DBInbound {
  60. id: number;
  61. userId: number;
  62. up: number;
  63. down: number;
  64. total: number;
  65. remark: string;
  66. enable: boolean;
  67. expiryTime: number;
  68. trafficReset: string;
  69. lastTrafficResetTime: number;
  70. listen: string;
  71. port: number;
  72. protocol: string;
  73. settings: RawJsonField;
  74. streamSettings: RawJsonField;
  75. tag: string;
  76. sniffing: RawJsonField;
  77. clientStats: ClientStats[];
  78. nodeId: number | null;
  79. fallbackParent: FallbackParentRef | null;
  80. private _clientStatsMap: Map<string, ClientStats> | null = null;
  81. constructor(data?: DBInboundInit) {
  82. this.id = 0;
  83. this.userId = 0;
  84. this.up = 0;
  85. this.down = 0;
  86. this.total = 0;
  87. this.remark = "";
  88. this.enable = true;
  89. this.expiryTime = 0;
  90. this.trafficReset = "never";
  91. this.lastTrafficResetTime = 0;
  92. this.listen = "";
  93. this.port = 0;
  94. this.protocol = "";
  95. this.settings = "";
  96. this.streamSettings = "";
  97. this.tag = "";
  98. this.sniffing = "";
  99. this.clientStats = [];
  100. this.nodeId = null;
  101. this.fallbackParent = null;
  102. if (data == null) {
  103. return;
  104. }
  105. ObjectUtil.cloneProps(this, data);
  106. }
  107. get totalGB(): number {
  108. return NumberFormatter.toFixed(this.total / SizeFormatter.ONE_GB, 2);
  109. }
  110. set totalGB(gb: number) {
  111. this.total = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
  112. }
  113. get isVMess() {
  114. return this.protocol === Protocols.VMESS;
  115. }
  116. get isVLess() {
  117. return this.protocol === Protocols.VLESS;
  118. }
  119. get isTrojan() {
  120. return this.protocol === Protocols.TROJAN;
  121. }
  122. get isSS() {
  123. return this.protocol === Protocols.SHADOWSOCKS;
  124. }
  125. get isMixed() {
  126. return this.protocol === Protocols.MIXED;
  127. }
  128. get isHTTP() {
  129. return this.protocol === Protocols.HTTP;
  130. }
  131. get isWireguard() {
  132. return this.protocol === Protocols.WIREGUARD;
  133. }
  134. get isHysteria() {
  135. return this.protocol === Protocols.HYSTERIA;
  136. }
  137. get address(): string {
  138. let address = location.hostname;
  139. if (!ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0") {
  140. address = this.listen;
  141. }
  142. return address;
  143. }
  144. get _expiryTime(): Dayjs | null {
  145. if (this.expiryTime === 0) {
  146. return null;
  147. }
  148. return dayjs(this.expiryTime);
  149. }
  150. set _expiryTime(t: Dayjs | null | undefined) {
  151. if (t == null) {
  152. this.expiryTime = 0;
  153. } else {
  154. this.expiryTime = t.valueOf();
  155. }
  156. }
  157. get isExpiry(): boolean {
  158. return this.expiryTime < new Date().getTime();
  159. }
  160. invalidateCache(): void {
  161. this._clientStatsMap = null;
  162. }
  163. getClientStats(email: string): ClientStats | undefined {
  164. if (!this._clientStatsMap) {
  165. this._clientStatsMap = new Map();
  166. if (Array.isArray(this.clientStats)) {
  167. for (const stats of this.clientStats) {
  168. if (stats && stats.email) {
  169. this._clientStatsMap.set(stats.email, stats);
  170. }
  171. }
  172. }
  173. }
  174. return this._clientStatsMap.get(email);
  175. }
  176. }