Compare commits

...

46 Commits

Author SHA1 Message Date
0ec00a5404 2.0.58 2023-07-01 17:23:50 +02:00
b0f48ba598 fix(core): update 2023-07-01 17:23:49 +02:00
ec4a51668c 2.0.57 2023-07-01 12:29:36 +02:00
07739bec27 fix(core): update 2023-07-01 12:29:35 +02:00
9aebd59c08 2.0.56 2023-07-01 11:55:35 +02:00
be7f4c503e fix(core): update 2023-07-01 11:55:35 +02:00
e1e1d4bf65 2.0.55 2023-07-01 11:49:40 +02:00
20ecb86a9e fix(core): update 2023-07-01 11:49:39 +02:00
83890d7cab 2.0.54 2023-06-12 01:06:22 +02:00
4c87ea8273 fix(core): update 2023-06-12 01:06:21 +02:00
4be625a0d9 2.0.53 2023-04-10 00:55:17 +02:00
c305ca517a fix(core): update 2023-04-10 00:55:16 +02:00
23dccae01b 2.0.52 2023-04-09 23:04:36 +02:00
d5f8d215a2 fix(core): update 2023-04-09 23:04:35 +02:00
3d4f8d1bbe 2.0.51 2023-04-04 17:23:50 +02:00
4724629efa fix(core): update 2023-04-04 17:23:49 +02:00
ff9aea12c3 2.0.50 2023-04-04 17:14:54 +02:00
910b9a495e fix(core): update 2023-04-04 17:14:54 +02:00
7fdf0a71a7 2.0.49 2023-04-01 15:12:55 +02:00
bf2c6660f2 fix(core): update 2023-04-01 15:12:54 +02:00
49afc16422 2.0.48 2023-03-31 18:04:22 +02:00
bb6f239075 fix(core): update 2023-03-31 18:04:21 +02:00
5bd5916696 2.0.47 2023-03-31 15:27:00 +02:00
62df38d083 fix(core): update 2023-03-31 15:26:59 +02:00
d7fe947107 2.0.46 2023-03-31 15:10:43 +02:00
dd426a4ca4 fix(core): update 2023-03-31 15:10:42 +02:00
2a2d4dabe4 2.0.45 2023-03-31 13:18:23 +02:00
830682d382 fix(core): update 2023-03-31 13:18:23 +02:00
d160a92bee 2.0.44 2023-03-30 19:44:44 +02:00
cc421c3321 fix(core): update 2023-03-30 19:44:44 +02:00
92ecef30d3 2.0.43 2023-03-30 19:42:55 +02:00
de4aab6df0 fix(core): update 2023-03-30 19:42:55 +02:00
5624e9dc1f 2.0.42 2023-03-30 19:40:41 +02:00
c7e40e4cde fix(core): update 2023-03-30 19:40:41 +02:00
0704febfc0 2.0.41 2023-03-30 17:44:10 +02:00
b8b1a61ae5 fix(core): update 2023-03-30 17:44:10 +02:00
45155bbce0 2.0.40 2023-03-30 16:44:46 +02:00
35bcf717cb fix(core): update 2023-03-30 16:44:45 +02:00
4d3be1064d 2.0.39 2023-03-30 16:42:02 +02:00
8ee72f9380 fix(core): update 2023-03-30 16:42:02 +02:00
049ceb7d15 2.0.38 2023-03-30 15:15:49 +02:00
87a5337db3 fix(core): update 2023-03-30 15:15:48 +02:00
2372e36367 2.0.37 2023-03-30 12:17:42 +02:00
748fc931c6 fix(core): update 2023-03-30 12:17:41 +02:00
d5cdb94c73 2.0.36 2023-03-29 19:51:53 +02:00
59d6c7d5ec fix(core): update 2023-03-29 19:51:52 +02:00
31 changed files with 2225 additions and 913 deletions

View File

@ -0,0 +1,67 @@
name: Default (not tags)
on:
push:
tags-ignore:
- '**'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Install pnpm and npmci
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
- name: Run npm prepare
run: npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build

View File

@ -0,0 +1,110 @@
name: Default (tags)
on:
push:
tags:
- '*'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Install pnpm and npmci
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
- name: Run npm prepare
run: npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build
release:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Release
run: |
npmci node install stable
npmci npm publish
metadata:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
continue-on-error: true
steps:
- uses: actions/checkout@v3
- name: Code quality
run: |
npmci command npm install -g typescript
npmci npm prepare
npmci npm install
- name: Trigger
run: npmci trigger
- name: Build docs and upload artifacts
run: |
npmci node install stable
npmci npm install
pnpm install -g @gitzone/tsdoc
npmci command tsdoc
continue-on-error: true

View File

@ -1,6 +1,6 @@
{
"name": "@apiglobal/typedserver",
"version": "2.0.35",
"version": "2.0.58",
"description": "easy serving of static files",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",
@ -39,27 +39,40 @@
"dependencies": {
"@apiglobal/typedrequest": "^2.0.12",
"@apiglobal/typedrequest-interfaces": "^2.0.1",
"@apiglobal/typedsocket": "^2.0.22",
"@apiglobal/typedsocket": "^2.0.24",
"@pushrocks/lik": "^6.0.2",
"@pushrocks/smartchok": "^1.0.23",
"@pushrocks/smartdelay": "^2.0.13",
"@pushrocks/smartexpress": "^4.0.34",
"@pushrocks/smartfile": "^10.0.7",
"@pushrocks/smartdelay": "^3.0.1",
"@pushrocks/smartenv": "^5.0.5",
"@pushrocks/smartfeed": "^1.0.11",
"@pushrocks/smartfile": "^10.0.25",
"@pushrocks/smartlog": "^3.0.1",
"@pushrocks/smartlog-destination-devtools": "^1.0.10",
"@pushrocks/smartmanifest": "^2.0.2",
"@pushrocks/smartmime": "^1.0.5",
"@pushrocks/smartopen": "^2.0.0",
"@pushrocks/smartpath": "^5.0.5",
"@pushrocks/smartpromise": "^3.1.7",
"@pushrocks/smartrx": "^3.0.0",
"@pushrocks/webstore": "^2.0.5",
"lit": "^2.7.0"
"@pushrocks/smartpromise": "^4.0.2",
"@pushrocks/smartrequest": "^2.0.15",
"@pushrocks/smartrx": "^3.0.2",
"@pushrocks/smartsitemap": "^2.0.1",
"@pushrocks/smarttime": "^4.0.1",
"@pushrocks/webstore": "^2.0.8",
"@tsclass/tsclass": "^4.0.42",
"@types/express": "^4.17.17",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.18.2",
"express-force-ssl": "^0.3.2",
"lit": "^2.7.5"
},
"devDependencies": {
"@gitzone/tsbuild": "^2.1.63",
"@gitzone/tsbundle": "^2.0.6",
"@gitzone/tsrun": "^1.2.39",
"@gitzone/tsbuild": "^2.1.66",
"@gitzone/tsbundle": "^2.0.8",
"@gitzone/tsrun": "^1.2.42",
"@gitzone/tstest": "^1.0.72",
"@pushrocks/tapbundle": "^5.0.4",
"@types/node": "^18.15.11"
"@pushrocks/tapbundle": "^5.0.8",
"@types/node": "^20.3.3"
},
"private": false,
"browserslist": [

1670
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
# @apiglobal/typedserver
# @pushrocks/typedserver
easy serving of static files
## Availabililty and Links

View File

@ -10,6 +10,7 @@ tap.test('should create a valid instance of TypedServer', async () => {
port: 3000,
serveDir: smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
watch: true,
cors: true,
});
expect(testTypedServer).toBeInstanceOf(TypedServer);
});

128
test/test.server.ts Normal file
View File

@ -0,0 +1,128 @@
// tslint:disable-next-line:no-implicit-dependencies
import { expect, tap } from '@pushrocks/tapbundle';
// helper dependencies
// tslint:disable-next-line:no-implicit-dependencies
import * as smartpath from '@pushrocks/smartpath';
import * as smartrequest from '@pushrocks/smartrequest';
import * as typedserver from '../ts/index.js';
let testServer: typedserver.servertools.Server;
let testRoute: typedserver.servertools.Route;
let testRoute2: typedserver.servertools.Route;
let testHandler: typedserver.servertools.Handler;
// =================
// Test class Server
// =================
tap.test('should create a valid Server', async () => {
testServer = new typedserver.servertools.Server({
cors: true,
domain: 'testing.git.zone',
forceSsl: false,
appVersion: 'v3.2.1',
manifest: {
name: 'Test App',
short_name: 'testapp',
},
feed: true,
sitemap: true,
robots: true,
});
expect(testServer).toBeInstanceOf(typedserver.servertools.Server);
});
// ================
// Test class Route
// ================
tap.test('should create a valid Route', async () => {
testRoute = testServer.addRoute('/someroute');
testRoute2 = testServer.addRoute('/someroute/*');
expect(testRoute).toBeInstanceOf(typedserver.servertools.Route);
});
// ==================
// Test class Handler
// ==================
tap.test('should produce a valid handler', async () => {
testHandler = new typedserver.servertools.Handler('POST', (request, response) => {
console.log('request body is:');
console.log(request.body);
response.send('hi');
});
expect(testHandler).toBeInstanceOf(typedserver.servertools.Handler);
});
tap.test('should add handler to route', async () => {
testRoute.addHandler(testHandler);
});
tap.test('should create a valid StaticHandler', async () => {
testRoute2.addHandler(
new typedserver.servertools.HandlerStatic(
smartpath.get.dirnameFromImportMetaUrl(import.meta.url)
)
);
});
tap.test('should add typedrequest and typedsocket', async () => {
const typedrequest = await import('@apiglobal/typedrequest');
const typedrouter = new typedrequest.TypedRouter();
testServer.addTypedRequest(typedrouter);
testServer.addTypedSocket(typedrouter);
});
// =====================
// start the server and test the configuration
// =====================
tap.test('should start the server allright', async () => {
await testServer.start(3000);
});
// see if a demo request holds up
tap.test('should issue a request', async (tools) => {
const response = await smartrequest.postJson('http://127.0.0.1:3000/someroute', {
headers: {
'X-Forwarded-Proto': 'https',
},
requestBody: {
someprop: 'hi',
},
});
console.log(response.body);
});
tap.test('should get a file from disk', async () => {
const response = await fetch('http://127.0.0.1:3000/someroute/testresponse.js');
console.log(response.status);
console.log(response.headers);
});
tap.test('should answer a preflight request', async () => {
const response = await fetch('http://127.0.0.1:3000/some/randompath/', {
method: 'OPTIONS',
});
console.log(response.headers);
});
tap.test('should exposer a sitemap', async () => {
const response = await fetch('http://127.0.0.1:3000/sitemap');
console.log(await response.text());
});
// ========
// clean up
// ========
tap.test('should stop the server', async () => {
await testServer.stop();
});
tap.start();

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@apiglobal/typedserver',
version: '2.0.35',
version: '2.0.58',
description: 'easy serving of static files'
}

View File

@ -1 +1,10 @@
import * as plugins from './typedserver.plugins.js';
import * as servertools from './servertools/index.js';
export { servertools };
export * from './typedserver.classes.typedserver.js';
// Type helpers
export type Request = plugins.express.Request;
export type Response = plugins.express.Response;

View File

@ -1,12 +1,3 @@
import * as typedrequestInterfaces from '@apiglobal/typedrequest-interfaces';
export interface IReq_PushLatestServerChangeTime extends typedrequestInterfaces.implementsTR<
typedrequestInterfaces.ITypedRequest,
IReq_PushLatestServerChangeTime
> {
method: 'pushLatestServerChangeTime',
request: {
time: number;
};
response: {}
}
export * from './requestmodifier.js';
export * from './responsemodifier.js';
export * from './typedrequests.js';

View File

@ -0,0 +1,11 @@
export type TRequestModifier = <T>(responseArg: {
headers: { [header: string]: string | string[] | undefined };
path: string;
body: string;
travelData?: T;
}) => Promise<{
headers: { [header: string]: string | string[] | undefined };
path: string;
body: string;
travelData?: T;
}>;

View File

@ -0,0 +1,11 @@
export type TResponseModifier = <T>(responseArg: {
headers: { [header: string]: number | string | string[] | undefined };
path: string;
responseContent: string;
travelData?: T;
}) => Promise<{
headers: { [header: string]: number | string | string[] | undefined };
path: string;
responseContent: string;
travelData?: T;
}>;

View File

@ -0,0 +1,25 @@
import * as typedrequestInterfaces from '@apiglobal/typedrequest-interfaces';
export interface IReq_PushLatestServerChangeTime
extends typedrequestInterfaces.implementsTR<
typedrequestInterfaces.ITypedRequest,
IReq_PushLatestServerChangeTime
> {
method: 'pushLatestServerChangeTime';
request: {
time: number;
};
response: {};
}
export interface IReq_GetLatestServerChangeTime
extends typedrequestInterfaces.implementsTR<
typedrequestInterfaces.ITypedRequest,
IReq_GetLatestServerChangeTime
> {
method: 'getLatestServerChangeTime';
request: {};
response: {
time: number;
};
}

View File

@ -0,0 +1,35 @@
import { Handler } from './classes.handler.js';
import { Server } from './classes.server.js';
import * as plugins from '../typedserver.plugins.js';
export class Feed {
public smartexpressRef: Server;
public smartfeedInstance = new plugins.smartfeed.Smartfeed();
public feedHandler = new Handler('GET', async (req, res) => {
if (!this.smartexpressRef.options.feedMetadata) {
res.status(500);
res.write('feed metadata is missing');
res.end();
return;
}
if (!this.smartexpressRef.options.articleGetterFunction) {
res.status(500);
res.write('no article getter function defined.');
res.end();
return;
}
const xmlString = await this.smartfeedInstance.createFeedFromArticleArray(
this.smartexpressRef.options.feedMetadata,
await this.smartexpressRef.options.articleGetterFunction()
);
res.type('.xml');
res.write(xmlString);
res.end();
});
constructor(smartexpressRefArg: Server) {
this.smartexpressRef = smartexpressRefArg;
this.smartexpressRef.addRouteBefore('/feed', this.feedHandler);
}
}

View File

@ -0,0 +1,17 @@
import * as plugins from '../typedserver.plugins.js';
import { type Request, type Response } from 'express';
export interface IHandlerFunction {
(requestArg: Request, responseArg: Response): void;
}
export type THttpMethods = 'ALL' | 'GET' | 'POST' | 'PUT' | 'DELETE';
export class Handler {
httpMethod: THttpMethods;
handlerFunction: IHandlerFunction;
constructor(httpMethodArg: THttpMethods, handlerArg: IHandlerFunction) {
this.httpMethod = httpMethodArg;
this.handlerFunction = handlerArg;
}
}

View File

@ -0,0 +1,77 @@
import * as plugins from '../typedserver.plugins.js';
import { Handler } from './classes.handler.js';
import * as interfaces from '../interfaces/index.js';
export class HandlerProxy extends Handler {
/**
* The constuctor of HandlerProxy
* @param remoteMountPointArg
*/
constructor(
remoteMountPointArg: string,
optionsArg?: {
responseModifier?: interfaces.TResponseModifier;
headers?: { [key: string]: string };
}
) {
super('ALL', async (req, res) => {
const relativeRequestPath = req.path.slice(req.route.path.length - 1);
const proxyRequestUrl = remoteMountPointArg + relativeRequestPath;
console.log(`proxy ${req.path} to ${proxyRequestUrl}`);
let proxiedResponse: plugins.smartrequest.IExtendedIncomingMessage;
try {
proxiedResponse = await plugins.smartrequest.request(proxyRequestUrl, {
method: req.method,
autoJsonParse: false,
});
} catch {
res.end('failed to fullfill request');
return;
}
for (const header of Object.keys(proxiedResponse.headers)) {
res.set(header, proxiedResponse.headers[header] as string);
}
// set additional headers
if (optionsArg && optionsArg.headers) {
for (const key of Object.keys(optionsArg.headers)) {
res.set(key, optionsArg.headers[key]);
}
}
let responseToSend: string = proxiedResponse.body;
if (typeof responseToSend !== 'string') {
console.log(proxyRequestUrl);
console.log(responseToSend);
throw new Error(`Proxied response is not a string, but ${typeof responseToSend}`);
}
if (optionsArg && optionsArg.responseModifier) {
const modifiedResponse = await optionsArg.responseModifier({
headers: res.getHeaders(),
path: req.path,
responseContent: responseToSend,
});
// headers
for (const key of Object.keys(res.getHeaders())) {
if (!modifiedResponse.headers[key]) {
res.removeHeader(key);
}
}
for (const key of Object.keys(modifiedResponse.headers)) {
res.setHeader(key, modifiedResponse.headers[key]);
}
// responseContent
responseToSend = modifiedResponse.responseContent;
}
res.status(200);
res.write(responseToSend);
res.end();
});
}
}

View File

@ -0,0 +1,126 @@
import * as plugins from '../typedserver.plugins.js';
import * as interfaces from '../interfaces/index.js';
import { Handler } from './classes.handler.js';
export class HandlerStatic extends Handler {
constructor(
pathArg: string,
optionsArg?: {
requestModifier?: interfaces.TRequestModifier;
responseModifier?: interfaces.TResponseModifier;
headers?: { [key: string]: string };
serveIndexHtmlDefault?: boolean;
}
) {
super('GET', async (req, res) => {
let requestPath = req.path;
let requestHeaders = req.headers;
let requestBody = req.body;
let travelData: unknown;
if (optionsArg && optionsArg.requestModifier) {
const modifiedRequest = await optionsArg.requestModifier({
headers: requestHeaders,
path: requestPath,
body: requestBody,
});
requestHeaders = modifiedRequest.headers;
requestPath = modifiedRequest.path;
requestBody = modifiedRequest.body;
travelData = modifiedRequest.travelData;
}
// lets compute some paths
let filePath: string = requestPath.slice(req.route.path.length - 1); // lets slice of the root
if (requestPath === '') {
console.log('replaced root with index.html');
filePath = 'index.html';
}
console.log(filePath);
const joinedPath = plugins.path.join(pathArg, filePath);
const defaultPath = plugins.path.join(pathArg, 'index.html');
let parsedPath = plugins.path.parse(joinedPath);
let usedPath: string;
// important security checks
if (
requestPath.includes('..') || // don't allow going up the filePath
requestPath.includes('~') || // don't allow referencing of home directory
!joinedPath.startsWith(pathArg) // make sure the joined path is within the directory
) {
res.writeHead(500);
res.end();
return;
}
// set additional headers
if (optionsArg && optionsArg.headers) {
for (const key of Object.keys(optionsArg.headers)) {
res.set(key, optionsArg.headers[key]);
}
}
// lets actually care about serving, if security checks pass
let fileString: string;
let fileEncoding: 'binary' | 'utf8';
try {
fileString = plugins.smartfile.fs.toStringSync(joinedPath);
fileEncoding = plugins.smartmime.getEncoding(joinedPath);
usedPath = joinedPath;
} catch (err) {
// try serving index.html instead
console.log(`could not resolve ${joinedPath}`);
if (optionsArg && optionsArg.serveIndexHtmlDefault) {
console.log(`serving default path ${defaultPath} instead of ${joinedPath}`);
try {
parsedPath = plugins.path.parse(defaultPath);
fileString = plugins.smartfile.fs.toStringSync(defaultPath);
fileEncoding = plugins.smartmime.getEncoding(defaultPath);
usedPath = defaultPath;
} catch (err) {
res.writeHead(500);
res.end('File not found!');
return;
}
} else {
res.writeHead(500);
res.end('File not found!');
return;
}
}
res.type(parsedPath.ext);
const headers = res.getHeaders();
// lets modify the response at last
if (optionsArg && optionsArg.responseModifier) {
const modifiedResponse = await optionsArg.responseModifier({
headers: res.getHeaders(),
path: usedPath,
responseContent: fileString,
travelData,
});
// headers
for (const key of Object.keys(res.getHeaders())) {
if (!modifiedResponse.headers[key]) {
res.removeHeader(key);
}
}
for (const key of Object.keys(modifiedResponse.headers)) {
res.setHeader(key, modifiedResponse.headers[key]);
}
// responseContent
fileString = modifiedResponse.responseContent;
}
res.status(200);
res.write(Buffer.from(fileString, fileEncoding));
res.end();
});
}
}

View File

@ -0,0 +1,17 @@
import * as plugins from '../typedserver.plugins.js';
import { Handler } from './classes.handler.js';
import * as interfaces from '../interfaces/index.js';
export class HandlerTypedRouter extends Handler {
/**
* The constuctor of HandlerProxy
* @param remoteMountPointArg
*/
constructor(typedrouter: plugins.typedrequest.TypedRouter) {
super('POST', async (req, res) => {
const response = await typedrouter.routeAndAddResponse(req.body);
res.json(response);
});
}
}

View File

@ -0,0 +1,32 @@
import * as plugins from '../typedserver.plugins.js';
import { Handler } from './classes.handler.js';
import { Server } from './classes.server.js';
import { ObjectMap } from '@pushrocks/lik';
import { type IRoute as IExpressRoute } from 'express';
export class Route {
public routeString: string;
public handlerObjectMap = new ObjectMap<Handler>();
public expressMiddlewareObjectMap = new ObjectMap<any>();
public expressRoute: IExpressRoute; // will be set to server route on server start
constructor(ServerArg: Server, routeStringArg: string) {
this.routeString = routeStringArg;
}
/**
* add a handler to do something with requests
* @param handlerArg
*/
public addHandler(handlerArg: Handler) {
this.handlerObjectMap.add(handlerArg);
}
/**
* add a express middleware
* @param middlewareArg
*/
public addExpressMiddleWare(middlewareArg: plugins.express.Application) {
this.expressMiddlewareObjectMap.add(middlewareArg);
}
}

View File

@ -0,0 +1,294 @@
import * as plugins from '../typedserver.plugins.js';
import { Route } from './classes.route.js';
import { Handler } from './classes.handler.js';
import { HandlerTypedRouter } from './classes.handlertypedrouter.js';
// export types
import { setupRobots } from './tools.robots.js';
import { setupManifest } from './tools.manifest.js';
import { Sitemap } from './classes.sitemap.js';
import { Feed } from './classes.feed.js';
import { type IServerOptions } from '../typedserver.classes.typedserver.js';
export type TServerStatus = 'initiated' | 'running' | 'stopped';
/**
* can be used to spawn a server to answer http/https calls
* for constructor options see [[IServerOptions]]
*/
export class Server {
public httpServer: plugins.http.Server | plugins.https.Server;
public expressAppInstance: plugins.express.Application;
public routeObjectMap = new Array<Route>();
public options: IServerOptions;
public serverStatus: TServerStatus = 'initiated';
public feed: Feed;
public sitemap: Sitemap;
public executeAfterStartFunctions: (() => Promise<void>)[] = [];
// do stuff when server is ready
private startedDeferred = plugins.smartpromise.defer();
// tslint:disable-next-line:member-ordering
public startedPromise = this.startedDeferred.promise;
private socketMap = new plugins.lik.ObjectMap<plugins.net.Socket>();
constructor(optionsArg: IServerOptions) {
this.options = {
...optionsArg,
};
}
/**
* allows updating of server options
* @param optionsArg
*/
public updateServerOptions(optionsArg: IServerOptions) {
Object.assign(this.options, optionsArg);
}
public addTypedRequest(typedrouter: plugins.typedrequest.TypedRouter) {
this.addRoute('/typedrequest', new HandlerTypedRouter(typedrouter));
}
public addTypedSocket(typedrouter: plugins.typedrequest.TypedRouter): void {
this.executeAfterStartFunctions.push(async () => {
plugins.typedsocket.TypedSocket.createServer(typedrouter, this);
});
}
public addRoute(routeStringArg: string, handlerArg?: Handler) {
const route = new Route(this, routeStringArg);
if (handlerArg) {
route.addHandler(handlerArg);
}
this.routeObjectMap.push(route);
return route;
}
public addRouteBefore(routeStringArg: string, handlerArg?: Handler) {
const route = new Route(this, routeStringArg);
if (handlerArg) {
route.addHandler(handlerArg);
}
this.routeObjectMap.unshift(route);
return route;
}
public async start(portArg: number | string = this.options.port, doListen = true) {
const done = plugins.smartpromise.defer();
if (typeof portArg === 'string') {
portArg = parseInt(portArg);
}
this.expressAppInstance = plugins.express();
if (!this.httpServer && (!this.options.privateKey || !this.options.publicKey)) {
console.log('Got no SSL certificates. Please ensure encryption using e.g. a reverse proxy');
this.httpServer = plugins.http.createServer(this.expressAppInstance);
} else if (!this.httpServer) {
console.log('Got SSL certificate. Using it for the http server');
this.httpServer = plugins.https.createServer(
{
key: this.options.privateKey,
cert: this.options.publicKey,
},
this.expressAppInstance
);
} else {
console.log('Using externally supplied http server');
}
this.httpServer.keepAliveTimeout = 600 * 1000;
this.httpServer.headersTimeout = 20 * 1000;
// general request handlling
this.expressAppInstance.use((req, res, next) => {
req.setTimeout(60 * 1000);
next();
});
// forceSsl
if (this.options.forceSsl) {
this.expressAppInstance.set('forceSSLOptions', {
enable301Redirects: true,
trustXFPHeader: true,
sslRequiredMessage: 'SSL Required.',
});
this.expressAppInstance.use(plugins.expressForceSsl);
}
// cors
if (this.options.cors) {
const cors = plugins.cors({
allowedHeaders: '*',
methods: '*',
origin: '*',
});
this.expressAppInstance.use(cors);
this.expressAppInstance.options('/*', cors);
}
this.expressAppInstance.use((req, res, next) => {
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
res.setHeader('Cross-Origin-Embedder-Policy', 'unsafe-none');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('SERVEZONE_ROUTE', 'LOSSLESS_ORIGIN_CONTAINER');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Expires', new Date(Date.now()).toUTCString());
next();
});
// body parsing
this.expressAppInstance.use(plugins.bodyParser.json({ limit: 100000000 })); // for parsing application/json
this.expressAppInstance.use(plugins.bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
// robots
if (this.options.robots && this.options.domain) {
await setupRobots(this, this.options.domain);
}
// manifest.json
if (this.options.manifest) {
await setupManifest(this.expressAppInstance, this.options.manifest);
}
// sitemaps
if (this.options.sitemap) {
this.sitemap = new Sitemap(this);
}
if (this.options.feed) {
// feed
this.feed = new Feed(this);
}
// appVersion
if (this.options.appVersion) {
this.expressAppInstance.use((req, res, next) => {
res.set('appversion', this.options.appVersion);
next();
});
this.addRoute(
'/appversion',
new Handler('GET', async (req, res) => {
res.write(this.options.appVersion);
res.end();
})
);
}
// set up routes in for express
await this.routeObjectMap.forEach(async (routeArg) => {
console.log(
`"${routeArg.routeString}" maps to ${routeArg.handlerObjectMap.getArray().length} handlers`
);
const expressRoute = this.expressAppInstance.route(routeArg.routeString);
routeArg.handlerObjectMap.forEach(async (handler) => {
console.log(` -> ${handler.httpMethod}`);
switch (handler.httpMethod) {
case 'GET':
expressRoute.get(handler.handlerFunction);
return;
case 'POST':
expressRoute.post(handler.handlerFunction);
return;
case 'PUT':
expressRoute.put(handler.handlerFunction);
return;
case 'ALL':
expressRoute.all(handler.handlerFunction);
return;
case 'DELETE':
expressRoute.delete(handler.handlerFunction);
return;
default:
return;
}
});
});
if (this.options.defaultAnswer) {
this.expressAppInstance.get('/', async (request, response) => {
response.send(await this.options.defaultAnswer());
});
}
this.httpServer.on('connection', (connection: plugins.net.Socket) => {
this.socketMap.add(connection);
console.log(`added connection. now ${this.socketMap.getArray().length} sockets connected.`);
const cleanupConnection = () => {
if (this.socketMap.checkForObject(connection)) {
this.socketMap.remove(connection);
console.log(`removed connection. ${this.socketMap.getArray().length} sockets remaining.`);
connection.destroy();
}
};
connection.on('close', () => {
cleanupConnection();
});
connection.on('error', () => {
cleanupConnection();
});
connection.on('end', () => {
cleanupConnection();
});
connection.on('timeout', () => {
cleanupConnection();
});
});
// finally listen on a port
if (doListen) {
this.httpServer.listen(portArg, '0.0.0.0', () => {
console.log(`now listening on ${portArg}!`);
this.startedDeferred.resolve();
this.serverStatus = 'running';
done.resolve();
});
} else {
console.log(
'The server does not listen on a network stack and instead expects to get handed requests by other mechanics'
);
}
await done.promise;
for (const executeAfterStartFunction of this.executeAfterStartFunctions) {
await executeAfterStartFunction();
}
}
public getHttpServer() {
return this.httpServer;
}
public getExpressAppInstance() {
return this.expressAppInstance;
}
public async stop() {
const done = plugins.smartpromise.defer();
if (this.httpServer) {
this.httpServer.close(async () => {
this.serverStatus = 'stopped';
done.resolve();
});
await this.socketMap.forEach(async (socket) => {
socket.destroy();
});
} else {
throw new Error('There is no Server to be stopped. Have you started it?');
}
return await done.promise;
}
/**
* allows handling requests and responses that come from other
* @param req
* @param res
*/
public async handleReqRes(req: plugins.express.Request, res: plugins.express.Response) {
this.expressAppInstance(req, res);
}
}

View File

@ -0,0 +1,68 @@
import { Server } from './classes.server.js';
import { Handler } from './classes.handler.js';
import * as plugins from '../typedserver.plugins.js';
import { type IUrlInfo } from '@pushrocks/smartsitemap';
export class Sitemap {
public smartexpressRef: Server;
public smartSitemap = new plugins.smartsitemap.SmartSitemap();
public urls: plugins.smartsitemap.IUrlInfo[] = [];
/**
* handles the normal sitemap request
*/
public sitemapHandler = new Handler('GET', async (req, res) => {
const sitemapXmlString = await this.smartSitemap.createSitemapFromUrlInfoArray(this.urls);
res.type('.xml');
res.write(sitemapXmlString);
res.end();
});
/**
* handles the sitemap-news request
*/
public sitemapNewsHandler = new Handler('GET', async (req, res) => {
if (!this.smartexpressRef.options.articleGetterFunction) {
res.status(500);
res.write('no article getter function defined.');
res.end();
return;
}
const sitemapNewsXml = await this.smartSitemap.createSitemapNewsFromArticleArray(
await this.smartexpressRef.options.articleGetterFunction()
);
res.type('.xml');
res.write(sitemapNewsXml);
res.end();
});
constructor(smartexpressRefArg: Server) {
this.smartexpressRef = smartexpressRefArg;
this.smartexpressRef.addRouteBefore('/sitemap', this.sitemapHandler);
this.smartexpressRef.addRouteBefore('/sitemap-news', this.sitemapNewsHandler);
// lets set the default url
if (this.smartexpressRef.options.domain) {
this.urls.push({
url: `https://${this.smartexpressRef.options.domain}/`,
timestamp: Date.now(),
frequency: 'daily',
});
}
}
/**
* replaces the current urlsArray
* @param urlsArg
*/
public replaceUrls(urlsArg: IUrlInfo[]) {
this.urls = urlsArg;
}
/**
* adds urls to the current set of urls
*/
public addUrls(urlsArg: IUrlInfo[]) {
this.urls = this.urls.concat(this.urls, urlsArg);
}
}

6
ts/servertools/index.ts Normal file
View File

@ -0,0 +1,6 @@
export * from './classes.server.js';
export * from './classes.route.js';
export * from './classes.handler.js';
export * from './classes.handlerstatic.js';
export * from './classes.handlerproxy.js';
export * from './classes.handlertypedrouter.js';

View File

@ -0,0 +1,14 @@
import * as plugins from '../typedserver.plugins.js';
export const setupManifest = async (
expressInstanceArg: plugins.express.Application,
manifestArg: plugins.smartmanifest.ISmartManifestConstructorOptions
) => {
const smartmanifestInstance = new plugins.smartmanifest.SmartManifest(manifestArg);
expressInstanceArg.get('/manifest.json', async (req, res) => {
res.status(200);
res.type('json');
res.write(smartmanifestInstance.jsonString());
res.end();
});
};

View File

@ -0,0 +1,33 @@
import * as plugins from '../typedserver.plugins.js';
import { Server } from './classes.server.js';
import { Handler } from './classes.handler.js';
export const setupRobots = async (smartexpressRefArg: Server, domainArg: string) => {
smartexpressRefArg.addRouteBefore(
'/robots.txt',
new Handler('GET', async (req, res) => {
res.type('text/plain');
res.send(`
User-agent: Googlebot-News
Disallow: /account
Disallow: /login
User-agent: *
Disallow: /account
Disallow: /login
${
smartexpressRefArg.options.blockWaybackMachine
? `
User-Agent: ia_archiver
Disallow: /
`
: ``
}
Sitemap: https://${domainArg}/sitemap
Sitemap: https://${domainArg}/sitemap-news
`);
})
);
};

View File

@ -0,0 +1,22 @@
import * as plugins from '../typedserver.plugins.js';
import { Server } from './classes.server.js';
import { Handler } from './classes.handler.js';
export const redirectFrom80To443 = async () => {
const smartexpressInstance = new Server({
cors: true,
forceSsl: true,
port: 80,
});
smartexpressInstance.addRoute(
'*',
new Handler('ALL', async (req, res) => {
res.redirect('https://' + req.headers.host + req.url);
})
);
await smartexpressInstance.start();
return smartexpressInstance;
};

View File

@ -1,12 +1,58 @@
import * as plugins from './typedserver.plugins.js';
import * as paths from './typedserver.paths.js';
import * as interfaces from './interfaces/index.js';
import * as servertools from './servertools/index.js';
export interface IEasyServerConstructorOptions {
serveDir: string;
injectReload: boolean;
port?: number;
export interface IServerOptions {
/**
* serve a particular directory
*/
serveDir?: string;
/**
* inject a reload script that takes care of live reloading
*/
injectReload?: boolean;
/**
* watch the serve directory?
*/
watch?: boolean;
cors: boolean;
/**
* a default answer given in case there is no other handler.
* @returns
*/
defaultAnswer?: () => Promise<string>;
/**
* will try to reroute traffic to an ssl connection using headers
*/
forceSsl?: boolean;
/**
* allows serving manifests
*/
manifest?: plugins.smartmanifest.ISmartManifestConstructorOptions;
/**
* the port to listen on
* can be overwritten when actually starting the server
*/
port?: number | string;
publicKey?: string;
privateKey?: string;
sitemap?: boolean;
feed?: boolean;
robots?: boolean;
domain?: string;
/**
* convey information about the app being served
*/
appVersion?: string;
feedMetadata?: plugins.smartfeed.IFeedOptions;
articleGetterFunction?: () => Promise<plugins.tsclass.content.IArticle[]>;
blockWaybackMachine?: boolean;
}
export class TypedServer {
@ -14,8 +60,8 @@ export class TypedServer {
// nothing here yet
// instance
public options: IEasyServerConstructorOptions;
public smartexpressInstance: plugins.smartexpress.Server;
public options: IServerOptions;
public server: servertools.Server;
public smartchokInstance: plugins.smartchok.Smartchok;
public serveDirHashSubject = new plugins.smartrx.rxjs.ReplaySubject<string>(1);
public serveHash: string = '000000';
@ -24,34 +70,24 @@ export class TypedServer {
public lastReload: number = Date.now();
public ended = false;
constructor(optionsArg: IEasyServerConstructorOptions) {
const standardOptions: IEasyServerConstructorOptions = {
injectReload: true,
constructor(optionsArg: IServerOptions) {
const standardOptions: IServerOptions = {
port: 3000,
serveDir: process.cwd(),
watch: true,
injectReload: false,
serveDir: null,
watch: false,
cors: true,
};
this.options = {
...standardOptions,
...optionsArg,
};
}
/**
* inits and starts the server
*/
public async start() {
// set the smartexpress instance
this.smartexpressInstance = new plugins.smartexpress.Server({
port: this.options.port,
forceSsl: false,
cors: true,
});
this.server = new servertools.Server(this.options);
// add routes to the smartexpress instance
this.smartexpressInstance.addRoute(
this.server.addRoute(
'/typedserver/:request',
new plugins.smartexpress.Handler('ALL', async (req, res) => {
new servertools.Handler('ALL', async (req, res) => {
switch (req.params.request) {
case 'devtools':
res.setHeader('Content-Type', 'text/javascript');
@ -73,67 +109,85 @@ export class TypedServer {
}
})
);
}
this.smartexpressInstance.addRoute(
'/*',
new plugins.smartexpress.HandlerStatic(this.options.serveDir, {
responseModifier: async (responseArg) => {
let fileString = responseArg.responseContent;
if (plugins.path.parse(responseArg.path).ext === '.html') {
const fileStringArray = fileString.split('<head>');
if (this.options.injectReload && fileStringArray.length === 2) {
fileStringArray[0] = `${fileStringArray[0]}<head>
<!-- injected by @apiglobal/typedserver start -->
<script async defer type="module" src="/typedserver/devtools"></script>
<script>
globalThis.typedserver = {
lastReload: '${this.lastReload}',
versionInfo: ${JSON.stringify({}, null, 2)},
}
</script>
<!-- injected by @apiglobal/typedserver stop -->
`;
fileString = fileStringArray.join('');
console.log('injected typedserver script.');
} else if (this.options.injectReload) {
console.log('Could not insert typedserver script');
/**
* inits and starts the server
*/
public async start() {
if (this.options.serveDir) {
this.server.addRoute(
'/*',
new servertools.HandlerStatic(this.options.serveDir, {
responseModifier: async (responseArg) => {
let fileString = responseArg.responseContent;
if (plugins.path.parse(responseArg.path).ext === '.html') {
const fileStringArray = fileString.split('<head>');
if (this.options.injectReload && fileStringArray.length === 2) {
fileStringArray[0] = `${fileStringArray[0]}<head>
<!-- injected by @apiglobal/typedserver start -->
<script async defer type="module" src="/typedserver/devtools"></script>
<script>
globalThis.typedserver = {
lastReload: ${this.lastReload},
versionInfo: ${JSON.stringify({}, null, 2)},
}
</script>
<!-- injected by @apiglobal/typedserver stop -->
`;
fileString = fileStringArray.join('');
console.log('injected typedserver script.');
} else if (this.options.injectReload) {
console.log('Could not insert typedserver script');
}
}
}
const headers = responseArg.headers;
headers.appHash = this.serveHash;
headers['Cache-Control'] = 'no-cache, no-store, must-revalidate';
headers['Pragma'] = 'no-cache';
headers['Expires'] = '0';
return {
headers,
path: responseArg.path,
responseContent: fileString,
};
},
serveIndexHtmlDefault: true,
})
);
this.smartchokInstance = new plugins.smartchok.Smartchok([this.options.serveDir], {});
if (this.options.watch) {
const headers = responseArg.headers;
headers.appHash = this.serveHash;
headers['Cache-Control'] = 'no-cache, no-store, must-revalidate';
headers['Pragma'] = 'no-cache';
headers['Expires'] = '0';
return {
headers,
path: responseArg.path,
responseContent: fileString,
};
},
serveIndexHtmlDefault: true,
})
);
} else if (this.options.injectReload) {
throw new Error(
'You set to inject the reload script without a serve dir. This is not supported at the moment.'
);
}
if (this.options.watch && this.options.serveDir) {
this.smartchokInstance = new plugins.smartchok.Smartchok([this.options.serveDir], {});
await this.smartchokInstance.start();
(await this.smartchokInstance.getObservableFor('change')).subscribe(async () => {
await this.createServeDirHash();
this.reload();
});
await this.createServeDirHash();
}
await this.createServeDirHash();
// lets start the server
await this.smartexpressInstance.start();
console.log('open url in browser');
await this.server.start();
this.typedsocket = await plugins.typedsocket.TypedSocket.createServer(
this.typedrouter,
this.smartexpressInstance
this.server
);
// lets setup typedrouter
this.typedrouter.addTypedHandler<interfaces.IReq_GetLatestServerChangeTime>(
new plugins.typedrequest.TypedHandler('getLatestServerChangeTime', async (reqDataArg) => {
return {
time: this.lastReload,
};
})
);
// console.log('open url in browser');
// await plugins.smartopen.openUrl(`http://testing.git.zone:${this.options.port}`);
}
@ -142,7 +196,9 @@ export class TypedServer {
*/
public async reload() {
this.lastReload = Date.now();
for (const connectionArg of await this.typedsocket.findAllTargetConnections(async () => true)) {
for (const connectionArg of await this.typedsocket.findAllTargetConnectionsByTag(
'typedserver_frontend'
)) {
const pushTime =
this.typedsocket.createTypedRequest<interfaces.IReq_PushLatestServerChangeTime>(
'pushLatestServerChangeTime',
@ -156,9 +212,11 @@ export class TypedServer {
public async stop() {
this.ended = true;
await this.smartexpressInstance.stop();
await this.server.stop();
await this.typedsocket.stop();
await this.smartchokInstance.stop();
if (this.smartchokInstance) {
await this.smartchokInstance.stop();
}
}
public async createServeDirHash() {

View File

@ -1,36 +1,61 @@
// node native
import * as http from 'http';
import * as https from 'https';
import * as net from 'net';
import * as path from 'path';
export { path };
export { http, https, net, path };
// @tsclass scope
import * as tsclass from '@tsclass/tsclass';
export { tsclass };
// @apiglobal scope
import * as typedrequest from '@apiglobal/typedrequest';
import * as typedrequestInterfaces from '@apiglobal/typedrequest-interfaces';
import * as typedsocket from '@apiglobal/typedsocket';
export {
typedrequest,
typedrequestInterfaces,
typedsocket,
}
export { typedrequest, typedrequestInterfaces, typedsocket };
// @pushrocks scope
import * as lik from '@pushrocks/lik';
import * as smartchok from '@pushrocks/smartchok';
import * as smartdelay from '@pushrocks/smartdelay';
import * as smartexpress from '@pushrocks/smartexpress';
import * as smartfeed from '@pushrocks/smartfeed';
import * as smartfile from '@pushrocks/smartfile';
import * as smartmanifest from '@pushrocks/smartmanifest';
import * as smartmime from '@pushrocks/smartmime';
import * as smartopen from '@pushrocks/smartopen';
import * as smartpath from '@pushrocks/smartpath';
import * as smartpromise from '@pushrocks/smartpromise';
import * as smartrequest from '@pushrocks/smartrequest';
import * as smartrx from '@pushrocks/smartrx';
import * as smartsitemap from '@pushrocks/smartsitemap';
import * as smarttime from '@pushrocks/smarttime';
export {
lik,
smartchok,
smartdelay,
smartexpress,
smartfeed,
smartfile,
smartmanifest,
smartmime,
smartopen,
smartpath,
smartpromise,
smartrequest,
smartsitemap,
smarttime,
smartrx,
};
// express
import bodyParser from 'body-parser';
import cors from 'cors';
import express from 'express';
// @ts-ignore
import expressForceSsl from 'express-force-ssl';
export { bodyParser, cors, express, expressForceSsl };

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@apiglobal/typedserver',
version: '2.0.35',
version: '2.0.58',
description: 'easy serving of static files'
}

View File

@ -61,11 +61,15 @@ export class ReloadChecker {
public async checkReload(lastServerChange: number) {
let reloadJustified = false;
(await this.store.get(this.storeKey)) !== lastServerChange ? (reloadJustified = true) : null;
let storedLastServerChange = await this.store.get(this.storeKey);
if (storedLastServerChange && storedLastServerChange !== lastServerChange) {
reloadJustified = true;
} else {
}
if (reloadJustified) {
this.store.set(this.storeKey, lastServerChange);
const reloadText = `about to reload ${
const reloadText = `upgrading... ${
globalThis.globalSw ? '(purging the sw cache first...)' : ''
}`;
this.infoscreen.setText(reloadText);
@ -98,8 +102,29 @@ export class ReloadChecker {
this.typedrouter,
plugins.typedsocket.TypedSocket.useWindowLocationOriginUrl()
);
this.typedsocket;
logger.log('success', `ReloadChecker connected through typedsocket!`)
this.typedsocket.addTag('typedserver_frontend', {});
this.typedsocket.eventSubject.subscribe(async (eventArg) => {
console.log(`typedsocket event subscription: ${eventArg}`);
if (
eventArg === 'disconnected' ||
eventArg === 'disconnecting' ||
eventArg === 'timedOut'
) {
this.backendConnectionLost = true;
this.infoscreen.setText(`typedsocket ${eventArg}!`);
} else if (eventArg === 'connected' && this.backendConnectionLost) {
this.backendConnectionLost = false;
this.infoscreen.setSuccess('typedsocket connected!');
// lets check if a reload is necessary
const getLatestServerChangeTime =
this.typedsocket.createTypedRequest<interfaces.IReq_GetLatestServerChangeTime>(
'getLatestServerChangeTime'
);
const response = await getLatestServerChangeTime.fire({});
this.checkReload(response.time);
}
});
logger.log('success', `ReloadChecker connected through typedsocket!`);
}
}

View File

@ -71,6 +71,7 @@ export class TypedserverInfoscreen extends LitElement {
];
public setText(textArg: string) {
this.success = false;
this.text = textArg;
this.show();
this.success = false;
@ -80,18 +81,27 @@ export class TypedserverInfoscreen extends LitElement {
this.text = textArg;
this.show();
this.success = true;
setTimeout(() => this.hide(), 1000);
}
private appended = false;
public show () {
this.appended = true;
public async show () {
document.body.append(this);
this.appended = true;
await plugins.smartdelay.delayFor(0);
const mainbox = this.shadowRoot.querySelector('.mainbox');
mainbox.classList.add('show');
}
public hide() {
this.success = false;
public async hide() {
this.text = '';
if (this.appended) {
const mainbox = this.shadowRoot.querySelector('.mainbox');
mainbox.classList.remove('show');
}
await plugins.smartdelay.delayFor(300);
if (this.appended) {
this.appended = false;
document.body.removeChild(this);
}
}
@ -99,7 +109,7 @@ export class TypedserverInfoscreen extends LitElement {
public render () {
return html`
<div class="mainbox ${this.show ? 'show' : ''} ${this.success ? 'success': ''}">${this.text}</div>
<div class="mainbox ${this.success ? 'success': ''}">${this.text}</div>
`
}

View File

@ -3,6 +3,7 @@
"experimentalDecorators": true,
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "nodenext"
"moduleResolution": "nodenext",
"esModuleInterop": true
}
}