import * as plugins from './nullresolve.plugins.js'; import * as paths from './nullresolve.paths.js'; import { configObject } from './nullresolve.config.js'; import type { IHtmlInfoOptions } from '@api.global/typedserver/infohtml'; export interface INullResolveOptions { port?: number; serviceDomain?: string; } type TResolvedNullResolveOptions = INullResolveOptions & { serviceDomain: string; }; export class NullResolve { public serviceServer: plugins.typedserver.utilityservers.UtilityServiceServer | null = null; private options: TResolvedNullResolveOptions; constructor(optionsArg: INullResolveOptions = {}) { this.options = { serviceDomain: 'nullresolve.lossless.one', ...optionsArg, }; } private async createInfoHtmlResponse( infoHtmlOptionsArg: IHtmlInfoOptions, ) { const { InfoHtml } = await import('@api.global/typedserver/infohtml'); const infoHtmlInstance = await InfoHtml.fromOptions(infoHtmlOptionsArg); return new Response(infoHtmlInstance.htmlString, { status: 200, headers: { 'content-type': 'text/html; charset=utf-8', }, }); } private getStatusInfoOptions(statusCodeArg: string): IHtmlInfoOptions { switch (statusCodeArg) { case 'ipblock': return { title: 'Lossless Network: Blocked IP', heading: 'Blocked IP', text: 'Your IP (::CLIENT_IP::) is not allowed to access this resource.', sentryDsn: configObject.sentryDsn, sentryMessage: 'ipblock', redirectTo: 'https://lossless.com', }; case 'firewall': return { title: 'Lossless Network: Firewall', heading: 'Firewall', text: 'Your request has been blocked by our firewall since it showed possibly harmful behaviour.', sentryDsn: configObject.sentryDsn, sentryMessage: 'firewall', redirectTo: 'https://lossless.com', }; case '500class': return { title: 'Lossless Network: 5xx', heading: '5xx', text: '::CLOUDFLARE_ERROR_500S_BOX::', sentryDsn: configObject.sentryDsn, sentryMessage: '5xx error', redirectTo: 'https://lossless.com', }; case '1000class': return { title: 'Lossless Network: DNS Resolution failed', heading: '1xxx', text: '::CLOUDFLARE_ERROR_1000S_BOX::', sentryDsn: configObject.sentryDsn, sentryMessage: '1000 class error', redirectTo: 'https://lossless.com', }; case 'alwaysonline': return { title: 'Lossless Network: No Cache', heading: 'No Cache', text: '::ALWAYS_ONLINE_NO_COPY_BOX::', sentryDsn: configObject.sentryDsn, sentryMessage: 'alwaysonline triggered. Potentially offline!', redirectTo: 'https://lossless.com', }; case 'waf': return { title: 'Lossless Network: Firewall Challenge', heading: 'Firewall Challenge', text: '::CAPTCHA_BOX::', redirectTo: 'https://lossless.com', }; case 'country': return { title: 'Lossless Network: Country Challenge', heading: 'Country Challenge', text: '::CAPTCHA_BOX::', redirectTo: 'https://lossless.com', }; case 'attack': return { title: 'Lossless Network: Advanced User Challenge', heading: 'Advanced User Challenge', text: '::IM_UNDER_ATTACK_BOX::', redirectTo: 'https://lossless.com', }; default: { const statusInstance = plugins.smartstatus.HttpStatus.getHttpStatusByString(statusCodeArg); return { title: `Lossless Network: ${statusInstance.code.toString()}`, heading: statusInstance.code.toString(), text: statusInstance.text, }; } } } private async addCustomRoutes(typedServerArg: plugins.typedserver.TypedServer) { typedServerArg.addRoute('/status/:code', 'GET', async (ctxArg) => { return this.createInfoHtmlResponse(this.getStatusInfoOptions(ctxArg.params.code)); }); typedServerArg.addRoute('/custom', 'GET', async (ctxArg) => { const options = { title: ctxArg.query.title || 'Lossless Network', heading: ctxArg.query.heading || 'Error!', text: ctxArg.query.text || 'Please wait...', redirectTo: ctxArg.query.redirectTo || 'https://lossless.com', }; return this.createInfoHtmlResponse({ ...options, sentryDsn: configObject.sentryDsn, sentryMessage: `nullresolve custom: ${options.title}`, }); }); } public async start() { if (this.serviceServer) { return; } const projectinfo = await plugins.projectinfo.ProjectInfo.create(paths.packageDir); this.serviceServer = new plugins.typedserver.utilityservers.UtilityServiceServer({ serviceDomain: this.options.serviceDomain, serviceName: 'nullresolve', serviceVersion: projectinfo.npm.version, port: this.options.port, addCustomRoutes: async (typedServerArg) => this.addCustomRoutes(typedServerArg), }); await this.serviceServer.start(); } public async stop() { if (!this.serviceServer) { return; } await this.serviceServer.stop(); this.serviceServer = null; } }