diff --git a/packages/core/eslint.config.mjs b/packages/core/eslint.config.mjs new file mode 100644 index 000000000..265a99010 --- /dev/null +++ b/packages/core/eslint.config.mjs @@ -0,0 +1,10 @@ +import rootConfig from "../../eslint.config.mjs"; + +export default [ + ...rootConfig, + { + rules: { + "@typescript-eslint/no-unnecessary-condition": "error", + }, + }, +]; diff --git a/packages/core/package.json b/packages/core/package.json index 588160be5..4aa5a670b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -40,7 +40,7 @@ "build": "tsc -b", "lint": "eslint .", "lint:fix": "eslint . --fix", - "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"**/*.json\"", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"**/*.json\" \"*.mjs\"", "test": "mocha --require ts-node/register --extension ts" }, "bugs": { @@ -50,8 +50,5 @@ "directories": { "test": "test" }, - "keywords": [], - "eslintConfig": { - "extends": "../../.eslintrc.js" - } + "keywords": [] } diff --git a/packages/core/src/codecs/octetstream-codec.ts b/packages/core/src/codecs/octetstream-codec.ts index 5aec25931..fff6f20b9 100644 --- a/packages/core/src/codecs/octetstream-codec.ts +++ b/packages/core/src/codecs/octetstream-codec.ts @@ -108,7 +108,7 @@ export default class OctetstreamCodec implements ContentCodec { if (typeSem) { if (typeSem[1] === "u") { // compare with schema information - if (parameters?.signed === "true") { + if (parameters.signed === "true") { throw new Error("Type is unsigned but 'signed' is true"); } // no schema, but type is unsigned @@ -117,7 +117,7 @@ export default class OctetstreamCodec implements ContentCodec { dataType = typeSem[2]; if (parseInt(typeSem[3]) !== bitLength) { throw new Error( - `Type is '${(typeSem[1] ?? "") + typeSem[2] + typeSem[3]}' but 'ex:bitLength' is ` + bitLength + `Type is '${(typeSem[1] || "") + typeSem[2] + typeSem[3]}' but 'ex:bitLength' is ` + bitLength ); } } @@ -130,11 +130,11 @@ export default class OctetstreamCodec implements ContentCodec { } // Handle byte swapping - if (parameters?.byteSeq?.includes("BYTE_SWAP") === true && bytes.length > 1) { + if (parameters.byteSeq?.includes("BYTE_SWAP") === true && bytes.length > 1) { bytes.swap16(); } - if (offset !== undefined && bitLength < bytes.length * 8) { + if (bitLength < bytes.length * 8) { bytes = this.readBits(bytes, offset, bitLength); bitLength = bytes.length * 8; } @@ -282,7 +282,7 @@ export default class OctetstreamCodec implements ContentCodec { throw new Error("'ex:bitOffset' must be a non-negative number"); } - let dataType: string = schema?.type ?? undefined; + let dataType: string | undefined = schema?.type; if (value === undefined) { throw new Error("Undefined value"); @@ -300,7 +300,7 @@ export default class OctetstreamCodec implements ContentCodec { if (typeSem) { if (typeSem[1] === "u") { // compare with schema information - if (parameters?.signed === "true") { + if (parameters.signed === "true") { throw new Error("Type is unsigned but 'signed' is true"); } // no schema, but type is unsigned @@ -310,7 +310,7 @@ export default class OctetstreamCodec implements ContentCodec { if (bitLength !== undefined) { if (parseInt(typeSem[3]) !== bitLength) { throw new Error( - `Type is '${(typeSem[1] ?? "") + typeSem[2] + typeSem[3]}' but 'ex:bitLength' is ` + + `Type is '${(typeSem[1] || "") + typeSem[2] + typeSem[3]}' but 'ex:bitLength' is ` + bitLength ); } @@ -446,7 +446,7 @@ export default class OctetstreamCodec implements ContentCodec { } // Handle byte swapping - if (byteSeq?.includes("BYTE_SwAP") && byteLength > 1) { + if (byteSeq.includes("BYTE_SwAP") && byteLength > 1) { buf.swap16(); } switch (byteLength) { @@ -584,6 +584,7 @@ export default class OctetstreamCodec implements ContentCodec { parameters: { [key: string]: string | undefined } = {}, result?: Buffer | undefined ): Buffer { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (typeof value !== "object" || value === null) { throw new Error("Value is not an object"); } diff --git a/packages/core/src/consumed-thing.ts b/packages/core/src/consumed-thing.ts index a17c72a2c..93bafc747 100644 --- a/packages/core/src/consumed-thing.ts +++ b/packages/core/src/consumed-thing.ts @@ -130,8 +130,12 @@ class InternalPropertySubscription extends InternalSubscription { private readonly form: FormElementProperty ) { super(thing, name, client); - const index = this.thing.properties?.[name].forms.indexOf(form as Form); - if (index === undefined || index < 0) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!this.thing.properties[name].forms) { + throw new Error(`Property '${name}' has no forms`); + } + const index = this.thing.properties[name].forms.indexOf(form as Form); + if (index < 0) { throw new Error(`Could not find form ${form.href} in property ${name}`); } this.formIndex = index; @@ -144,9 +148,6 @@ class InternalPropertySubscription extends InternalSubscription { public async unobserveProperty(options: WoT.InteractionOptions = {}): Promise { const tp = this.thing.properties[this.name]; - if (tp == null) { - throw new Error(`ConsumedThing '${this.thing.title}' does not have property ${this.name}`); - } options.formIndex ??= this.matchingUnsubscribeForm(); const { form } = this.thing.getClientFor(tp.forms, "unobserveproperty", Affordance.PropertyAffordance, options); if (form == null) { @@ -195,7 +196,7 @@ class InternalPropertySubscription extends InternalSubscription { for (let i = 0; i < forms.length; i++) { let score = 0; const form = forms[i]; - if (form.op === operation || (form?.op?.includes(operation) === true && Array.isArray(form.op) === true)) { + if (form.op === operation || (form.op?.includes(operation) === true && Array.isArray(form.op) === true)) { score += 1; } @@ -232,7 +233,7 @@ function findFormIndexWithScoring( for (let i = 0; i < forms.length; i++) { let score = 0; const form = forms[i]; - if (form.op === operation || (form?.op?.includes(operation) === true && Array.isArray(form.op) === true)) { + if (form.op === operation || (form.op?.includes(operation) === true && Array.isArray(form.op) === true)) { score += 1; } @@ -261,8 +262,12 @@ class InternalEventSubscription extends InternalSubscription { private readonly form: FormElementEvent ) { super(thing, name, client); - const index = this.thing.events?.[name].forms.indexOf(form as Form); - if (index === undefined || index < 0) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!this.thing.events[name].forms) { + throw new Error(`Event '${name}' has no forms`); + } + const index = this.thing.events[name].forms.indexOf(form as Form); + if (index < 0) { throw new Error(`Could not find form ${form.href} in event ${name}`); } this.formIndex = index; @@ -275,9 +280,6 @@ class InternalEventSubscription extends InternalSubscription { public async unsubscribeEvent(options: WoT.InteractionOptions = {}): Promise { const te = this.thing.events[this.name]; - if (te == null) { - throw new Error(`ConsumedThing '${this.thing.title}' does not have event ${this.name}`); - } options.formIndex ??= this.matchingUnsubscribeForm(); @@ -461,6 +463,7 @@ export default class ConsumedThing extends Thing implements IConsumedThing { let ws: SecurityScheme | undefined = this.securityDefinitions[s]; // also push nosec in case of proxy + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (ws?.scheme === "combo") { ws = resolveComboScheme(ws as ComboSecurityScheme, s); } @@ -476,23 +479,21 @@ export default class ConsumedThing extends Thing implements IConsumedThing { } ensureClientSecurity(client: ProtocolClient, form: Form | undefined): void { - if (this.securityDefinitions != null) { - const logStatement = () => - debug(`ConsumedThing '${this.title}' setting credentials for ${client} based on thing security`); - - if (form != null && Array.isArray(form.security) && form.security.length > 0) { - // Note security member in form objects overrides (i.e., completely replace) all definitions activated at the Thing level - // see https://www.w3.org/TR/wot-thing-description/#security-serialization-json - - logStatement(); - client.setSecurity(this.getSecuritySchemes(form.security), this.#servient.retrieveCredentials(this.id)); - } else if (Array.isArray(this.security) && this.security.length > 0) { - logStatement(); - client.setSecurity( - this.getSecuritySchemes(this.security as string[]), - this.#servient.getCredentials(this.id) - ); - } + const logStatement = () => + debug(`ConsumedThing '${this.title}' setting credentials for ${client} based on thing security`); + + if (form != null && Array.isArray(form.security) && form.security.length > 0) { + // Note security member in form objects overrides (i.e., completely replace) all definitions activated at the Thing level + // see https://www.w3.org/TR/wot-thing-description/#security-serialization-json + + logStatement(); + client.setSecurity(this.getSecuritySchemes(form.security), this.#servient.retrieveCredentials(this.id)); + } else if (Array.isArray(this.security) && this.security.length > 0) { + logStatement(); + client.setSecurity( + this.getSecuritySchemes(this.security as string[]), + this.#servient.getCredentials(this.id) + ); } } @@ -566,17 +567,12 @@ export default class ConsumedThing extends Thing implements IConsumedThing { async readProperty(propertyName: string, options?: WoT.InteractionOptions): Promise { // TODO pass expected form op to getClientFor() const tp = this.properties[propertyName]; - if (tp == null) { - throw new Error(`ConsumedThing '${this.title}' does not have property ${propertyName}`); - } const { client, form } = this.getClientFor(tp.forms, "readproperty", Affordance.PropertyAffordance, options); if (form == null) { throw new Error(`ConsumedThing '${this.title}' did not get suitable form`); } - if (client == null) { - throw new Error(`ConsumedThing '${this.title}' did not get suitable client for ${form.href}`); - } + debug(`ConsumedThing '${this.title}' reading ${form.href}`); // uriVariables ? @@ -597,6 +593,7 @@ export default class ConsumedThing extends Thing implements IConsumedThing { outputDataSchema: WoT.DataSchema | undefined ): InteractionOutput { // infer media type from form if not in response metadata + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition content.type ??= form.contentType ?? "application/json"; // check if returned media type is the same as expected media type (from TD) this.checkMediaTypeOrThrow(content, form); @@ -623,6 +620,7 @@ export default class ConsumedThing extends Thing implements IConsumedThing { synchronous?: boolean ): ActionInteractionOutput { // infer media type from form if not in response metadata + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition content.type ??= form.contentType ?? "application/json"; // check if returned media type is the same as expected media type (from TD) this.checkMediaTypeOrThrow(content, form); @@ -677,16 +675,10 @@ export default class ConsumedThing extends Thing implements IConsumedThing { ): Promise { // TODO pass expected form op to getClientFor() const tp = this.properties[propertyName]; - if (tp == null) { - throw new Error(`ConsumedThing '${this.title}' does not have property ${propertyName}`); - } const { client, form } = this.getClientFor(tp.forms, "writeproperty", Affordance.PropertyAffordance, options); if (form == null) { throw new Error(`ConsumedThing '${this.title}' did not get suitable form`); } - if (client == null) { - throw new Error(`ConsumedThing '${this.title}' did not get suitable client for ${form.href}`); - } debug(`ConsumedThing '${this.title}' writing ${form.href} with '${value}'`); const content = ContentManager.valueToContent(value, tp, form.contentType); @@ -718,16 +710,10 @@ export default class ConsumedThing extends Thing implements IConsumedThing { options?: WoT.InteractionOptions ): Promise { const ta = this.actions[actionName]; - if (ta == null) { - throw new Error(`ConsumedThing '${this.title}' does not have action ${actionName}`); - } const { client, form } = this.getClientFor(ta.forms, "invokeaction", Affordance.ActionAffordance, options); if (form == null) { throw new Error(`ConsumedThing '${this.title}' did not get suitable form`); } - if (client == null) { - throw new Error(`ConsumedThing '${this.title}' did not get suitable client for ${form.href}`); - } debug( `ConsumedThing '${this.title}' invoking ${form.href}${ parameter !== undefined ? " with '" + parameter + "'" : "" @@ -763,16 +749,10 @@ export default class ConsumedThing extends Thing implements IConsumedThing { options?: WoT.InteractionOptions ): Promise { const tp = this.properties[name]; - if (tp == null) { - throw new Error(`ConsumedThing '${this.title}' does not have property ${name}`); - } const { client, form } = this.getClientFor(tp.forms, "observeproperty", Affordance.PropertyAffordance, options); if (form == null) { throw new Error(`ConsumedThing '${this.title}' did not get suitable form`); } - if (client == null) { - throw new Error(`ConsumedThing '${this.title}' did not get suitable client for ${form.href}`); - } if (this.observedProperties.has(name)) { throw new Error( `ConsumedThing '${this.title}' has already a function subscribed to ${name}. You can only observe once` @@ -820,16 +800,10 @@ export default class ConsumedThing extends Thing implements IConsumedThing { options?: WoT.InteractionOptions ): Promise { const te = this.events[name]; - if (te == null) { - throw new Error(`ConsumedThing '${this.title}' does not have event ${name}`); - } const { client, form } = this.getClientFor(te.forms, "subscribeevent", Affordance.EventAffordance, options); if (form == null) { throw new Error(`ConsumedThing '${this.title}' did not get suitable form`); } - if (client == null) { - throw new Error(`ConsumedThing '${this.title}' did not get suitable client for ${form.href}`); - } if (this.subscribedEvents.has(name)) { throw new Error( `ConsumedThing '${this.title}' has already a function subscribed to ${name}. You can only subscribe once` diff --git a/packages/core/src/content-serdes.ts b/packages/core/src/content-serdes.ts index d6cac1189..245bb8c03 100644 --- a/packages/core/src/content-serdes.ts +++ b/packages/core/src/content-serdes.ts @@ -57,6 +57,7 @@ export class ContentSerdes { private offered: Set = new Set(); public static get(): ContentSerdes { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (this.instance == null) { this.instance = new ContentSerdes(); // JSON @@ -126,6 +127,7 @@ export class ContentSerdes { } public contentToValue(content: ReadContent, schema: DataSchema): DataSchemaValue | undefined { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (content.type === undefined) { if (content.body.byteLength > 0) { // default to application/json @@ -162,8 +164,6 @@ export class ContentSerdes { schema: DataSchema | undefined, contentType = ContentSerdes.DEFAULT ): Content { - if (value === undefined) warn("ContentSerdes valueToContent got no value"); - if (value instanceof ReadableStream) { return new Content(contentType, ProtocolHelpers.toNodeStream(value)); } diff --git a/packages/core/src/exposed-thing.ts b/packages/core/src/exposed-thing.ts index 51401068d..7283f5ea7 100644 --- a/packages/core/src/exposed-thing.ts +++ b/packages/core/src/exposed-thing.ts @@ -152,32 +152,22 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { } public emitEvent(name: string, data: WoT.InteractionInput): void { - if (this.events[name] != null) { - const eventAffordance = this.events[name]; - this.#eventListeners.notify(eventAffordance, data, eventAffordance.data); - } else { - // NotFoundError - throw new Error("NotFoundError for event '" + name + "'"); - } + const eventAffordance = this.events[name]; + this.#eventListeners.notify(eventAffordance, data, eventAffordance.data); } public async emitPropertyChange(name: string): Promise { - if (this.properties[name] != null) { - const property = this.properties[name]; - const readHandler = this.#propertyHandlers.get(name)?.readHandler; + const property = this.properties[name]; + const readHandler = this.#propertyHandlers.get(name)?.readHandler; - if (!readHandler) { - throw new Error( - "Can't read property readHandler is not defined. Did you forget to register a readHandler?" - ); - } - - const data = await readHandler(); - this.#propertyListeners.notify(property, data, property); - } else { - // NotFoundError - throw new Error("NotFoundError for property '" + name + "'"); + if (!readHandler) { + throw new Error( + "Can't read property readHandler is not defined. Did you forget to register a readHandler?" + ); } + + const data = await readHandler(); + this.#propertyListeners.notify(property, data, property); } /** @inheritDoc */ @@ -211,24 +201,20 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { setPropertyReadHandler(propertyName: string, handler: WoT.PropertyReadHandler): WoT.ExposedThing { debug(`ExposedThing '${this.title}' setting read handler for '${propertyName}'`); - if (this.properties[propertyName] != null) { - // setting read handler for writeOnly not allowed - if (this.properties[propertyName].writeOnly === true) { - throw new Error( - `ExposedThing '${this.title}' cannot set read handler for property '${propertyName}' due to writeOnly flag` - ); + // setting read handler for writeOnly not allowed + if (this.properties[propertyName].writeOnly === true) { + throw new Error( + `ExposedThing '${this.title}' cannot set read handler for property '${propertyName}' due to writeOnly flag` + ); + } else { + let propertyHandler = this.#propertyHandlers.get(propertyName); + if (propertyHandler) { + propertyHandler.readHandler = handler; } else { - let propertyHandler = this.#propertyHandlers.get(propertyName); - if (propertyHandler) { - propertyHandler.readHandler = handler; - } else { - propertyHandler = { readHandler: handler }; - } - - this.#propertyHandlers.set(propertyName, propertyHandler); + propertyHandler = { readHandler: handler }; } - } else { - throw new Error(`ExposedThing '${this.title}' has no Property '${propertyName}'`); + + this.#propertyHandlers.set(propertyName, propertyHandler); } return this; } @@ -236,24 +222,21 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { /** @inheritDoc */ setPropertyWriteHandler(propertyName: string, handler: WoT.PropertyWriteHandler): WoT.ExposedThing { debug(`ExposedThing '${this.title}' setting write handler for '${propertyName}'`); - if (this.properties[propertyName] != null) { - // setting write handler for readOnly not allowed - if (this.properties[propertyName].readOnly === true) { - throw new Error( - `ExposedThing '${this.title}' cannot set write handler for property '${propertyName}' due to readOnly flag` - ); - } else { - let propertyHandler = this.#propertyHandlers.get(propertyName); - if (propertyHandler) { - propertyHandler.writeHandler = handler; - } else { - propertyHandler = { writeHandler: handler }; - } - this.#propertyHandlers.set(propertyName, propertyHandler); - } + // setting write handler for readOnly not allowed + if (this.properties[propertyName].readOnly === true) { + throw new Error( + `ExposedThing '${this.title}' cannot set write handler for property '${propertyName}' due to readOnly flag` + ); } else { - throw new Error(`ExposedThing '${this.title}' has no Property '${propertyName}'`); + let propertyHandler = this.#propertyHandlers.get(propertyName); + if (propertyHandler) { + propertyHandler.writeHandler = handler; + } else { + propertyHandler = { writeHandler: handler }; + } + + this.#propertyHandlers.set(propertyName, propertyHandler); } return this; } @@ -262,22 +245,18 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { setPropertyObserveHandler(name: string, handler: WoT.PropertyReadHandler): WoT.ExposedThing { debug(`ExposedThing '${this.title}' setting property observe handler for '${name}'`); - if (this.properties[name] != null) { - if (this.properties[name].observable !== true) { - throw new Error( - `ExposedThing '${this.title}' cannot set observe handler for property '${name}' since the observable flag is set to false` - ); + if (this.properties[name].observable !== true) { + throw new Error( + `ExposedThing '${this.title}' cannot set observe handler for property '${name}' since the observable flag is set to false` + ); + } else { + let propertyHandler = this.#propertyHandlers.get(name); + if (propertyHandler) { + propertyHandler.observeHandler = handler; } else { - let propertyHandler = this.#propertyHandlers.get(name); - if (propertyHandler) { - propertyHandler.observeHandler = handler; - } else { - propertyHandler = { observeHandler: handler }; - } - this.#propertyHandlers.set(name, propertyHandler); + propertyHandler = { observeHandler: handler }; } - } else { - throw new Error(`ExposedThing '${this.title}' has no Property '${name}'`); + this.#propertyHandlers.set(name, propertyHandler); } return this; } @@ -286,22 +265,18 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { setPropertyUnobserveHandler(name: string, handler: WoT.PropertyReadHandler): WoT.ExposedThing { debug(`ExposedThing '${this.title}' setting property unobserve handler for '${name}'`); - if (this.properties[name] != null) { - if (this.properties[name].observable !== true) { - throw new Error( - `ExposedThing '${this.title}' cannot set unobserve handler for property '${name}' due to missing observable flag` - ); + if (this.properties[name].observable !== true) { + throw new Error( + `ExposedThing '${this.title}' cannot set unobserve handler for property '${name}' due to missing observable flag` + ); + } else { + let propertyHandler = this.#propertyHandlers.get(name); + if (propertyHandler) { + propertyHandler.unobserveHandler = handler; } else { - let propertyHandler = this.#propertyHandlers.get(name); - if (propertyHandler) { - propertyHandler.unobserveHandler = handler; - } else { - propertyHandler = { unobserveHandler: handler }; - } - this.#propertyHandlers.set(name, propertyHandler); + propertyHandler = { unobserveHandler: handler }; } - } else { - throw new Error(`ExposedThing '${this.title}' has no Property '${name}'`); + this.#propertyHandlers.set(name, propertyHandler); } return this; } @@ -309,12 +284,7 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { /** @inheritDoc */ setActionHandler(actionName: string, handler: WoT.ActionHandler): WoT.ExposedThing { debug(`ExposedThing '${this.title}' setting action handler for '${actionName}'`); - - if (this.actions[actionName] != null) { - this.#actionHandlers.set(actionName, handler); - } else { - throw new Error(`ExposedThing '${this.title}' has no Action '${actionName}'`); - } + this.#actionHandlers.set(actionName, handler); return this; } @@ -322,18 +292,13 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { setEventSubscribeHandler(name: string, handler: WoT.EventSubscriptionHandler): WoT.ExposedThing { debug(`ExposedThing '${this.title}' setting event subscribe handler for '${name}'`); - if (this.events[name] != null) { - let eventHandler = this.#eventHandlers.get(name); - if (eventHandler) { - eventHandler.subscribe = handler; - } else { - eventHandler = { subscribe: handler }; - } - - this.#eventHandlers.set(name, eventHandler); + let eventHandler = this.#eventHandlers.get(name); + if (eventHandler) { + eventHandler.subscribe = handler; } else { - throw new Error(`ExposedThing '${this.title}' has no Event '${name}'`); + eventHandler = { subscribe: handler }; } + this.#eventHandlers.set(name, eventHandler); return this; } @@ -341,18 +306,13 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { setEventUnsubscribeHandler(name: string, handler: WoT.EventSubscriptionHandler): WoT.ExposedThing { debug(`ExposedThing '${this.title}' setting event unsubscribe handler for '${name}'`); - if (this.events[name] != null) { - let eventHandler = this.#eventHandlers.get(name); - if (eventHandler) { - eventHandler.unsubscribe = handler; - } else { - eventHandler = { unsubscribe: handler }; - } - - this.#eventHandlers.set(name, eventHandler); + let eventHandler = this.#eventHandlers.get(name); + if (eventHandler) { + eventHandler.unsubscribe = handler; } else { - throw new Error(`ExposedThing '${this.title}' has no Event '${name}'`); + eventHandler = { unsubscribe: handler }; } + this.#eventHandlers.set(name, eventHandler); return this; } @@ -366,27 +326,23 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { options: WoT.InteractionOptions & { formIndex: number } ): Promise { // TODO: handling URI variables? - if (this.actions[name] != null) { - debug(`ExposedThing '${this.title}' has Action state of '${name}'`); - - const handler = this.#actionHandlers.get(name); - if (handler != null) { - debug(`ExposedThing '${this.title}' calls registered handler for Action '${name}'`); - Helpers.validateInteractionOptions(this, this.actions[name], options); - const form = this.actions[name].forms[options.formIndex] ?? { contentType: "application/json" }; - const result: WoT.InteractionInput | void = await handler( - new InteractionOutput(inputContent, form, this.actions[name].input), - options - ); - if (result !== undefined) { - // TODO: handle form.response.contentType - return ContentManager.valueToContent(result, this.actions[name].output, form.contentType); - } - } else { - throw new Error(`ExposedThing '${this.title}' has no handler for Action '${name}'`); + debug(`ExposedThing '${this.title}' has Action state of '${name}'`); + + const handler = this.#actionHandlers.get(name); + if (handler != null) { + debug(`ExposedThing '${this.title}' calls registered handler for Action '${name}'`); + Helpers.validateInteractionOptions(this, this.actions[name], options); + const form = this.actions[name].forms[options.formIndex] ?? { contentType: "application/json" }; + const result: WoT.InteractionInput | void = await handler( + new InteractionOutput(inputContent, form, this.actions[name].input), + options + ); + if (result !== undefined) { + // TODO: handle form.response.contentType + return ContentManager.valueToContent(result, this.actions[name].output, form.contentType); } } else { - throw new Error(`ExposedThing '${this.title}', no action found for '${name}'`); + throw new Error(`ExposedThing '${this.title}' has no handler for Action '${name}'`); } } @@ -398,28 +354,24 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { propertyName: string, options: WoT.InteractionOptions & { formIndex: number } ): Promise { - if (this.properties[propertyName] != null) { - debug(`ExposedThing '${this.title}' has Action state of '${propertyName}'`); - - const readHandler = this.#propertyHandlers.get(propertyName)?.readHandler; - - if (readHandler != null) { - debug(`ExposedThing '${this.title}' calls registered readHandler for Property '${propertyName}'`); - Helpers.validateInteractionOptions(this, this.properties[propertyName], options); - const result: WoT.InteractionInput | void = await readHandler(options); - const form = this.properties[propertyName]?.forms[options.formIndex] ?? { - contentType: "application/json", - }; - return ContentManager.valueToContent( - result, - this.properties[propertyName], - form?.contentType ?? "application/json" - ); - } else { - throw new Error(`ExposedThing '${this.title}' has no readHandler for Property '${propertyName}'`); - } + debug(`ExposedThing '${this.title}' has Action state of '${propertyName}'`); + + const readHandler = this.#propertyHandlers.get(propertyName)?.readHandler; + + if (readHandler != null) { + debug(`ExposedThing '${this.title}' calls registered readHandler for Property '${propertyName}'`); + Helpers.validateInteractionOptions(this, this.properties[propertyName], options); + const result: WoT.InteractionInput | void = await readHandler(options); + const form = this.properties[propertyName].forms[options.formIndex] ?? { + contentType: "application/json", + }; + return ContentManager.valueToContent( + result, + this.properties[propertyName], + form.contentType ?? "application/json" + ); } else { - throw new Error(`ExposedThing '${this.title}', no property found for '${propertyName}'`); + throw new Error(`ExposedThing '${this.title}' has no readHandler for Property '${propertyName}'`); } } @@ -483,21 +435,17 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { options: WoT.InteractionOptions & { formIndex: number } ): Promise { // TODO: to be removed next api does not allow an ExposedThing to be also a ConsumeThing - if (this.properties[propertyName] != null) { - if (this.properties[propertyName].readOnly === true) { - throw new Error(`ExposedThing '${this.title}', property '${propertyName}' is readOnly`); - } - Helpers.validateInteractionOptions(this, this.properties[propertyName], options); - const writeHandler = this.#propertyHandlers.get(propertyName)?.writeHandler; - const form = this.properties[propertyName]?.forms[options.formIndex] ?? {}; - // call write handler (if any) - if (writeHandler != null) { - await writeHandler(new InteractionOutput(inputContent, form, this.properties[propertyName]), options); - } else { - throw new Error(`ExposedThing '${this.title}' has no writeHandler for Property '${propertyName}'`); - } + if (this.properties[propertyName].readOnly === true) { + throw new Error(`ExposedThing '${this.title}', property '${propertyName}' is readOnly`); + } + Helpers.validateInteractionOptions(this, this.properties[propertyName], options); + const writeHandler = this.#propertyHandlers.get(propertyName)?.writeHandler; + const form = this.properties[propertyName].forms[options.formIndex] ?? {}; + // call write handler (if any) + if (writeHandler != null) { + await writeHandler(new InteractionOutput(inputContent, form, this.properties[propertyName]), options); } else { - throw new Error(`ExposedThing '${this.title}', no property found for '${propertyName}'`); + throw new Error(`ExposedThing '${this.title}' has no writeHandler for Property '${propertyName}'`); } } @@ -539,33 +487,29 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { listener: ContentListener, options: WoT.InteractionOptions & { formIndex: number } ): Promise { - if (this.events[name] != null) { - Helpers.validateInteractionOptions(this, this.events[name], options); - - const formIndex = ProtocolHelpers.getFormIndexForOperation( - this.events[name], - "event", - "subscribeevent", - options.formIndex - ); + Helpers.validateInteractionOptions(this, this.events[name], options); - if (formIndex !== -1) { - this.#eventListeners.register(this.events[name], formIndex, listener); - debug(`ExposedThing '${this.title}' subscribes to event '${name}'`); - } else { - throw new Error( - `ExposedThing '${this.title}', no property listener from found for '${name}' with form index '${options.formIndex}'` - ); - } + const formIndex = ProtocolHelpers.getFormIndexForOperation( + this.events[name], + "event", + "subscribeevent", + options.formIndex + ); - const subscribe = this.#eventHandlers.get(name)?.subscribe; - if (subscribe) { - await subscribe(options); - } + if (formIndex !== -1) { + this.#eventListeners.register(this.events[name], formIndex, listener); debug(`ExposedThing '${this.title}' subscribes to event '${name}'`); } else { - throw new Error(`ExposedThing '${this.title}', no event found for '${name}'`); + throw new Error( + `ExposedThing '${this.title}', no property listener from found for '${name}' with form index '${options.formIndex}'` + ); + } + + const subscribe = this.#eventHandlers.get(name)?.subscribe; + if (subscribe) { + await subscribe(options); } + debug(`ExposedThing '${this.title}' subscribes to event '${name}'`); } /** @@ -577,30 +521,27 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { listener: ContentListener, options: WoT.InteractionOptions & { formIndex: number } ): void { - if (this.events[name] != null) { - Helpers.validateInteractionOptions(this, this.events[name], options); - - const formIndex = ProtocolHelpers.getFormIndexForOperation( - this.events[name], - "event", - "unsubscribeevent", - options.formIndex - ); - if (formIndex !== -1) { - this.#eventListeners.unregister(this.events[name], formIndex, listener); - } else { - throw new Error( - `ExposedThing '${this.title}', no event listener from found for '${name}' with form index '${options.formIndex}'` - ); - } - const unsubscribe = this.#eventHandlers.get(name)?.unsubscribe; - if (unsubscribe) { - unsubscribe(options); - } - debug(`ExposedThing '${this.title}' unsubscribes from event '${name}'`); + // if (this.events[name] != null) { + Helpers.validateInteractionOptions(this, this.events[name], options); + + const formIndex = ProtocolHelpers.getFormIndexForOperation( + this.events[name], + "event", + "unsubscribeevent", + options.formIndex + ); + if (formIndex !== -1) { + this.#eventListeners.unregister(this.events[name], formIndex, listener); } else { - throw new Error(`ExposedThing '${this.title}', no event found for '${name}'`); + throw new Error( + `ExposedThing '${this.title}', no event listener from found for '${name}' with form index '${options.formIndex}'` + ); + } + const unsubscribe = this.#eventHandlers.get(name)?.unsubscribe; + if (unsubscribe) { + unsubscribe(options); } + debug(`ExposedThing '${this.title}' unsubscribes from event '${name}'`); } /** @@ -612,30 +553,26 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { listener: ContentListener, options: WoT.InteractionOptions & { formIndex: number } ): Promise { - if (this.properties[name] != null) { - Helpers.validateInteractionOptions(this, this.properties[name], options); - const formIndex = ProtocolHelpers.getFormIndexForOperation( - this.properties[name], - "property", - "observeproperty", - options.formIndex + Helpers.validateInteractionOptions(this, this.properties[name], options); + const formIndex = ProtocolHelpers.getFormIndexForOperation( + this.properties[name], + "property", + "observeproperty", + options.formIndex + ); + + if (formIndex !== -1) { + this.#propertyListeners.register(this.properties[name], formIndex, listener); + debug(`ExposedThing '${this.title}' subscribes to property '${name}'`); + } else { + throw new Error( + `ExposedThing '${this.title}', no property listener from found for '${name}' with form index '${options.formIndex}'` ); + } - if (formIndex !== -1) { - this.#propertyListeners.register(this.properties[name], formIndex, listener); - debug(`ExposedThing '${this.title}' subscribes to property '${name}'`); - } else { - throw new Error( - `ExposedThing '${this.title}', no property listener from found for '${name}' with form index '${options.formIndex}'` - ); - } - - const observeHandler = this.#propertyHandlers.get(name)?.observeHandler; - if (observeHandler) { - await observeHandler(options); - } - } else { - throw new Error(`ExposedThing '${this.title}', no property found for '${name}'`); + const observeHandler = this.#propertyHandlers.get(name)?.observeHandler; + if (observeHandler) { + await observeHandler(options); } } @@ -644,29 +581,25 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { listener: ContentListener, options: WoT.InteractionOptions & { formIndex: number } ): void { - if (this.properties[name] != null) { - Helpers.validateInteractionOptions(this, this.properties[name], options); - const formIndex = ProtocolHelpers.getFormIndexForOperation( - this.properties[name], - "property", - "unobserveproperty", - options.formIndex + Helpers.validateInteractionOptions(this, this.properties[name], options); + const formIndex = ProtocolHelpers.getFormIndexForOperation( + this.properties[name], + "property", + "unobserveproperty", + options.formIndex + ); + + if (formIndex !== -1) { + this.#propertyListeners.unregister(this.properties[name], formIndex, listener); + } else { + throw new Error( + `ExposedThing '${this.title}', no property listener from found for '${name}' with form index '${options.formIndex}'` ); + } - if (formIndex !== -1) { - this.#propertyListeners.unregister(this.properties[name], formIndex, listener); - } else { - throw new Error( - `ExposedThing '${this.title}', no property listener from found for '${name}' with form index '${options.formIndex}'` - ); - } - - const unobserveHandler = this.#propertyHandlers.get(name)?.unobserveHandler; - if (unobserveHandler) { - unobserveHandler(options); - } - } else { - throw new Error(`ExposedThing '${this.title}', no property found for '${name}'`); + const unobserveHandler = this.#propertyHandlers.get(name)?.unobserveHandler; + if (unobserveHandler) { + unobserveHandler(options); } } diff --git a/packages/core/src/helpers.ts b/packages/core/src/helpers.ts index e39fb8c48..38877d843 100644 --- a/packages/core/src/helpers.ts +++ b/packages/core/src/helpers.ts @@ -63,9 +63,6 @@ export default class Helpers implements Resolver { const parsed = new URL(uri); debug(parsed); // remove trailing ':' - if (parsed.protocol === null) { - throw new Error(`Protocol in url "${uri}" must be valid`); - } const scheme = parsed.protocol.slice(0, -1); debug(`Helpers found scheme '${scheme}'`); return scheme; @@ -243,7 +240,7 @@ export default class Helpers implements Resolver { } if (tdSchemaCopy.definitions != null) { - for (const [prop, propValue] of Object.entries(tdSchemaCopy.definitions) ?? []) { + for (const [prop, propValue] of Object.entries(tdSchemaCopy.definitions)) { tdSchemaCopy.definitions[prop] = this.createExposeThingInitSchema(propValue); } } @@ -351,12 +348,12 @@ export default class Helpers implements Resolver { uriVariables: { [k: string]: DataSchema } = {} ): Record { const params: Record = {}; - if (url == null || (uriVariables == null && globalUriVariables == null)) { + if (url == null) { return params; } - const queryparams = url.split("?")[1]; - if (queryparams == null) { + const queryparams = url.split("?")[1] as string | undefined; + if (queryparams === undefined) { return params; } const queries = queryparams.indexOf("&") !== -1 ? queryparams.split("&") : [queryparams]; @@ -367,14 +364,14 @@ export default class Helpers implements Resolver { const queryKey: string = decodeURIComponent(indexPair[0]); const queryValue: string = decodeURIComponent(indexPair.length > 1 ? indexPair[1] : ""); - if (uriVariables != null && uriVariables[queryKey] != null) { + if (Object.prototype.hasOwnProperty.call(uriVariables, queryKey)) { if (uriVariables[queryKey].type === "integer" || uriVariables[queryKey].type === "number") { // *cast* it to number params[queryKey] = +queryValue; } else { params[queryKey] = queryValue; } - } else if (globalUriVariables != null && globalUriVariables[queryKey] != null) { + } else if (Object.prototype.hasOwnProperty.call(globalUriVariables, queryKey)) { if (globalUriVariables[queryKey].type === "integer" || globalUriVariables[queryKey].type === "number") { // *cast* it to number params[queryKey] = +queryValue; diff --git a/packages/core/src/protocol-helpers.ts b/packages/core/src/protocol-helpers.ts index c2c5cdb77..47199637c 100644 --- a/packages/core/src/protocol-helpers.ts +++ b/packages/core/src/protocol-helpers.ts @@ -19,7 +19,7 @@ import { ReadableStream as PolyfillStream } from "web-streams-polyfill"; import { ActionElement, EventElement, PropertyElement } from "wot-thing-description-types"; import { createLoggers } from "./logger"; -const { debug, warn } = createLoggers("core", "protocol-helpers"); +const { warn } = createLoggers("core", "protocol-helpers"); export interface IManagedStream { nodeStream: Readable; @@ -63,7 +63,9 @@ function isManaged(obj: unknown): obj is IManagedStream { export default class ProtocolHelpers { // set contentType (extend with more?) public static updatePropertyFormWithTemplate(form: Form, property: PropertyElement): void { - for (const formTemplate of property.forms ?? []) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (property.forms == null) return; + for (const formTemplate of property.forms) { // 1. Try to find match with correct href scheme if (formTemplate.href) { // TODO match for example http only? @@ -77,7 +79,9 @@ export default class ProtocolHelpers { } public static updateActionFormWithTemplate(form: Form, action: ActionElement): void { - for (const formTemplate of action.forms ?? []) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (action.forms == null) return; + for (const formTemplate of action.forms) { // 1. Try to find match with correct href scheme if (formTemplate.href) { // TODO match for example http only? @@ -91,7 +95,9 @@ export default class ProtocolHelpers { } public static updateEventFormWithTemplate(form: Form, event: EventElement): void { - for (const formTemplate of event.forms ?? []) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (event.forms == null) return; + for (const formTemplate of event.forms) { // 1. Try to find match with correct href scheme if (formTemplate.href) { // TODO match for example http only? @@ -112,16 +118,9 @@ export default class ProtocolHelpers { // try to find contentType (How to do this better) // Should interaction methods like readProperty() return an encapsulated value container with value&contentType // as sketched in https://github.com/w3c/wot-scripting-api/issues/201#issuecomment-573702999 - if ( - propertyName != null && - uriScheme != null && - td?.properties != null && - td.properties[propertyName] != null && - td.properties[propertyName].forms != null && - Array.isArray(td.properties[propertyName].forms) - ) { + if (td.properties != null && Array.isArray(td.properties[propertyName].forms)) { for (const form of td.properties[propertyName].forms) { - if (form.href?.startsWith(uriScheme) && form.contentType != null) { + if (form.href.startsWith(uriScheme) && form.contentType != null) { return form.contentType; // abort loop } } @@ -136,15 +135,9 @@ export default class ProtocolHelpers { uriScheme: string ): string | undefined { // try to find contentType - if ( - actionName != null && - uriScheme != null && - td?.actions && - td.actions != null && - Array.isArray(td.actions[actionName]?.forms) - ) { + if (td.actions && Array.isArray(td.actions[actionName].forms)) { for (const form of td.actions[actionName].forms) { - if (form.href && form.href.startsWith(uriScheme) && form.contentType != null) { + if (form.href.startsWith(uriScheme) && form.contentType != null) { return form.contentType; // abort loop } } @@ -159,13 +152,7 @@ export default class ProtocolHelpers { uriScheme: string ): string | undefined { // try to find contentType - if ( - eventName != null && - uriScheme != null && - td?.events && - td?.events[eventName]?.forms != null && - Array.isArray(td.events[eventName].forms) - ) { + if (td.events && Array.isArray(td.events[eventName].forms)) { for (const form of td.events[eventName].forms) { if (form.href && form.href.startsWith(uriScheme) && form.contentType != null) { return form.contentType; // abort loop @@ -235,26 +222,21 @@ export default class ProtocolHelpers { static readStreamFully(stream: NodeJS.ReadableStream): Promise> { return new Promise((resolve, reject) => { - if (stream != null) { - const chunks: Array = []; - stream.on("data", (data) => chunks.push(data)); - stream.on("error", reject); - stream.on("end", () => { - if ( - chunks[0] != null && - (chunks[0] instanceof Array || chunks[0] instanceof Buffer || chunks[0] instanceof Uint8Array) - ) { - resolve(Buffer.concat(chunks as Array)); - } else if (chunks[0] != null && typeof chunks[0] === "string") { - resolve(Buffer.from(chunks.join())); - } else { - resolve(Buffer.from(chunks as Array)); - } - }); - } else { - debug(`Protocol-Helper returns empty buffer for readStreamFully due to undefined stream`); - resolve(Buffer.alloc(0)); - } + const chunks: Array = []; + stream.on("data", (data) => chunks.push(data)); + stream.on("error", reject); + stream.on("end", () => { + if ( + chunks[0] != null && + (chunks[0] instanceof Array || chunks[0] instanceof Buffer || chunks[0] instanceof Uint8Array) + ) { + resolve(Buffer.concat(chunks as Array)); + } else if (chunks[0] != null && typeof chunks[0] === "string") { + resolve(Buffer.from(chunks.join())); + } else { + resolve(Buffer.from(chunks as Array)); + } + }); }); } @@ -345,15 +327,15 @@ export default class ProtocolHelpers { } // If a form index hint is gived, you it. Just check the form actually supports the op - if (interaction.forms !== undefined && formIndex !== undefined && interaction.forms.length > formIndex) { + if (formIndex !== undefined && interaction.forms.length > formIndex) { const form = interaction.forms[formIndex]; - if (form != null && (operationName == null || form.op?.includes(operationName) === true)) { + if (operationName == null || form.op?.includes(operationName) === true) { finalFormIndex = formIndex; } } // If no form was found yet, loop through all forms - if (interaction.forms !== undefined && finalFormIndex === -1) { + if (finalFormIndex === -1) { if (operationName !== undefined) { interaction.forms.every((form: Form) => { // operationName !== undefined @@ -363,12 +345,9 @@ export default class ProtocolHelpers { } return finalFormIndex === -1; }); - } else { - interaction.forms.every((form: Form) => { - // interaction.forms !== undefined - finalFormIndex = interaction.forms!.indexOf(form); - return false; - }); + } else if (interaction.forms.length > 0) { + // interaction.forms !== undefined + finalFormIndex = 0; } } diff --git a/packages/core/src/protocol-listener-registry.ts b/packages/core/src/protocol-listener-registry.ts index b5575488f..6a6abdddc 100644 --- a/packages/core/src/protocol-listener-registry.ts +++ b/packages/core/src/protocol-listener-registry.ts @@ -21,6 +21,7 @@ export default class ProtocolListenerRegistry { private static EMPTY_MAP = new Map(); private listeners: Map> = new Map(); register(affordance: ThingInteraction, formIndex: number, listener: ContentListener): void { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (affordance.forms[formIndex] == null) { throw new Error( "Can't register the listener for affordance with formIndex. The affordance does not contain the form" @@ -76,6 +77,10 @@ export default class ProtocolListenerRegistry { if (formIndex !== undefined) { const listeners = formMap.get(formIndex); if (listeners) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!affordance.forms || affordance.forms[formIndex] === undefined) { + throw new Error(`Form at index ${formIndex} does not exist`); + } const contentType = affordance.forms[formIndex].contentType; const content = contentSerdes.valueToContent(data, schema, contentType); diff --git a/packages/core/src/serdes.ts b/packages/core/src/serdes.ts index 81dabb642..022e4341b 100644 --- a/packages/core/src/serdes.ts +++ b/packages/core/src/serdes.ts @@ -27,7 +27,7 @@ import { ThingDescription, } from "wot-thing-description-types"; -const { debug, warn } = createLoggers("core", "serdes"); +const { debug } = createLoggers("core", "serdes"); type AffordanceElement = PropertyElement | ActionElement | EventElement; @@ -91,6 +91,7 @@ export function parseTD(td: string, normalize?: boolean): Thing { // apply defaults as per WoT Thing Description spec + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (thing["@context"] === undefined) { thing["@context"] = [TD.DEFAULT_CONTEXT_V1, TD.DEFAULT_CONTEXT_V11]; } else if (Array.isArray(thing["@context"])) { @@ -131,6 +132,7 @@ export function parseTD(td: string, normalize?: boolean): Thing { } thing["@context"] = semContext as ThingContext; } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition } else if (thing["@context"] !== TD.DEFAULT_CONTEXT_V1 && thing["@context"] !== TD.DEFAULT_CONTEXT_V11) { const semContext = thing["@context"]; // insert default contexts as first entries @@ -168,9 +170,6 @@ export function parseTD(td: string, normalize?: boolean): Thing { adjustAffordanceField(thing, affordanceKey); } - if (thing.security === undefined) { - warn("parseTD() found no security metadata"); - } // wrap in array for later simplification if (typeof thing.security === "string") { thing.security = [thing.security]; @@ -180,10 +179,11 @@ export function parseTD(td: string, normalize?: boolean): Thing { const allForms = []; // properties for (const [propName, prop] of Object.entries(thing.properties ?? {})) { - // ensure forms mandatory forms field - if (prop.forms == null) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!prop.forms || !Array.isArray(prop.forms)) { throw new Error(`Property '${propName}' has no forms field`); } + // ensure forms mandatory forms field for (const form of prop.forms) { if (!form.href) { throw new Error(`Form of Property '${propName}' has no href field`); @@ -197,10 +197,11 @@ export function parseTD(td: string, normalize?: boolean): Thing { } // actions for (const [actName, act] of Object.entries(thing.actions ?? {})) { - // ensure forms mandatory forms field - if (act.forms == null) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!act.forms || !Array.isArray(act.forms)) { throw new Error(`Action '${actName}' has no forms field`); } + // ensure forms mandatory forms field for (const form of act.forms) { if (!form.href) { throw new Error(`Form of Action '${actName}' has no href field`); @@ -214,10 +215,11 @@ export function parseTD(td: string, normalize?: boolean): Thing { } // events for (const [evtName, evt] of Object.entries(thing.events ?? {})) { - // ensure forms mandatory forms field - if (evt.forms == null) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!evt.forms || !Array.isArray(evt.forms)) { throw new Error(`Event '${evtName}' has no forms field`); } + // ensure forms mandatory forms field for (const form of evt.forms) { if (!form.href) { throw new Error(`Form of Event '${evtName}' has no href field`); @@ -251,7 +253,7 @@ export function serializeTD(thing: Thing): string { const copy: Thing = JSON.parse(JSON.stringify(thing)); // clean-ups - if (copy.security == null || copy.security.length === 0) { + if (copy.security.length === 0) { copy.securityDefinitions = { nosec_sc: { scheme: "nosec" }, }; @@ -287,7 +289,7 @@ export function serializeTD(thing: Thing): string { delete copy.events; } - if (copy?.links.length === 0) { + if (copy.links?.length === 0) { delete copy.links; } diff --git a/packages/core/src/servient.ts b/packages/core/src/servient.ts index 99261b4d2..4b52ff868 100644 --- a/packages/core/src/servient.ts +++ b/packages/core/src/servient.ts @@ -178,6 +178,7 @@ export default class Servient { } public addCredentials(credentials: Record): void { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition for (const [credentialKey, credentialValue] of Object.entries(credentials ?? {})) { debug(`Servient storing credentials for '${credentialKey}'`); const currentCredentials = this.credentialStore.get(credentialKey) ?? []; diff --git a/packages/core/test/content-serdes-test.ts b/packages/core/test/content-serdes-test.ts index d67b460a7..160d0fcca 100644 --- a/packages/core/test/content-serdes-test.ts +++ b/packages/core/test/content-serdes-test.ts @@ -71,7 +71,7 @@ const checkStreamToValue = ( expect( ContentSerdes.contentToValue( { type: contentType, body: octectBuffer }, - { type: type ?? "integer", properties: {}, ...schema } + { type: type, properties: {}, ...schema } ) ).to.deep.equal(match); }; diff --git a/packages/core/test/server-test.ts b/packages/core/test/server-test.ts index 8216aa370..c6ba6d0f5 100644 --- a/packages/core/test/server-test.ts +++ b/packages/core/test/server-test.ts @@ -273,7 +273,7 @@ class WoTServerTest { expect(thing).to.have.property("properties").to.have.property("my number"); // Check internals, how to to check handlers properly with *some* type-safety - const ff = await readHandler?.(); + const ff = await readHandler(); expect(ff).to.equal(1); }