From 7cdcd960bea8f4fe36a89e8ed741d8e1bd5bb2af Mon Sep 17 00:00:00 2001 From: reluc Date: Tue, 1 Aug 2023 16:40:23 +0200 Subject: [PATCH 1/2] chore(binding-http): use ts strict checking --- packages/binding-http/src/credential.ts | 31 ++--- .../binding-http/src/http-client-factory.ts | 4 +- packages/binding-http/src/http-client-impl.ts | 19 +-- packages/binding-http/src/http-server.ts | 46 +++++--- .../binding-http/src/https-client-factory.ts | 4 +- packages/binding-http/src/oauth-manager.ts | 2 +- .../src/oauth-token-validation.ts | 7 +- packages/binding-http/src/routes/action.ts | 8 +- packages/binding-http/src/routes/event.ts | 10 +- .../binding-http/src/routes/properties.ts | 11 +- .../src/routes/property-observe.ts | 12 +- packages/binding-http/src/routes/property.ts | 14 ++- .../src/routes/thing-description.ts | 14 ++- .../src/subscription-protocols.ts | 18 +-- packages/binding-http/test/credential-test.ts | 18 +-- .../test/http-client-basic-test.ts | 2 +- .../test/http-client-oauth-tests.ts | 2 +- .../binding-http/test/http-client-test.ts | 17 +-- .../test/http-server-oauth-tests.ts | 32 +++--- .../binding-http/test/http-server-test.ts | 108 +++++++++--------- .../test/oauth-token-validation-tests.ts | 2 +- packages/binding-http/tsconfig.json | 3 +- packages/core/src/helpers.ts | 14 +-- 23 files changed, 214 insertions(+), 184 deletions(-) diff --git a/packages/binding-http/src/credential.ts b/packages/binding-http/src/credential.ts index f3679b04c..02b421abf 100644 --- a/packages/binding-http/src/credential.ts +++ b/packages/binding-http/src/credential.ts @@ -31,7 +31,7 @@ export interface BasicCredentialConfiguration { export class BasicCredential extends Credential { private readonly username: string; private readonly password: string; - private readonly options: BasicSecurityScheme; + private readonly options: BasicSecurityScheme | undefined; /** * */ @@ -114,7 +114,7 @@ export class BasicKeyCredential extends Credential { export class OAuthCredential extends Credential { private token: Token | Promise; - private readonly refresh: () => Promise; + private readonly refresh: (() => Promise) | undefined; /** * @@ -167,9 +167,9 @@ export class TuyaCustomBearer extends Credential { protected key: string; protected secret: string; protected baseUri: string; - protected token: string; - protected refreshToken: string; - protected expireTime: Date; + protected token: string | undefined; + protected refreshToken: string | undefined; + protected expireTime: Date | undefined; constructor(credentials: TuyaCustomBearerCredentialConfiguration, scheme: TuyaCustomBearerSecurityScheme) { super(); @@ -187,11 +187,11 @@ export class TuyaCustomBearer extends Credential { const body = request.body ? request.body.read().toString() : ""; const headers = this.getHeaders(true, request.headers.raw(), body, url, request.method); Object.assign(headers, request.headers.raw()); - return new Request(url, { method: request.method, body: body !== "" ? body : null, headers: headers }); + return new Request(url, { method: request.method, body: body !== "" ? body : undefined, headers: headers }); } protected async requestAndRefreshToken(refresh: boolean): Promise { - const headers = this.getHeaders(false, {}, "", null, null); + const headers = this.getHeaders(false, {}, ""); const request = { headers: headers, method: "GET", @@ -210,11 +210,11 @@ export class TuyaCustomBearer extends Credential { } } - private getHeaders(NormalRequest: boolean, headers: unknown, body: string, url: string, method: string) { + private getHeaders(NormalRequest: boolean, headers: unknown, body: string, url?: string, method?: string) { const requestTime = Date.now().toString(); const replaceUri = this.baseUri.replace("/v1.0", ""); - const _url = url ? url.replace(`${replaceUri}`, "") : null; - const sign = this.requestSign(NormalRequest, requestTime, body, headers, _url, method); + const _url = url ? url.replace(`${replaceUri}`, "") : undefined; + const sign = this.requestSign(NormalRequest, requestTime, body, _url, method); return { t: requestTime, client_id: this.key, @@ -224,21 +224,14 @@ export class TuyaCustomBearer extends Credential { }; } - private requestSign( - NormalRequest: boolean, - requestTime: string, - body: string, - headers: unknown, - path: string, - method: string - ): string { + private requestSign(NormalRequest: boolean, requestTime: string, body: string, path = "", method?: string): string { const bodyHash = crypto.createHash("sha256").update(body).digest("hex"); let signUrl = "/v1.0/token?grant_type=1"; const headerString = ""; let useToken = ""; const _method = method || "GET"; if (NormalRequest) { - useToken = this.token; + useToken = this.token ?? ""; const pathQuery = queryString.parse(path.split("?")[1]); let query: Record = {}; query = Object.assign(query, pathQuery); diff --git a/packages/binding-http/src/http-client-factory.ts b/packages/binding-http/src/http-client-factory.ts index 256ec055d..537daaeab 100644 --- a/packages/binding-http/src/http-client-factory.ts +++ b/packages/binding-http/src/http-client-factory.ts @@ -26,10 +26,10 @@ const { debug, warn } = createLoggers("binding-http", "http-client-factory"); export default class HttpClientFactory implements ProtocolClientFactory { public readonly scheme: string = "http"; - private config: HttpConfig = null; + private config: HttpConfig | null = null; private oAuthManager: OAuthManager = new OAuthManager(); - constructor(config: HttpConfig = null) { + constructor(config: HttpConfig | null = null) { this.config = config; } diff --git a/packages/binding-http/src/http-client-impl.ts b/packages/binding-http/src/http-client-impl.ts index 66492ba27..8a38e4d83 100644 --- a/packages/binding-http/src/http-client-impl.ts +++ b/packages/binding-http/src/http-client-impl.ts @@ -25,7 +25,7 @@ import { Subscription } from "rxjs/Subscription"; import * as TD from "@node-wot/td-tools"; // for Security definition -import { ProtocolClient, Content, ProtocolHelpers, createLoggers } from "@node-wot/core"; +import { ProtocolClient, Content, ProtocolHelpers, createLoggers, ContentSerdes } from "@node-wot/core"; import { HttpForm, HttpHeader, HttpConfig, HTTPMethodName, TuyaCustomBearerSecurityScheme } from "./http"; import fetch, { Request, RequestInit, Response } from "node-fetch"; import { Buffer } from "buffer"; @@ -50,15 +50,15 @@ const { debug, warn, error } = createLoggers("binding-http", "http-client-impl") export default class HttpClient implements ProtocolClient { private readonly agent: http.Agent; private readonly provider: "https" | "http"; - private proxyRequest: Request = null; + private proxyRequest: Request | null = null; private allowSelfSigned = false; private oauth: OAuthManager; - private credential: Credential = null; + private credential: Credential | null = null; private activeSubscriptions = new Map(); - constructor(config: HttpConfig = null, secure = false, oauthManager: OAuthManager = new OAuthManager()) { + constructor(config: HttpConfig | null = null, secure = false, oauthManager: OAuthManager = new OAuthManager()) { // config proxy by client side (not from TD) if (config !== null && config.proxy && config.proxy.href) { this.proxyRequest = new Request(HttpClient.fixLocalhostName(config.proxy.href)); @@ -125,7 +125,7 @@ export default class HttpClient implements ProtocolClient { // in browsers node-fetch uses the native fetch, which returns a ReadableStream // not complaint with node. Therefore we have to force the conversion here. const body = ProtocolHelpers.toNodeStream(result.body as Readable); - return new Content(result.headers.get("content-type"), body); + return new Content(result.headers.get("content-type") ?? ContentSerdes.DEFAULT, body); } public async writeResource(form: HttpForm, content: Content): Promise { @@ -162,6 +162,9 @@ export default class HttpClient implements ProtocolClient { } else if (form.subprotocol === "sse") { // server sent events internalSubscription = new SSESubscription(form); + } else { + reject(new Error(`HttpClient does not support subprotocol ${form.subprotocol}`)); + return; } internalSubscription @@ -202,7 +205,7 @@ export default class HttpClient implements ProtocolClient { // in browsers node-fetch uses the native fetch, which returns a ReadableStream // not complaint with node. Therefore we have to force the conversion here. const body = ProtocolHelpers.toNodeStream(result.body as Readable); - return new Content(result.headers.get("content-type"), body); + return new Content(result.headers.get("content-type") ?? ContentSerdes.DEFAULT, body); } public async unlinkResource(form: HttpForm): Promise { @@ -210,7 +213,7 @@ export default class HttpClient implements ProtocolClient { const internalSub = this.activeSubscriptions.get(form.href); if (internalSub) { - this.activeSubscriptions.get(form.href).close(); + internalSub.close(); } else { warn(`HttpClient cannot unlink ${form.href} no subscription found`); } @@ -399,7 +402,7 @@ export default class HttpClient implements ProtocolClient { } } - private static isOAuthTokenExpired(result: Response, credential: Credential) { + private static isOAuthTokenExpired(result: Response, credential: Credential | null) { return result.status === 401 && credential instanceof OAuthCredential; } diff --git a/packages/binding-http/src/http-server.ts b/packages/binding-http/src/http-server.ts index 96009df3c..b19e95baf 100644 --- a/packages/binding-http/src/http-server.ts +++ b/packages/binding-http/src/http-server.ts @@ -63,16 +63,16 @@ export default class HttpServer implements ProtocolServer { // private readonly OPTIONS_BODY_VARIABLES ='body'; private readonly port: number = 8080; - private readonly address: string = undefined; - private readonly baseUri: string = undefined; - private readonly urlRewrite: Record = undefined; + private readonly address?: string = undefined; + private readonly baseUri?: string = undefined; + private readonly urlRewrite?: Record = undefined; private readonly httpSecurityScheme: string = "NoSec"; // HTTP header compatible string private readonly validOAuthClients: RegExp = /.*/g; - private readonly server: http.Server | https.Server = null; - private readonly middleware: MiddlewareRequestHandler = null; + private readonly server: http.Server | https.Server; + private readonly middleware: MiddlewareRequestHandler | null = null; private readonly things: Map = new Map(); - private servient: Servient = null; - private oAuthValidator: Validator; + private servient: Servient | null = null; + private oAuthValidator?: Validator = undefined; private router: Router.Instance; constructor(config: HttpConfig = {}) { @@ -88,11 +88,11 @@ export default class HttpServer implements ProtocolServer { .map((envVar) => { return { key: envVar, value: process.env[envVar] }; }) - .find((envObj) => envObj.value != null); + .find((envObj) => envObj.value != null && envObj.value !== undefined) as { key: string; value: string }; if (environmentObj) { info(`HttpServer Port Overridden to ${environmentObj.value} by Environment Variable ${environmentObj.key}`); - this.port = +environmentObj.value; + this.port = parseInt(environmentObj.value); } if (config.address !== undefined) { @@ -115,7 +115,7 @@ export default class HttpServer implements ProtocolServer { const pathname = req.url; if (config.urlRewrite) { const entryUrl = pathname; - const internalUrl = config.urlRewrite[entryUrl]; + const internalUrl = config.urlRewrite[entryUrl ?? "/"]; if (internalUrl) { req.url = internalUrl; router.lookup(req, res, this); @@ -301,7 +301,7 @@ export default class HttpServer implements ProtocolServer { } } - public async expose(thing: ExposedThing, tdTemplate?: WoT.ExposedThingInit): Promise { + public async expose(thing: ExposedThing, tdTemplate: WoT.ExposedThingInit = {}): Promise { let urlPath = slugify(thing.title, { lower: true }); if (this.things.has(urlPath)) { @@ -336,7 +336,7 @@ export default class HttpServer implements ProtocolServer { public destroy(thingId: string): Promise { debug(`HttpServer on port ${this.getPort()} destroying thingId '${thingId}'`); return new Promise((resolve, reject) => { - let removedThing: ExposedThing; + let removedThing: ExposedThing | undefined; for (const name of Array.from(this.things.keys())) { const expThing = this.things.get(name); if (expThing?.id === thingId) { @@ -413,7 +413,7 @@ export default class HttpServer implements ProtocolServer { const form = new TD.Form(href, type); ProtocolHelpers.updatePropertyFormWithTemplate( form, - (tdTemplate?.properties[propertyName] ?? {}) as PropertyElement + (tdTemplate.properties?.[propertyName] ?? {}) as PropertyElement ); if (thing.properties[propertyName].readOnly) { form.op = ["readproperty"]; @@ -466,7 +466,7 @@ export default class HttpServer implements ProtocolServer { const form = new TD.Form(href, type); ProtocolHelpers.updateActionFormWithTemplate( form, - (tdTemplate?.actions[actionName] ?? {}) as ActionElement + (tdTemplate.actions?.[actionName] ?? {}) as ActionElement ); form.op = ["invokeaction"]; const hform: HttpForm = form; @@ -488,7 +488,7 @@ export default class HttpServer implements ProtocolServer { const form = new TD.Form(href, type); ProtocolHelpers.updateEventFormWithTemplate( form, - (tdTemplate?.events[eventName] ?? {}) as EventElement + (tdTemplate.events?.[eventName] ?? {}) as EventElement ); form.subprotocol = "longpoll"; form.op = ["subscribeevent", "unsubscribeevent"]; @@ -502,6 +502,10 @@ export default class HttpServer implements ProtocolServer { public async checkCredentials(thing: ExposedThing, req: http.IncomingMessage): Promise { debug(`HttpServer on port ${this.getPort()} checking credentials for '${thing.id}'`); + if (this.servient === null) { + throw new Error("Servient not set"); + } + const creds = this.servient.getCredentials(thing.id); switch (this.httpSecurityScheme) { @@ -526,13 +530,17 @@ export default class HttpServer implements ProtocolServer { const scopes = Helpers.toStringArray(oAuthScheme.scopes); // validate call requires array of strings while oAuthScheme.scopes can be string or array of strings let valid = false; + if (!this.oAuthValidator) { + throw new Error("OAuth validator not set. Cannot validate request."); + } + try { valid = await this.oAuthValidator.validate(req, scopes, this.validOAuthClients); - } catch (error) { + } catch (err) { // TODO: should we answer differently to the client if something went wrong? error("OAuth authorization error; sending unauthorized response error"); error("this was possibly caused by a misconfiguration of the server"); - error(`${error}`); + error(`${err}`); } return valid; @@ -583,7 +591,7 @@ export default class HttpServer implements ProtocolServer { } private async handleRequest(req: http.IncomingMessage, res: http.ServerResponse) { - const requestUri = new URL(req.url, `${this.scheme}://${req.headers.host}`); + const requestUri = new URL(req.url ?? "", `${this.scheme}://${req.headers.host}`); debug( `HttpServer on port ${this.getPort()} received '${req.method} ${ @@ -606,7 +614,7 @@ export default class HttpServer implements ProtocolServer { res.setHeader("Access-Control-Allow-Origin", "*"); } - const contentTypeHeader: string | string[] = req.headers["content-type"]; + const contentTypeHeader = req.headers["content-type"]; let contentType: string = Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : contentTypeHeader; if (req.method === "PUT" || req.method === "POST") { diff --git a/packages/binding-http/src/https-client-factory.ts b/packages/binding-http/src/https-client-factory.ts index fe710daf2..612f1364c 100644 --- a/packages/binding-http/src/https-client-factory.ts +++ b/packages/binding-http/src/https-client-factory.ts @@ -25,9 +25,9 @@ const { debug, warn } = createLoggers("binding-http", "https-client-factory"); export default class HttpsClientFactory implements ProtocolClientFactory { public readonly scheme: string = "https"; - private config: HttpConfig = null; + private config: HttpConfig | null = null; - constructor(config: HttpConfig = null) { + constructor(config: HttpConfig | null = null) { this.config = config; } diff --git a/packages/binding-http/src/oauth-manager.ts b/packages/binding-http/src/oauth-manager.ts index d9cfed025..ad287e45f 100644 --- a/packages/binding-http/src/oauth-manager.ts +++ b/packages/binding-http/src/oauth-manager.ts @@ -49,7 +49,7 @@ function createRequestFunction(rejectUnauthorized: boolean) { }); response.on("end", () => { resolve({ - status: response.statusCode, + status: response.statusCode ?? 500, // we are not expecting undefined status codes body: body.toString(), }); }); diff --git a/packages/binding-http/src/oauth-token-validation.ts b/packages/binding-http/src/oauth-token-validation.ts index 7e7ab69dd..2f3372569 100644 --- a/packages/binding-http/src/oauth-token-validation.ts +++ b/packages/binding-http/src/oauth-token-validation.ts @@ -57,7 +57,7 @@ export abstract class Validator { function extractTokenFromRequest(request: http.IncomingMessage) { const headerToken = request.headers.authorization; - const url = new URL(request.url, `http://${request.headers.host}`); + const url = new URL(request.url ?? "", `http://${request.headers.host}`); const queryToken = url.searchParams.get("access_token"); if (!headerToken && !queryToken) { @@ -68,7 +68,7 @@ function extractTokenFromRequest(request: http.IncomingMessage) { return queryToken; } - const matches = headerToken.match(/Bearer\s(\S+)/); + const matches = headerToken?.match(/Bearer\s(\S+)/); if (!matches) { throw new Error("Invalid request: malformed authorization header"); @@ -112,8 +112,7 @@ export class EndpointValidator extends Validator { throw new Error("Introspection endpoint error: " + response.statusText); } - let contentType = response.headers.get("content-type"); - contentType = response.headers.get("content-type")?.split(";")[0]; + const contentType = response.headers.get("content-type")?.split(";")[0]; if (contentType !== "application/json") { throw new Error( diff --git a/packages/binding-http/src/routes/action.ts b/packages/binding-http/src/routes/action.ts index 88e6943f6..5112e3b99 100644 --- a/packages/binding-http/src/routes/action.ts +++ b/packages/binding-http/src/routes/action.ts @@ -33,7 +33,7 @@ export default async function actionRoute( return; } - const contentTypeHeader: string | string[] = req.headers["content-type"]; + const contentTypeHeader = req.headers["content-type"]; let contentType: string = Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : contentTypeHeader; try { contentType = validOrDefaultRequestContentType(req, res, contentType); @@ -87,9 +87,11 @@ export default async function actionRoute( res.end(); } } catch (err) { - error(`HttpServer on port ${this.getPort()} got internal error on invoke '${req.url}': ${err.message}`); + const message = err instanceof Error ? err.message : JSON.stringify(err); + + error(`HttpServer on port ${this.getPort()} got internal error on invoke '${req.url}': ${message}`); res.writeHead(500); - res.end(err.message); + res.end(message); } } else { // may have been OPTIONS that failed the credentials check diff --git a/packages/binding-http/src/routes/event.ts b/packages/binding-http/src/routes/event.ts index 4f1c43d55..6996f143b 100644 --- a/packages/binding-http/src/routes/event.ts +++ b/packages/binding-http/src/routes/event.ts @@ -32,7 +32,7 @@ export default async function eventRoute( return; } - const contentTypeHeader: string | string[] = req.headers["content-type"]; + const contentTypeHeader = req.headers["content-type"]; const contentType: string = Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : contentTypeHeader; const event = thing.events[_params.event]; @@ -75,14 +75,14 @@ export default async function eventRoute( } value.body.pipe(res); } catch (err) { - if (err?.code === "ERR_HTTP_HEADERS_SENT") { + // Safe cast to NodeJS.ErrnoException we are checking if it is equal to ERR_HTTP_HEADERS_SENT + if ((err as NodeJS.ErrnoException)?.code === "ERR_HTTP_HEADERS_SENT") { thing.handleUnsubscribeEvent(_params.event, listener, options); return; } + const message = err instanceof Error ? err.message : JSON.stringify(err); warn( - `HttpServer on port ${this.getPort()} cannot process data for Event '${_params.event}: ${ - err.message - }'` + `HttpServer on port ${this.getPort()} cannot process data for Event '${_params.event}: ${message}'` ); res.writeHead(500); res.end("Invalid Event Data"); diff --git a/packages/binding-http/src/routes/properties.ts b/packages/binding-http/src/routes/properties.ts index dd9e5f6c0..a7b0d6a84 100644 --- a/packages/binding-http/src/routes/properties.ts +++ b/packages/binding-http/src/routes/properties.ts @@ -13,7 +13,7 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ import { IncomingMessage, ServerResponse } from "http"; -import { Content, ContentSerdes, PropertyContentMap, createLoggers } from "@node-wot/core"; +import { ContentSerdes, PropertyContentMap, createLoggers } from "@node-wot/core"; import { respondUnallowedMethod } from "./common"; import HttpServer from "../http-server"; @@ -55,7 +55,8 @@ export default async function propertiesRoute( res.writeHead(200); const recordResponse: Record = {}; for (const key of propMap.keys()) { - const content: Content = propMap.get(key); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- map key is always present as checked above + const content = propMap.get(key)!; const value = ContentSerdes.get().contentToValue( { type: ContentSerdes.DEFAULT, body: await content.toBuffer() }, {} @@ -64,9 +65,11 @@ export default async function propertiesRoute( } res.end(JSON.stringify(recordResponse)); } catch (err) { - error(`HttpServer on port ${this.getPort()} got internal error on invoke '${req.url}': ${err.message}`); + const message = err instanceof Error ? err.message : JSON.stringify(err); + + error(`HttpServer on port ${this.getPort()} got internal error on invoke '${req.url}': ${message}`); res.writeHead(500); - res.end(err.message); + res.end(message); } } else if (req.method === "HEAD") { res.writeHead(202); diff --git a/packages/binding-http/src/routes/property-observe.ts b/packages/binding-http/src/routes/property-observe.ts index f31e4eb62..e570738de 100644 --- a/packages/binding-http/src/routes/property-observe.ts +++ b/packages/binding-http/src/routes/property-observe.ts @@ -32,7 +32,7 @@ export default async function propertyObserveRoute( return; } - const contentTypeHeader: string | string[] = req.headers["content-type"]; + const contentTypeHeader = req.headers["content-type"]; const contentType: string = Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : contentTypeHeader; const property = thing.properties[_params.property]; @@ -69,14 +69,16 @@ export default async function propertyObserveRoute( // send property data value.body.pipe(res); } catch (err) { - if (err?.code === "ERR_HTTP_HEADERS_SENT") { + // Safe cast to NodeJS.ErrnoException we are checking if it is equal to ERR_HTTP_HEADERS_SENT + if ((err as NodeJS.ErrnoException)?.code === "ERR_HTTP_HEADERS_SENT") { thing.handleUnobserveProperty(_params.property, listener, options); return; } + const message = err instanceof Error ? err.message : JSON.stringify(err); warn( - `HttpServer on port ${this.getPort()} cannot process data for Property '${_params.property}: ${ - err.message - }'` + `HttpServer on port ${this.getPort()} cannot process data for Property '${ + _params.property + }: ${message}'` ); res.writeHead(500); res.end("Invalid Property Data"); diff --git a/packages/binding-http/src/routes/property.ts b/packages/binding-http/src/routes/property.ts index 71445a264..47cc72b7e 100644 --- a/packages/binding-http/src/routes/property.ts +++ b/packages/binding-http/src/routes/property.ts @@ -32,7 +32,7 @@ export default async function propertyRoute( return; } - const contentTypeHeader: string | string[] = req.headers["content-type"]; + const contentTypeHeader = req.headers["content-type"]; let contentType: string = Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : contentTypeHeader; try { contentType = validOrDefaultRequestContentType(req, res, contentType); @@ -84,9 +84,11 @@ export default async function propertyRoute( res.writeHead(200); content.body.pipe(res); } catch (err) { - error(`HttpServer on port ${this.getPort()} got internal error on read '${req.url}': ${err.message}`); + const message = err instanceof Error ? err.message : JSON.stringify(err); + + error(`HttpServer on port ${this.getPort()} got internal error on read '${req.url}': ${message}`); res.writeHead(500); - res.end(err.message); + res.end(message); } } else if (req.method === "PUT") { if (!property.readOnly) { @@ -96,9 +98,11 @@ export default async function propertyRoute( res.writeHead(204); res.end("Changed"); } catch (err) { - error(`HttpServer on port ${this.getPort()} got internal error on invoke '${req.url}': ${err.message}`); + const message = err instanceof Error ? err.message : JSON.stringify(err); + + error(`HttpServer on port ${this.getPort()} got internal error on invoke '${req.url}': ${message}`); res.writeHead(500); - res.end(err.message); + res.end(message); } } else { respondUnallowedMethod(req, res, "GET, PUT"); diff --git a/packages/binding-http/src/routes/thing-description.ts b/packages/binding-http/src/routes/thing-description.ts index c05e4b133..136787595 100644 --- a/packages/binding-http/src/routes/thing-description.ts +++ b/packages/binding-http/src/routes/thing-description.ts @@ -33,19 +33,21 @@ function resetMultiLangInteraction( delete interactions[interName].description; // use new language title - if (interactions[interName].titles) { - for (const titleLang in interactions[interName].titles) { + const titles = interactions[interName].titles; + if (titles) { + for (const titleLang in titles) { if (titleLang.startsWith(prefLang)) { - interactions[interName].title = interactions[interName].titles[titleLang]; + interactions[interName].title = titles[titleLang]; } } } // use new language description - if (interactions[interName].descriptions) { - for (const descLang in interactions[interName].descriptions) { + const descriptions = interactions[interName].descriptions; + if (descriptions) { + for (const descLang in descriptions) { if (descLang.startsWith(prefLang)) { - interactions[interName].description = interactions[interName].descriptions[descLang]; + interactions[interName].description = descriptions[descLang]; } } } diff --git a/packages/binding-http/src/subscription-protocols.ts b/packages/binding-http/src/subscription-protocols.ts index a60e17036..e0483282a 100644 --- a/packages/binding-http/src/subscription-protocols.ts +++ b/packages/binding-http/src/subscription-protocols.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { HttpClient, HttpForm } from "./http"; import EventSource from "eventsource"; -import { Content, ProtocolHelpers, createLoggers } from "@node-wot/core"; +import { Content, ContentSerdes, ProtocolHelpers, createLoggers } from "@node-wot/core"; import { Readable } from "stream"; const { debug } = createLoggers("binding-http", "subscription-protocols"); @@ -73,13 +73,14 @@ export class LongPollingSubscription implements InternalSubscription { // in browsers node-fetch uses the native fetch, which returns a ReadableStream // not complaint with node. Therefore we have to force the conversion here. const body = ProtocolHelpers.toNodeStream(result.body as Readable); - next(new Content(result.headers.get("content-type"), body)); + next(new Content(result.headers.get("content-type") ?? ContentSerdes.DEFAULT, body)); polling(false); } complete && complete(); } catch (e) { - error && error(e); + const err = e instanceof Error ? e : new Error(JSON.stringify(e)); + error && error(err); complete && complete(); reject(e); } @@ -96,7 +97,7 @@ export class LongPollingSubscription implements InternalSubscription { export class SSESubscription implements InternalSubscription { private form: HttpForm; - private eventSource: EventSource; + private eventSource: EventSource | undefined; private closed: boolean; /** * @@ -116,11 +117,14 @@ export class SSESubscription implements InternalSubscription { }; this.eventSource.onmessage = (event) => { debug(`HttpClient received ${JSON.stringify(event)} from ${this.form.href}`); - const output = new Content(this.form.contentType, Readable.from(JSON.stringify(event))); + const output = new Content( + this.form.contentType ?? ContentSerdes.DEFAULT, + Readable.from(JSON.stringify(event)) + ); next(output); }; this.eventSource.onerror = function (event) { - error(new Error(event.toString())); + error?.(new Error(event.toString())); complete && complete(); reject(event.toString()); }; @@ -128,6 +132,6 @@ export class SSESubscription implements InternalSubscription { } close(): void { - this.eventSource.close(); + this.eventSource?.close(); } } diff --git a/packages/binding-http/test/credential-test.ts b/packages/binding-http/test/credential-test.ts index 41bbe4cc2..29419a483 100644 --- a/packages/binding-http/test/credential-test.ts +++ b/packages/binding-http/test/credential-test.ts @@ -37,7 +37,7 @@ chai.use(chaiAsPromised); @suite("Credetials auth test suite") class CredentialTest { @test async "should sign in with basic"(): Promise { - const scheme: BasicSecurityScheme = { + const scheme: BasicSecurityScheme & { name: string } = { scheme: "basic", in: "header", name: "testHeader", @@ -53,13 +53,13 @@ class CredentialTest { const basic = new BasicCredential(config, scheme); const response = await basic.sign(request); - response.headers - .get(scheme.name) - .should.be.equal("Basic " + Buffer.from(config.username + ":" + config.password).toString("base64")); + chai.expect(response.headers?.get(scheme.name)).be.equal( + "Basic " + Buffer.from(config.username + ":" + config.password).toString("base64") + ); } @test async "should sign in with bearer"(): Promise { - const scheme: BearerSecurityScheme = { + const scheme: BearerSecurityScheme & { name: string } = { scheme: "bearer", in: "header", name: "testHeader", @@ -74,11 +74,11 @@ class CredentialTest { const bearer = new BearerCredential(config, scheme); const response = await bearer.sign(request); - response.headers.get(scheme.name).should.be.equal("Bearer " + config.token); + chai.expect(response.headers?.get(scheme.name)).be.equal("Bearer " + config.token); } @test async "should sign in with basic key"(): Promise { - const scheme: APIKeySecurityScheme = { + const scheme: APIKeySecurityScheme & { name: string } = { scheme: "apikey", in: "header", name: "testHeader", @@ -93,7 +93,7 @@ class CredentialTest { const basic = new BasicKeyCredential(config, scheme); const response = await basic.sign(request); - response.headers.get(scheme.name).should.be.equal(config.apiKey); + chai.expect(response.headers?.get(scheme.name)).be.equal(config.apiKey); } @test async "should sign in with TuyaCustomBearer"(): Promise { @@ -121,6 +121,6 @@ class CredentialTest { const response = await credentials.sign(request); timekeeper.reset(); - response.headers.get("sign").should.be.equal(sign); + chai.expect(response.headers.get("sign")).be.equal(sign); } } diff --git a/packages/binding-http/test/http-client-basic-test.ts b/packages/binding-http/test/http-client-basic-test.ts index 5e03f1fbd..7177f3316 100644 --- a/packages/binding-http/test/http-client-basic-test.ts +++ b/packages/binding-http/test/http-client-basic-test.ts @@ -52,7 +52,7 @@ function mockService(req: express.Request, res: express.Response) { } @suite("HTTP auth basic client implementation") class HttpClientBasicTest { - private client: HttpClient; + private client!: HttpClient; private static server: https.Server; diff --git a/packages/binding-http/test/http-client-oauth-tests.ts b/packages/binding-http/test/http-client-oauth-tests.ts index d2c0ab503..e0f34b13c 100644 --- a/packages/binding-http/test/http-client-oauth-tests.ts +++ b/packages/binding-http/test/http-client-oauth-tests.ts @@ -28,7 +28,7 @@ import bodyParser from "body-parser"; @suite("HTTP oauth client implementation") class HttpClientOAuthTest { - private client: HttpClient; + private client!: HttpClient; static model: InMemoryModel; static server: https.Server; diff --git a/packages/binding-http/test/http-client-test.ts b/packages/binding-http/test/http-client-test.ts index b7eb47627..8ca5bc718 100644 --- a/packages/binding-http/test/http-client-test.ts +++ b/packages/binding-http/test/http-client-test.ts @@ -62,10 +62,10 @@ const port4 = 30003; class TestHttpServer implements ProtocolServer { public readonly scheme: string = "test"; - private testVector: TestVector; + private testVector: TestVector | undefined; private readonly port: number = 60606; - private readonly address: string = undefined; + private readonly address: string | undefined = undefined; private readonly server: http.Server; constructor(port?: number, address?: string) { @@ -158,16 +158,19 @@ class TestHttpServer implements ProtocolServer { body.push(data); }); req.on("end", () => { + if (!this.testVector) { + chai.assert.fail("No test vector given"); + } let value; try { value = ContentSerdes.get().contentToValue( { type: ContentSerdes.DEFAULT, body: Buffer.concat(body) }, - this.testVector.schema + this.testVector.schema ?? { type: "string" } ); } catch (err) { throw new Error("Cannot deserialize client payload"); } - expect(value).to.equal(this.testVector.payload); + expect(value).to.equal(this.testVector?.payload); res.end(); }); } else { @@ -189,7 +192,7 @@ class HttpClientTest1 { HttpClientTest1.httpServer.stop(); } - private client: HttpClient; + private client!: HttpClient; before() { this.client = new HttpClient(); @@ -243,7 +246,7 @@ class HttpClientTest1 { new DefaultContent(Readable.from(inputVector3.payload)) ); - expect(resource.type).eql(null); + expect(resource.type).eql(ContentSerdes.DEFAULT); const body = await resource.toBuffer(); body.toString("ascii").should.eql(""); } @@ -259,7 +262,7 @@ class HttpClientTest1 { }; HttpClientTest1.httpServer.setTestVector(inputVector1); const resource = await this.client.readResource(inputVector1.form); - expect(resource.type).eql(null); + expect(resource.type).eql(ContentSerdes.DEFAULT); const body = await resource.toBuffer(); body.toString("ascii").should.eql(""); } diff --git a/packages/binding-http/test/http-server-oauth-tests.ts b/packages/binding-http/test/http-server-oauth-tests.ts index 59adb710f..d61a89bbc 100644 --- a/packages/binding-http/test/http-server-oauth-tests.ts +++ b/packages/binding-http/test/http-server-oauth-tests.ts @@ -13,7 +13,7 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ import { suite, test } from "@testdeck/mocha"; -import { should } from "chai"; +import { should, expect } from "chai"; import { HttpServer, OAuth2ServerConfig } from "../src/http"; import { IntrospectionEndpoint, EndpointValidator } from "../src/oauth-token-validation"; import Servient, { ExposedThing } from "@node-wot/core"; @@ -24,7 +24,7 @@ class MockServient extends Servient {} should(); @suite("OAuth server token validation tests") class OAuthServerTests { - private server: HttpServer; + private server!: HttpServer; async before() { // eslint-disable-next-line @typescript-eslint/no-empty-function console.debug = () => {}; @@ -47,7 +47,7 @@ class OAuthServerTests { await this.server.start(new MockServient()); - const testThing = new ExposedThing(null, { + const testThing = new ExposedThing(new Servient(), { title: "TestOAuth", id: "test", securityDefinitions: { @@ -80,19 +80,21 @@ class OAuthServerTests { @test async "should configure oauth"() { /* eslint-disable dot-notation */ this.server["httpSecurityScheme"].should.be.equal("OAuth"); - this.server["oAuthValidator"].should.be.instanceOf(EndpointValidator); + expect(this.server["oAuthValidator"]).to.be.instanceOf(EndpointValidator); /* eslint-enable dot-notation */ } @test async "should call oauth validation"() { let called = false; - // eslint-disable-next-line dot-notation - this.server["oAuthValidator"].validate = async (token, scopes, clients) => { + /* eslint-disable dot-notation */ + expect(this.server["oAuthValidator"]).to.not.be.equal(undefined, "OAuth validator not set"); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.server["oAuthValidator"]!.validate = async (token, scopes, clients) => { called = true; return true; }; - + /* eslint-enable dot-notation */ await fetch("http://localhost:8080/testoauth/properties/test"); called.should.eql(true); @@ -101,12 +103,14 @@ class OAuthServerTests { @test async "should send unauthorized if oauth validation fails"() { let called = false; - // eslint-disable-next-line dot-notation - this.server["oAuthValidator"].validate = async (token, scopes, clients) => { + /* eslint-disable dot-notation */ + expect(this.server["oAuthValidator"]).to.not.be.equal(undefined, "OAuth validator not set"); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.server["oAuthValidator"]!.validate = async (token, scopes, clients) => { called = true; return false; }; - + /* eslint-enable dot-notation */ const response = await fetch("http://localhost:8080/testoauth/properties/test"); called.should.eql(true); @@ -117,12 +121,14 @@ class OAuthServerTests { @test async "should send error if oauth validation throws"() { let called = false; - // eslint-disable-next-line dot-notation - this.server["oAuthValidator"].validate = async (token, scopes, clients) => { + /* eslint-disable dot-notation */ + expect(this.server["oAuthValidator"]).to.not.be.equal(undefined, "OAuth validator not set"); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.server["oAuthValidator"]!.validate = async (token, scopes, clients) => { called = true; return false; }; - + /* eslint-enable dot-notation */ const response = await fetch("http://localhost:8080/testoauth/properties/test"); called.should.eql(true); diff --git a/packages/binding-http/test/http-server-test.ts b/packages/binding-http/test/http-server-test.ts index 38ba34f87..afd275b0d 100644 --- a/packages/binding-http/test/http-server-test.ts +++ b/packages/binding-http/test/http-server-test.ts @@ -23,7 +23,7 @@ import * as chai from "chai"; import fetch from "node-fetch"; import HttpServer from "../src/http-server"; -import { Content, createLoggers, ExposedThing, Helpers } from "@node-wot/core"; +import Servient, { Content, createLoggers, ExposedThing, Helpers } from "@node-wot/core"; import { DataSchemaValue, InteractionInput, InteractionOptions } from "wot-typescript-definitions"; import chaiAsPromised from "chai-as-promised"; import { Readable } from "stream"; @@ -43,7 +43,7 @@ class HttpServerTest { @test async "should start and stop a server"() { const httpServer = new HttpServer({ port }); - await httpServer.start(null); + await httpServer.start(new Servient()); expect(httpServer.getPort()).to.eq(port); // from test await httpServer.stop(); @@ -52,7 +52,7 @@ class HttpServerTest { @test async "should use middleware if provided"() { const middleware: MiddlewareRequestHandler = async (req, res, next) => { - if (req.url.endsWith("testMiddleware")) { + if (req.url?.endsWith("testMiddleware")) { res.statusCode = 401; res.end("Unauthorized"); } else { @@ -65,9 +65,9 @@ class HttpServerTest { middleware, }); - await httpServer.start(null); + await httpServer.start(new Servient()); - const testThing = new ExposedThing(null, { + const testThing = new ExposedThing(new Servient(), { title: "Test", properties: { testMiddleware: { @@ -103,9 +103,9 @@ class HttpServerTest { @test async "should be able to destroy a thing"() { const httpServer = new HttpServer({ port: 0 }); - await httpServer.start(null); + await httpServer.start(new Servient()); - let testThing = new ExposedThing(null); + let testThing = new ExposedThing(new Servient()); testThing = Helpers.extend( { title: "Test", @@ -135,9 +135,9 @@ class HttpServerTest { @test async "should change resource from 'off' to 'on' and try to invoke"() { const httpServer = new HttpServer({ port: 0 }); - await httpServer.start(null); + await httpServer.start(new Servient()); - const testThing = new ExposedThing(null, { + const testThing = new ExposedThing(new Servient(), { title: "Test", properties: { test: { @@ -222,9 +222,9 @@ class HttpServerTest { @test async "should return 204 when action has not output"() { const httpServer = new HttpServer({ port: 0 }); - await httpServer.start(null); + await httpServer.start(new Servient()); - const testThing = new ExposedThing(null, { + const testThing = new ExposedThing(new Servient(), { title: "Test", actions: { noOutput: { @@ -252,9 +252,9 @@ class HttpServerTest { @test async "should check uriVariables consistency"() { const httpServer = new HttpServer({ port: 0 }); - await httpServer.start(null); + await httpServer.start(new Servient()); - const testThing = new ExposedThing(null, { + const testThing = new ExposedThing(new Servient(), { title: "Test", properties: { test: { @@ -277,13 +277,13 @@ class HttpServerTest { }); let test: DataSchemaValue; testThing.setPropertyReadHandler("test", (options) => { - expect(options.uriVariables).to.deep.equal({ id: "testId" }); + expect(options?.uriVariables).to.deep.equal({ id: "testId" }); return new Promise((resolve, reject) => { resolve(test); }); }); testThing.setPropertyWriteHandler("test", async (value, options) => { - expect(options.uriVariables).to.deep.equal({ id: "testId" }); + expect(options?.uriVariables).to.deep.equal({ id: "testId" }); test = await value.value(); }); // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -316,9 +316,9 @@ class HttpServerTest { @test async "should serialize objects for actions and properties"() { const httpServer = new HttpServer({ port: 0 }); - await httpServer.start(null); + await httpServer.start(new Servient()); - const testThing = new ExposedThing(null, { + const testThing = new ExposedThing(new Servient(), { title: "Test", properties: { test: { @@ -331,7 +331,7 @@ class HttpServerTest { }, }, }); - let test = {}; + let test: DataSchemaValue = {}; testThing.setPropertyReadHandler("test", (_) => Promise.resolve(test)); testThing.setPropertyWriteHandler("test", async (value) => { test = await value.value(); @@ -385,13 +385,13 @@ class HttpServerTest { @test async "should cause EADDRINUSE error when already running"() { const httpServer1 = new HttpServer({ port: 0 }); - await httpServer1.start(null); + await httpServer1.start(new Servient()); expect(httpServer1.getPort()).to.be.above(0); const httpServer2 = new HttpServer({ port: httpServer1.getPort() }); try { - await httpServer2.start(null); // should fail + await httpServer2.start(new Servient()); // should fail } catch (err) { error(`HttpServer failed correctly on EADDRINUSE. ${err}`); assert(true); @@ -413,7 +413,7 @@ class HttpServerTest { @test async "should start and stop a server with no security"() { const httpServer = new HttpServer({ port, security: { scheme: "nosec" } }); - await httpServer.start(null); + await httpServer.start(new Servient()); expect(httpServer.getPort()).to.eq(port); // port test expect(httpServer.getHttpSecurityScheme()).to.eq("NoSec"); // HTTP security scheme test (nosec -> NoSec) await httpServer.stop(); @@ -429,8 +429,8 @@ class HttpServerTest { scheme: "bearer", }, }); - await httpServer.start(null); - const testThing = new ExposedThing(null); + await httpServer.start(new Servient()); + const testThing = new ExposedThing(new Servient()); testThing.title = "Test"; testThing.securityDefinitions = { bearer: { @@ -453,10 +453,10 @@ class HttpServerTest { scheme: "bearer", }, }); - await httpServer.start(null); + await httpServer.start(new Servient()); try { - const testThing = new ExposedThing(null); + const testThing = new ExposedThing(new Servient()); testThing.title = "Test"; testThing.securityDefinitions = { oauth2: { @@ -472,14 +472,14 @@ class HttpServerTest { @test async "config.port is overridden by WOT_PORT or PORT"() { // Works when none set let httpServer = new HttpServer({ port }); - await httpServer.start(null); + await httpServer.start(new Servient()); expect(httpServer.getPort()).to.eq(port); // WOT PORT from test await httpServer.stop(); // Check PORT process.env.PORT = "2222"; httpServer = new HttpServer({ port }); - await httpServer.start(null); + await httpServer.start(new Servient()); expect(httpServer.getPort()).to.eq(2222); // from PORT await httpServer.stop(); @@ -487,7 +487,7 @@ class HttpServerTest { process.env.PORT = undefined; process.env.WOT_PORT = "3333"; httpServer = new HttpServer({ port }); - await httpServer.start(null); + await httpServer.start(new Servient()); expect(httpServer.getPort()).to.eq(3333); // WOT PORT from test await httpServer.stop(); @@ -495,7 +495,7 @@ class HttpServerTest { process.env.PORT = "2600"; process.env.WOT_PORT = "1337"; httpServer = new HttpServer({ port }); - await httpServer.start(null); + await httpServer.start(new Servient()); expect(httpServer.getPort()).to.eq(1337); // WOT PORT from test await httpServer.stop(); delete process.env.PORT; @@ -511,9 +511,9 @@ class HttpServerTest { port: 8080, }); - await httpServer.start(null); + await httpServer.start(new Servient()); - const testThing = new ExposedThing(null, { + const testThing = new ExposedThing(new Servient(), { title: "Smart Coffee Machine", properties: { maintenanceNeeded: { @@ -548,9 +548,9 @@ class HttpServerTest { @test async "should take in account global uriVariables"() { const httpServer = new HttpServer({ port: 0 }); - await httpServer.start(null); + await httpServer.start(new Servient()); - const testThing = new ExposedThing(null, { + const testThing = new ExposedThing(new Servient(), { title: "Test", uriVariables: { globalVarTest: { @@ -576,7 +576,7 @@ class HttpServerTest { }); let test: DataSchemaValue; testThing.setPropertyReadHandler("test", (options) => { - expect(options.uriVariables).to.deep.equal({ id: "testId", globalVarTest: "test1" }); + expect(options?.uriVariables).to.deep.equal({ id: "testId", globalVarTest: "test1" }); return new Promise((resolve, reject) => { resolve(test); }); @@ -600,9 +600,9 @@ class HttpServerTest { @test async "should allow url rewrite"() { const httpServer = new HttpServer({ port: 0, urlRewrite: { "/myroot/foo": "/test/properties/test" } }); - await httpServer.start(null); + await httpServer.start(new Servient()); - const testThing = new ExposedThing(null, { + const testThing = new ExposedThing(new Servient(), { title: "Test", properties: { test: { @@ -610,7 +610,7 @@ class HttpServerTest { }, }, }); - let test = {}; + let test: DataSchemaValue = {}; testThing.setPropertyReadHandler("test", (_) => Promise.resolve(test)); testThing.setPropertyWriteHandler("test", async (value) => { test = await value.value(); @@ -639,7 +639,7 @@ class HttpServerTest { @test async "should report allproperties excluding non-JSON properties"() { const httpServer = new HttpServer({ port: 0 }); - await httpServer.start(null); + await httpServer.start(new Servient()); const tdTemplate: WoT.ExposedThingInit = { title: "TestA", @@ -668,7 +668,7 @@ class HttpServerTest { }, }, }; - const testThing = new ExposedThing(null, tdTemplate); + const testThing = new ExposedThing(new Servient(), tdTemplate); const image = "FOO"; const integer = 123; @@ -733,7 +733,7 @@ class HttpServerTest { @test async "should support setting SVG contentType"() { const httpServer = new HttpServer({ port: 0 }); - await httpServer.start(null); + await httpServer.start(new Servient()); const tdTemplate = { title: "Test", @@ -747,7 +747,7 @@ class HttpServerTest { }, }, }; - const testThing = new ExposedThing(null, tdTemplate); + const testThing = new ExposedThing(new Servient(), tdTemplate); const image = "FOO"; testThing.setPropertyReadHandler("image", (_) => Promise.resolve(image)); @@ -771,7 +771,7 @@ class HttpServerTest { @test async "should support setting PNG contentType"() { const httpServer = new HttpServer({ port: 0 }); - await httpServer.start(null); + await httpServer.start(new Servient()); const tdTemplate = { title: "Test", @@ -785,7 +785,7 @@ class HttpServerTest { }, }, }; - const testThing = new ExposedThing(null, tdTemplate); + const testThing = new ExposedThing(new Servient(), tdTemplate); const image = "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; @@ -807,9 +807,9 @@ class HttpServerTest { @test async "should support TD content negotiation"() { const httpServer = new HttpServer({ port: 0 }); - await httpServer.start(null); + await httpServer.start(new Servient()); - const testThing = new ExposedThing(null, { + const testThing = new ExposedThing(new Servient(), { title: "Test", }); @@ -819,7 +819,7 @@ class HttpServerTest { const testCases = [ { - inputHeaders: {}, + inputHeaders: undefined, expected: "application/td+json", expectedResponseCode: 200, }, @@ -881,9 +881,9 @@ class HttpServerTest { @test async "should not support unknown Content-Types during TD content negotiation"() { const httpServer = new HttpServer({ port: 0 }); - await httpServer.start(null); + await httpServer.start(new Servient()); - const testThing = new ExposedThing(null, { + const testThing = new ExposedThing(new Servient(), { title: "Test", }); @@ -905,7 +905,7 @@ class HttpServerTest { @test async "TD should have form with readallproperties"() { const httpServer = new HttpServer({ port: 0 }); - await httpServer.start(null); + await httpServer.start(new Servient()); const tdTemplate: WoT.ExposedThingInit = { title: "Test", @@ -916,7 +916,7 @@ class HttpServerTest { }, }, }; - const testThing = new ExposedThing(null, tdTemplate); + const testThing = new ExposedThing(new Servient(), tdTemplate); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -939,7 +939,7 @@ class HttpServerTest { @test async "TD should have form with writeallproperties"() { const httpServer = new HttpServer({ port: 0 }); - await httpServer.start(null); + await httpServer.start(new Servient()); const tdTemplate: WoT.ExposedThingInit = { title: "Test", @@ -950,7 +950,7 @@ class HttpServerTest { }, }, }; - const testThing = new ExposedThing(null, tdTemplate); + const testThing = new ExposedThing(new Servient(), tdTemplate); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -973,7 +973,7 @@ class HttpServerTest { @test async "TD should have form with readallproperties and writeallproperties"() { const httpServer = new HttpServer({ port: 0 }); - await httpServer.start(null); + await httpServer.start(new Servient()); const tdTemplate: WoT.ExposedThingInit = { title: "Test", @@ -988,7 +988,7 @@ class HttpServerTest { }, }, }; - const testThing = new ExposedThing(null, tdTemplate); + const testThing = new ExposedThing(new Servient(), tdTemplate); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/packages/binding-http/test/oauth-token-validation-tests.ts b/packages/binding-http/test/oauth-token-validation-tests.ts index 9f0dd5e57..3f426141d 100644 --- a/packages/binding-http/test/oauth-token-validation-tests.ts +++ b/packages/binding-http/test/oauth-token-validation-tests.ts @@ -41,7 +41,7 @@ describe("OAuth2.0 Validator tests", () => { }); @suite class IntrospectProtocolTests { - private validator: Validator; + private validator!: Validator; static server: http.Server; static before() { // eslint-disable-next-line @typescript-eslint/no-empty-function diff --git a/packages/binding-http/tsconfig.json b/packages/binding-http/tsconfig.json index e9fa7c062..df9cd1d90 100644 --- a/packages/binding-http/tsconfig.json +++ b/packages/binding-http/tsconfig.json @@ -2,7 +2,8 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "dist", - "rootDir": "src" + "rootDir": "src", + "strict": true }, "include": ["src/**/*"], "references": [{ "path": "../td-tools" }, { "path": "../core" }] diff --git a/packages/core/src/helpers.ts b/packages/core/src/helpers.ts index 621615b17..0dbb667a2 100644 --- a/packages/core/src/helpers.ts +++ b/packages/core/src/helpers.ts @@ -117,12 +117,12 @@ export default class Helpers implements Resolver { } } - public static toUriLiteral(address: string): string { + public static toUriLiteral(address?: string): string { // Due to crash logged with: // TypeError: Cannot read property 'indexOf' of undefined at Function.Helpers.toUriLiteral if (!address) { error(`AddressHelper received invalid address '${address}'`); - return "{invalid address}"; + return "{invalid address - undefined}"; } if (address.indexOf(":") !== -1) { @@ -140,7 +140,7 @@ export default class Helpers implements Resolver { } } - public static toStringArray(input: string[] | string): string[] { + public static toStringArray(input: string[] | string | undefined): string[] { if (input) { if (typeof input === "string") { return [input]; @@ -345,12 +345,12 @@ export default class Helpers implements Resolver { * @returns merged and validated uriVariables */ static parseUrlParameters( - url: string, - globalUriVariables: { [key: string]: TD.DataSchema }, - uriVariables: { [k: string]: DataSchema } + url: string | undefined, + globalUriVariables: { [key: string]: TD.DataSchema } = {}, + uriVariables: { [k: string]: DataSchema } = {} ): Record { const params: Record = {}; - if (url == null || (!uriVariables && !globalUriVariables)) { + if ((url === undefined && url == null) || (!uriVariables && !globalUriVariables)) { return params; } From 5469a3ada4a6da4a368388bc02a4aa94276caf38 Mon Sep 17 00:00:00 2001 From: reluc Date: Wed, 2 Aug 2023 16:16:25 +0200 Subject: [PATCH 2/2] fix(binding-http/credential): use `?` instead of `| undefined` --- packages/binding-http/src/credential.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/binding-http/src/credential.ts b/packages/binding-http/src/credential.ts index 02b421abf..59c020bea 100644 --- a/packages/binding-http/src/credential.ts +++ b/packages/binding-http/src/credential.ts @@ -31,7 +31,7 @@ export interface BasicCredentialConfiguration { export class BasicCredential extends Credential { private readonly username: string; private readonly password: string; - private readonly options: BasicSecurityScheme | undefined; + private readonly options?: BasicSecurityScheme; /** * */ @@ -114,7 +114,7 @@ export class BasicKeyCredential extends Credential { export class OAuthCredential extends Credential { private token: Token | Promise; - private readonly refresh: (() => Promise) | undefined; + private readonly refresh?: () => Promise; /** * @@ -167,9 +167,9 @@ export class TuyaCustomBearer extends Credential { protected key: string; protected secret: string; protected baseUri: string; - protected token: string | undefined; - protected refreshToken: string | undefined; - protected expireTime: Date | undefined; + protected token?: string; + protected refreshToken?: string; + protected expireTime?: Date; constructor(credentials: TuyaCustomBearerCredentialConfiguration, scheme: TuyaCustomBearerSecurityScheme) { super();