feat(TypedServer): Integrate SmartServe controller routing; add built-in routes controller and refactor TypedServer to use controllers and FileServer
This commit is contained in:
13
changelog.md
13
changelog.md
@@ -1,5 +1,18 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-12-02 - 4.1.0 - feat(TypedServer)
|
||||
Integrate SmartServe controller routing; add built-in routes controller and refactor TypedServer to use controllers and FileServer
|
||||
|
||||
- Add BuiltInRoutesController exposing /robots.txt, /manifest.json, /sitemap, /sitemap-news, /feed and /appversion
|
||||
- Refactor TypedRequestHandler into a SmartServe-decorated TypedRequestController and register it with ControllerRegistry
|
||||
- Refactor TypedServer to use SmartServe: register controller instances, use ControllerRegistry matching, and delegate WebSocket integration to SmartServe
|
||||
- Introduce FileServer-based static serving with HTML reload script injection and improved default root handling
|
||||
- Expand supported HTTP methods to include HEAD and OPTIONS
|
||||
- Remove legacy FeedHelper and consolidate sitemap/feed handling into controllers and helpers
|
||||
- Enhance servertools legacy Express utilities: improved HandlerProxy, HandlerStatic, Compressor with caching and preferred compression support
|
||||
- Service worker subsystem improvements: CacheManager, NetworkManager, UpdateManager and backend enhancements for robust caching, revalidation and client reloads
|
||||
- Web-inject LitElement properties switched from private fields to accessor syntax (typedserver_web.infoscreen)
|
||||
|
||||
## 2025-12-02 - 4.0.0 - BREAKING CHANGE(typedserver)
|
||||
Migrate to new push.rocks packages and async smartfs API; replace smartchok with smartwatch; update deps and service worker handling
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@api.global/typedserver',
|
||||
version: '4.0.0',
|
||||
version: '4.1.0',
|
||||
description: 'A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.'
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import * as interfaces from '../dist_ts_interfaces/index.js';
|
||||
import { DevToolsHandler } from './controllers/controller.devtools.js';
|
||||
import { TypedRequestHandler } from './controllers/controller.typedrequest.js';
|
||||
import { DevToolsController } from './controllers/controller.devtools.js';
|
||||
import { TypedRequestController } from './controllers/controller.typedrequest.js';
|
||||
import { BuiltInRoutesController } from './controllers/controller.builtin.js';
|
||||
|
||||
export interface IServerOptions {
|
||||
/**
|
||||
@@ -57,7 +58,7 @@ export interface IServerOptions {
|
||||
blockWaybackMachine?: boolean;
|
||||
}
|
||||
|
||||
export type THttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'ALL';
|
||||
export type THttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS' | 'ALL';
|
||||
|
||||
export interface IRouteHandler {
|
||||
(request: Request): Promise<Response | null>;
|
||||
@@ -81,14 +82,17 @@ export class TypedServer {
|
||||
public typedsocket: plugins.typedsocket.TypedSocket;
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
// Sitemap and Feed helpers
|
||||
// Sitemap helper
|
||||
private sitemapHelper: SitemapHelper;
|
||||
private feedHelper: FeedHelper;
|
||||
private smartmanifestInstance: plugins.smartmanifest.SmartManifest;
|
||||
|
||||
// Request handlers
|
||||
private devToolsHandler: DevToolsHandler;
|
||||
private typedRequestHandler: TypedRequestHandler;
|
||||
// Decorated controllers
|
||||
private devToolsController: DevToolsController;
|
||||
private typedRequestController: TypedRequestController;
|
||||
private builtInRoutesController: BuiltInRoutesController;
|
||||
|
||||
// File server for static files
|
||||
private fileServer: plugins.smartserve.FileServer;
|
||||
|
||||
// Custom route handlers (for addRoute API)
|
||||
private customRoutes: IRegisteredRoute[] = [];
|
||||
@@ -108,13 +112,6 @@ export class TypedServer {
|
||||
...standardOptions,
|
||||
...optionsArg,
|
||||
};
|
||||
|
||||
// Initialize handlers
|
||||
this.devToolsHandler = new DevToolsHandler({
|
||||
getLastReload: () => this.lastReload,
|
||||
getEnded: () => this.ended,
|
||||
});
|
||||
this.typedRequestHandler = new TypedRequestHandler(this.typedrouter);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,11 +128,7 @@ export class TypedServer {
|
||||
* @param method - HTTP method (GET, POST, PUT, DELETE, PATCH, ALL)
|
||||
* @param handler - Async function that receives Request and returns Response or null
|
||||
*/
|
||||
public addRoute(
|
||||
path: string,
|
||||
method: THttpMethod,
|
||||
handler: IRouteHandler
|
||||
): void {
|
||||
public addRoute(path: string, method: THttpMethod, handler: IRouteHandler): void {
|
||||
// Convert Express-style path to regex
|
||||
const paramNames: string[] = [];
|
||||
let regexPattern = path
|
||||
@@ -199,13 +192,45 @@ export class TypedServer {
|
||||
if (this.options.sitemap) {
|
||||
this.sitemapHelper = new SitemapHelper(this.options.domain);
|
||||
}
|
||||
if (this.options.feed) {
|
||||
this.feedHelper = new FeedHelper();
|
||||
}
|
||||
if (this.options.manifest) {
|
||||
this.smartmanifestInstance = new plugins.smartmanifest.SmartManifest(this.options.manifest);
|
||||
}
|
||||
|
||||
// Initialize file server for static files
|
||||
if (this.options.serveDir) {
|
||||
this.fileServer = new plugins.smartserve.FileServer({
|
||||
root: this.options.serveDir,
|
||||
index: ['index.html'],
|
||||
etag: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize decorated controllers
|
||||
this.devToolsController = new DevToolsController({
|
||||
getLastReload: () => this.lastReload,
|
||||
getEnded: () => this.ended,
|
||||
});
|
||||
|
||||
this.typedRequestController = new TypedRequestController(this.typedrouter);
|
||||
|
||||
this.builtInRoutesController = new BuiltInRoutesController({
|
||||
domain: this.options.domain,
|
||||
robots: this.options.robots,
|
||||
manifest: this.smartmanifestInstance,
|
||||
sitemap: this.options.sitemap,
|
||||
feed: this.options.feed,
|
||||
appVersion: this.options.appVersion,
|
||||
feedMetadata: this.options.feedMetadata,
|
||||
articleGetterFunction: this.options.articleGetterFunction,
|
||||
blockWaybackMachine: this.options.blockWaybackMachine,
|
||||
getSitemapUrls: () => this.sitemapHelper?.urls || [],
|
||||
});
|
||||
|
||||
// Register controllers with SmartServe's ControllerRegistry
|
||||
plugins.smartserve.ControllerRegistry.registerInstance(this.devToolsController);
|
||||
plugins.smartserve.ControllerRegistry.registerInstance(this.typedRequestController);
|
||||
plugins.smartserve.ControllerRegistry.registerInstance(this.builtInRoutesController);
|
||||
|
||||
// Build SmartServe options
|
||||
const smartServeOptions: plugins.smartserve.ISmartServeOptions = {
|
||||
port,
|
||||
@@ -227,19 +252,12 @@ export class TypedServer {
|
||||
console.log(`WebSocket disconnected: ${peer.id}`);
|
||||
},
|
||||
},
|
||||
static: this.options.serveDir
|
||||
? {
|
||||
root: this.options.serveDir,
|
||||
index: ['index.html'],
|
||||
etag: true,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
this.smartServe = new plugins.smartserve.SmartServe(smartServeOptions);
|
||||
|
||||
// Set up custom request handler for all custom routes
|
||||
this.smartServe.setHandler(async (request: Request): Promise<Response | null> => {
|
||||
// Set up custom request handler that integrates with ControllerRegistry
|
||||
this.smartServe.setHandler(async (request: Request): Promise<Response> => {
|
||||
return this.handleRequest(request);
|
||||
});
|
||||
|
||||
@@ -282,168 +300,121 @@ export class TypedServer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an IRequestContext from a Request
|
||||
*/
|
||||
private async createContext(
|
||||
request: Request,
|
||||
params: Record<string, string>
|
||||
): Promise<plugins.smartserve.IRequestContext> {
|
||||
const url = new URL(request.url);
|
||||
const method = request.method.toUpperCase() as THttpMethod;
|
||||
|
||||
// Parse query params
|
||||
const query: Record<string, string> = {};
|
||||
url.searchParams.forEach((value, key) => {
|
||||
query[key] = value;
|
||||
});
|
||||
|
||||
// Parse body
|
||||
let body: unknown = undefined;
|
||||
const contentType = request.headers.get('content-type');
|
||||
if (contentType?.includes('application/json')) {
|
||||
try {
|
||||
body = await request.clone().json();
|
||||
} catch {
|
||||
body = {};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
request,
|
||||
body,
|
||||
params,
|
||||
query,
|
||||
headers: request.headers,
|
||||
path: url.pathname,
|
||||
method,
|
||||
url,
|
||||
runtime: 'node' as const,
|
||||
state: {},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Main request handler - routes to appropriate sub-handlers
|
||||
*/
|
||||
private async handleRequest(request: Request): Promise<Response | null> {
|
||||
private async handleRequest(request: Request): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
const path = url.pathname;
|
||||
const method = request.method;
|
||||
const method = request.method.toUpperCase() as THttpMethod;
|
||||
|
||||
// DevTools handler
|
||||
let response = await this.devToolsHandler.handle(request);
|
||||
if (response) return response;
|
||||
// First, try to match via ControllerRegistry (decorated routes)
|
||||
const match = plugins.smartserve.ControllerRegistry.matchRoute(path, method);
|
||||
if (match) {
|
||||
try {
|
||||
const context = await this.createContext(request, match.params);
|
||||
const result = await match.route.handler(context);
|
||||
|
||||
// TypedRequest handler
|
||||
response = await this.typedRequestHandler.handle(request);
|
||||
if (response) return response;
|
||||
// Handle Response or convert to Response
|
||||
if (result instanceof Response) {
|
||||
return result;
|
||||
}
|
||||
return new Response(JSON.stringify(result), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof plugins.smartserve.RouteNotFoundError) {
|
||||
// Route explicitly threw "not found", continue to other handlers
|
||||
} else {
|
||||
console.error('Controller error:', error);
|
||||
return new Response('Internal Server Error', { status: 500 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom routes (registered via addRoute)
|
||||
for (const route of this.customRoutes) {
|
||||
if (route.method === 'ALL' || route.method === method) {
|
||||
const params = this.parseRouteParams(route, path);
|
||||
if (params !== null) {
|
||||
// Attach params to request for handler to access
|
||||
(request as any).params = params;
|
||||
response = await route.handler(request);
|
||||
const response = await route.handler(request);
|
||||
if (response) return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Robots.txt
|
||||
if (this.options.robots && this.options.domain && path === '/robots.txt' && method === 'GET') {
|
||||
return this.handleRobots();
|
||||
}
|
||||
|
||||
// Manifest.json
|
||||
if (this.options.manifest && path === '/manifest.json' && method === 'GET') {
|
||||
return this.handleManifest();
|
||||
}
|
||||
|
||||
// Sitemap
|
||||
if (this.options.sitemap && path === '/sitemap' && method === 'GET') {
|
||||
return this.handleSitemap();
|
||||
}
|
||||
|
||||
// Sitemap News
|
||||
if (this.options.sitemap && path === '/sitemap-news' && method === 'GET') {
|
||||
return this.handleSitemapNews();
|
||||
}
|
||||
|
||||
// Feed
|
||||
if (this.options.feed && path === '/feed' && method === 'GET') {
|
||||
return this.handleFeed();
|
||||
}
|
||||
|
||||
// App version
|
||||
if (this.options.appVersion && path === '/appversion' && method === 'GET') {
|
||||
return new Response(this.options.appVersion, {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
});
|
||||
}
|
||||
|
||||
// HTML injection for reload (if enabled)
|
||||
if (this.options.injectReload && this.options.serveDir) {
|
||||
response = await this.handleHtmlWithInjection(request);
|
||||
const response = await this.handleHtmlWithInjection(request);
|
||||
if (response) return response;
|
||||
}
|
||||
|
||||
// Not handled - let SmartServe handle (static files, etc.)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle robots.txt request
|
||||
*/
|
||||
private handleRobots(): Response {
|
||||
const waybackBlock = this.options.blockWaybackMachine
|
||||
? `
|
||||
User-Agent: ia_archiver
|
||||
Disallow: /
|
||||
`
|
||||
: '';
|
||||
|
||||
const content = `
|
||||
User-agent: Googlebot-News
|
||||
Disallow: /account
|
||||
Disallow: /login
|
||||
|
||||
User-agent: *
|
||||
Disallow: /account
|
||||
Disallow: /login
|
||||
${waybackBlock}
|
||||
Sitemap: https://${this.options.domain}/sitemap
|
||||
Sitemap: https://${this.options.domain}/sitemap-news
|
||||
`;
|
||||
|
||||
return new Response(content.trim(), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle manifest.json request
|
||||
*/
|
||||
private handleManifest(): Response {
|
||||
return new Response(this.smartmanifestInstance.jsonString(), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle sitemap request
|
||||
*/
|
||||
private async handleSitemap(): Promise<Response> {
|
||||
const sitemapXmlString = await this.sitemapHelper.createSitemap();
|
||||
return new Response(sitemapXmlString, {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/xml' },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle sitemap-news request
|
||||
*/
|
||||
private async handleSitemapNews(): Promise<Response> {
|
||||
if (!this.options.articleGetterFunction) {
|
||||
return new Response('no article getter function defined.', { status: 500 });
|
||||
// Try static file serving
|
||||
if (this.fileServer && (method === 'GET' || method === 'HEAD')) {
|
||||
try {
|
||||
const staticResponse = await this.fileServer.serve(request);
|
||||
if (staticResponse) {
|
||||
return staticResponse;
|
||||
}
|
||||
} catch (error) {
|
||||
// Fall through to 404
|
||||
}
|
||||
}
|
||||
|
||||
const sitemapNewsXml = await this.sitemapHelper.createSitemapNews(
|
||||
await this.options.articleGetterFunction()
|
||||
);
|
||||
|
||||
return new Response(sitemapNewsXml, {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/xml' },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle feed request
|
||||
*/
|
||||
private async handleFeed(): Promise<Response> {
|
||||
if (!this.options.feedMetadata) {
|
||||
return new Response('feed metadata is missing', { status: 500 });
|
||||
// Default answer for root
|
||||
if (path === '/' && method === 'GET' && this.options.defaultAnswer) {
|
||||
const html = await this.options.defaultAnswer();
|
||||
return new Response(html, {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'text/html' },
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.options.articleGetterFunction) {
|
||||
return new Response('no article getter function defined.', { status: 500 });
|
||||
}
|
||||
|
||||
const xmlString = await this.feedHelper.createFeed(
|
||||
this.options.feedMetadata,
|
||||
await this.options.articleGetterFunction()
|
||||
);
|
||||
|
||||
return new Response(xmlString, {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/xml' },
|
||||
});
|
||||
// Not found
|
||||
return new Response('Not Found', { status: 404 });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -633,17 +604,3 @@ class SitemapHelper {
|
||||
this.urls = this.urls.concat(urlsArg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Feed helper class
|
||||
*/
|
||||
class FeedHelper {
|
||||
private smartfeedInstance = new plugins.smartfeed.Smartfeed();
|
||||
|
||||
async createFeed(
|
||||
feedMetadata: plugins.smartfeed.IFeedOptions,
|
||||
articles: plugins.tsclass.content.IArticle[]
|
||||
): Promise<string> {
|
||||
return this.smartfeedInstance.createFeedFromArticleArray(feedMetadata, articles);
|
||||
}
|
||||
}
|
||||
|
||||
125
ts/controllers/controller.builtin.ts
Normal file
125
ts/controllers/controller.builtin.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
/**
|
||||
* Built-in routes controller for TypedServer
|
||||
* Handles robots.txt, manifest.json, sitemap, feed, appversion
|
||||
*/
|
||||
@plugins.smartserve.Route('')
|
||||
export class BuiltInRoutesController {
|
||||
private options: {
|
||||
domain?: string;
|
||||
robots?: boolean;
|
||||
manifest?: plugins.smartmanifest.SmartManifest;
|
||||
sitemap?: boolean;
|
||||
feed?: boolean;
|
||||
appVersion?: string;
|
||||
feedMetadata?: plugins.smartfeed.IFeedOptions;
|
||||
articleGetterFunction?: () => Promise<plugins.tsclass.content.IArticle[]>;
|
||||
blockWaybackMachine?: boolean;
|
||||
getSitemapUrls: () => plugins.smartsitemap.IUrlInfo[];
|
||||
};
|
||||
|
||||
constructor(options: typeof BuiltInRoutesController.prototype.options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
@plugins.smartserve.Get('/robots.txt')
|
||||
async getRobots(ctx: plugins.smartserve.IRequestContext): Promise<Response> {
|
||||
if (!this.options.robots || !this.options.domain) {
|
||||
throw new plugins.smartserve.RouteNotFoundError(ctx.path, ctx.method);
|
||||
}
|
||||
|
||||
const robotsContent = [
|
||||
'User-agent: *',
|
||||
'Allow: /',
|
||||
`Sitemap: https://${this.options.domain}/sitemap`,
|
||||
];
|
||||
|
||||
if (this.options.blockWaybackMachine) {
|
||||
robotsContent.push('', 'User-agent: ia_archiver', 'Disallow: /');
|
||||
}
|
||||
|
||||
return new Response(robotsContent.join('\n'), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
});
|
||||
}
|
||||
|
||||
@plugins.smartserve.Get('/manifest.json')
|
||||
async getManifest(ctx: plugins.smartserve.IRequestContext): Promise<Response> {
|
||||
if (!this.options.manifest) {
|
||||
throw new plugins.smartserve.RouteNotFoundError(ctx.path, ctx.method);
|
||||
}
|
||||
|
||||
return new Response(this.options.manifest.jsonString(), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
@plugins.smartserve.Get('/sitemap')
|
||||
async getSitemap(ctx: plugins.smartserve.IRequestContext): Promise<Response> {
|
||||
if (!this.options.sitemap || !this.options.domain) {
|
||||
throw new plugins.smartserve.RouteNotFoundError(ctx.path, ctx.method);
|
||||
}
|
||||
|
||||
const smartsitemap = new plugins.smartsitemap.SmartSitemap();
|
||||
const urls = this.options.getSitemapUrls();
|
||||
const sitemapXml = await smartsitemap.createSitemapFromUrlInfoArray(urls);
|
||||
|
||||
return new Response(sitemapXml, {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/xml' },
|
||||
});
|
||||
}
|
||||
|
||||
@plugins.smartserve.Get('/sitemap-news')
|
||||
async getSitemapNews(ctx: plugins.smartserve.IRequestContext): Promise<Response> {
|
||||
if (!this.options.sitemap || !this.options.domain || !this.options.articleGetterFunction) {
|
||||
throw new plugins.smartserve.RouteNotFoundError(ctx.path, ctx.method);
|
||||
}
|
||||
|
||||
const smartsitemap = new plugins.smartsitemap.SmartSitemap();
|
||||
const articles = await this.options.articleGetterFunction();
|
||||
const sitemapNewsXml = await smartsitemap.createSitemapNewsFromArticleArray(articles);
|
||||
|
||||
return new Response(sitemapNewsXml, {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/xml' },
|
||||
});
|
||||
}
|
||||
|
||||
@plugins.smartserve.Get('/feed')
|
||||
async getFeed(ctx: plugins.smartserve.IRequestContext): Promise<Response> {
|
||||
if (!this.options.feed || !this.options.feedMetadata) {
|
||||
throw new plugins.smartserve.RouteNotFoundError(ctx.path, ctx.method);
|
||||
}
|
||||
|
||||
const smartfeed = new plugins.smartfeed.Smartfeed();
|
||||
const articles = this.options.articleGetterFunction
|
||||
? await this.options.articleGetterFunction()
|
||||
: [];
|
||||
|
||||
const feedXml = await smartfeed.createFeedFromArticleArray(
|
||||
this.options.feedMetadata,
|
||||
articles
|
||||
);
|
||||
|
||||
return new Response(feedXml, {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/atom+xml' },
|
||||
});
|
||||
}
|
||||
|
||||
@plugins.smartserve.Get('/appversion')
|
||||
async getAppVersion(ctx: plugins.smartserve.IRequestContext): Promise<Response> {
|
||||
if (!this.options.appVersion) {
|
||||
throw new plugins.smartserve.RouteNotFoundError(ctx.path, ctx.method);
|
||||
}
|
||||
|
||||
return new Response(this.options.appVersion, {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,34 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
/**
|
||||
* TypedRequest handler for type-safe RPC endpoint
|
||||
* TypedRequest controller for type-safe RPC endpoint
|
||||
*/
|
||||
export class TypedRequestHandler {
|
||||
@plugins.smartserve.Route('/typedrequest')
|
||||
export class TypedRequestController {
|
||||
private typedRouter: plugins.typedrequest.TypedRouter;
|
||||
|
||||
constructor(typedRouter: plugins.typedrequest.TypedRouter) {
|
||||
this.typedRouter = typedRouter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a request - returns Response if handled, null otherwise
|
||||
*/
|
||||
public async handle(request: Request): Promise<Response | null> {
|
||||
const url = new URL(request.url);
|
||||
const path = url.pathname;
|
||||
@plugins.smartserve.Post('/')
|
||||
async handleTypedRequest(ctx: plugins.smartserve.IRequestContext): Promise<Response> {
|
||||
try {
|
||||
const response = await this.typedRouter.routeAndAddResponse(ctx.body as plugins.typedrequestInterfaces.ITypedRequest);
|
||||
|
||||
if (path === '/typedrequest' && request.method === 'POST') {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const response = await this.typedRouter.routeAndAddResponse(body);
|
||||
|
||||
return new Response(plugins.smartjson.stringify(response), {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({ error: 'Invalid request' }), {
|
||||
status: 400,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
return new Response(plugins.smartjson.stringify(response), {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({ error: 'Invalid request' }), {
|
||||
status: 400,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './controller.devtools.js';
|
||||
export * from './controller.typedrequest.js';
|
||||
export * from './controller.builtin.js';
|
||||
|
||||
@@ -14,10 +14,10 @@ export class TypedserverInfoscreen extends LitElement {
|
||||
//INSTANCE
|
||||
|
||||
@property()
|
||||
private text = 'Hello';
|
||||
accessor text = 'Hello';
|
||||
|
||||
@property()
|
||||
private success = false;
|
||||
accessor success = false;
|
||||
|
||||
public static styles = [
|
||||
css`
|
||||
|
||||
Reference in New Issue
Block a user