From a883280c02891b3fc92af53cefc28768dbd0a6b0 Mon Sep 17 00:00:00 2001 From: sebastiankb Date: Wed, 6 Aug 2025 09:35:07 +0200 Subject: [PATCH 01/16] Update README.md --- packages/binding-modbus/README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/binding-modbus/README.md b/packages/binding-modbus/README.md index b19208fba..1593c961f 100644 --- a/packages/binding-modbus/README.md +++ b/packages/binding-modbus/README.md @@ -8,7 +8,20 @@ W3C WoT Binding Template for Modbus can be found [here](https://w3c.github.io/wo Current Maintainer(s): [@relu91](https://github.com/relu91) [@fillobotto](https://github.com/fillobotto) -## Client Example +## Protocol specifier + +The protocol prefix handled by this binding is `modbus+tcp://`. + +## Getting Started + +In the following examples it is shown how to use the Modbus binding of node-wot. + +### Prerequisites + +- `npm install @node-wot/core` +- `npm install @node-wot/binding-modbus` + +### Client Example You can use a code like the following to use the binding. This specific code is interacting with one of the Eclipse Thingweb Test Things at . @@ -42,10 +55,6 @@ main(); ## Binding Information -### Protocol specifier - -The protocol prefix handled by this binding is `modbus+tcp://`. - ### New Form Fields for the Modbus Binding **Note**: for further details please refer to the [documentation](https://github.com/eclipse-thingweb/node-wot/blob/master/packages/binding-modbus/src/modbus.ts). From c90bd5cb2f111cef24c44a660680b235e380801b Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Tue, 7 Oct 2025 11:47:48 +0200 Subject: [PATCH 02/16] refactor: activate rule no-unused-private-class-members but turn off for some exceptions --- eslint.config.mjs | 2 +- packages/core/src/consumed-thing.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 752c49bbd..21d4e3524 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -135,7 +135,7 @@ export default defineConfig([ "no-use-before-define": "error", - "no-unused-private-class-members": "off", // https://github.com/eclipse-thingweb/node-wot/issues/1430 + "no-unused-private-class-members": "error", "no-prototype-builtins": "off", "no-case-declarations": "off", diff --git a/packages/core/src/consumed-thing.ts b/packages/core/src/consumed-thing.ts index 0feb684b5..1a762633b 100644 --- a/packages/core/src/consumed-thing.ts +++ b/packages/core/src/consumed-thing.ts @@ -65,7 +65,9 @@ export interface ClientAndForm { } class ConsumedThingProperty extends ThingProperty implements ThingProperty, BaseSchema { + // eslint-disable-next-line no-unused-private-class-members #name: string; + // eslint-disable-next-line no-unused-private-class-members #thing: ConsumedThing; constructor(name: string, thing: ConsumedThing) { @@ -77,7 +79,9 @@ class ConsumedThingProperty extends ThingProperty implements ThingProperty, Base } class ConsumedThingAction extends ThingAction implements ThingAction { + // eslint-disable-next-line no-unused-private-class-members #name: string; + // eslint-disable-next-line no-unused-private-class-members #thing: ConsumedThing; constructor(name: string, thing: ConsumedThing) { @@ -89,7 +93,9 @@ class ConsumedThingAction extends ThingAction implements ThingAction { } class ConsumedThingEvent extends ThingEvent { + // eslint-disable-next-line no-unused-private-class-members #name: string; + // eslint-disable-next-line no-unused-private-class-members #thing: ConsumedThing; constructor(name: string, thing: ConsumedThing) { From 48ea3152cf4715dcf313c6f1ee41ce692826b6b7 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Tue, 7 Oct 2025 17:13:20 +0200 Subject: [PATCH 03/16] refactor: activate eslint n/no-unpublished-import by moving/adding dependencies --- eslint.config.mjs | 2 +- package-lock.json | 10 +++++++--- packages/core/package.json | 6 +++--- packages/examples/src/security/oauth/package.json | 4 +++- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 752c49bbd..7c096556e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -84,7 +84,7 @@ export default defineConfig([ "n/no-unsupported-features/node-builtins": "off", // https://github.com/eclipse-thingweb/node-wot/issues/1430 "n/no-extraneous-import": "off", // https://github.com/eclipse-thingweb/node-wot/issues/1430 "n/no-deprecated-api": "off", // https://github.com/eclipse-thingweb/node-wot/issues/1430 - "n/no-unpublished-import": "off", // https://github.com/eclipse-thingweb/node-wot/issues/1430 + "n/no-unpublished-import": "error", "n/no-process-exit": "off", // https://github.com/eclipse-thingweb/node-wot/issues/1430 "n/hashbang": "warn", diff --git a/package-lock.json b/package-lock.json index a1f7df527..e9afb9754 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11872,6 +11872,8 @@ }, "node_modules/wot-thing-description-types": { "version": "1.1.0-12-March-2025", + "resolved": "https://registry.npmjs.org/wot-thing-description-types/-/wot-thing-description-types-1.1.0-12-March-2025.tgz", + "integrity": "sha512-BRRW4uBATljCPqCbpzNhgY2HFXz8Llh7mAJmjkVimxizOPq8bkmlm0Op/Yv8l7pNQ1GaoBtYwn2BQQjhcQCadA==", "license": "W3C-20150513" }, "node_modules/wot-thing-model-types": { @@ -11880,6 +11882,8 @@ }, "node_modules/wot-typescript-definitions": { "version": "0.8.0-SNAPSHOT.31", + "resolved": "https://registry.npmjs.org/wot-typescript-definitions/-/wot-typescript-definitions-0.8.0-SNAPSHOT.31.tgz", + "integrity": "sha512-BBUWfJnNsSMb60FlUfQN+TWFP2W71LM4g7in83Y+vO6BjGvsyO0KY4jTbazjhLdxxTjbyftrExNkKNwtBIbxfA==", "license": "W3C-20150513", "dependencies": { "wot-thing-description-types": "1.1.0-12-March-2025" @@ -12479,14 +12483,14 @@ "uritemplate": "0.3.4", "url-toolkit": "2.1.6", "uuid": "^7.0.3", - "web-streams-polyfill": "^4.0.0" + "web-streams-polyfill": "^4.0.0", + "wot-thing-description-types": "^1.1.0-12-March-2025" }, "devDependencies": { "@types/content-type": "^1.1.8", "@types/debug": "^4.1.7", "@types/uritemplate": "^0.3.4", - "@types/uuid": "^8.3.1", - "wot-thing-description-types": "^1.1.0-12-March-2025" + "@types/uuid": "^8.3.1" } }, "packages/core/node_modules/ajv-formats": { diff --git a/packages/core/package.json b/packages/core/package.json index 6acad672a..6e8fa3dd3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -18,8 +18,7 @@ "@types/content-type": "^1.1.8", "@types/debug": "^4.1.7", "@types/uritemplate": "^0.3.4", - "@types/uuid": "^8.3.1", - "wot-thing-description-types": "^1.1.0-12-March-2025" + "@types/uuid": "^8.3.1" }, "dependencies": { "@petamoriken/float16": "^3.1.1", @@ -34,7 +33,8 @@ "uritemplate": "0.3.4", "url-toolkit": "2.1.6", "uuid": "^7.0.3", - "web-streams-polyfill": "^4.0.0" + "web-streams-polyfill": "^4.0.0", + "wot-thing-description-types": "^1.1.0-12-March-2025" }, "scripts": { "build": "tsc -b", diff --git a/packages/examples/src/security/oauth/package.json b/packages/examples/src/security/oauth/package.json index ce6c0c28f..654548ec6 100644 --- a/packages/examples/src/security/oauth/package.json +++ b/packages/examples/src/security/oauth/package.json @@ -18,7 +18,9 @@ "cors": "^2.8.5", "ts-node": "10.9.1", "typescript": "4.7.4", - "typescript-standard": "^0.3.36", + "typescript-standard": "^0.3.36" + }, + "dependencies": { "wot-typescript-definitions": "0.8.0-SNAPSHOT.31" } } From ab2e33c734ffe749fa12b43ffdae936ec246f155 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Tue, 7 Oct 2025 17:59:32 +0200 Subject: [PATCH 04/16] refactor: activate eslint typescript-eslint/prefer-nullish-coalescing --- eslint.config.mjs | 2 +- packages/binding-coap/src/coap-server.ts | 4 +--- packages/binding-http/src/http-client-impl.ts | 2 +- packages/binding-http/src/http-server.ts | 4 +--- packages/binding-modbus/src/modbus-client.ts | 8 +++----- packages/binding-opcua/src/opcua-protocol-client.ts | 4 +--- 6 files changed, 8 insertions(+), 16 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 752c49bbd..d32a3e8fa 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -122,7 +122,7 @@ export default defineConfig([ "@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/no-unused-expressions": "off", "@typescript-eslint/no-require-imports": "off", // https://github.com/eclipse-thingweb/node-wot/issues/1430 - "@typescript-eslint/prefer-nullish-coalescing": "off", // https://github.com/eclipse-thingweb/node-wot/issues/1430 + "@typescript-eslint/prefer-nullish-coalescing": "error", "@typescript-eslint/no-empty-object-type": "off", // https://github.com/eclipse-thingweb/node-wot/issues/1430 "@typescript-eslint/no-floating-promises": "off", // https://github.com/eclipse-thingweb/node-wot/issues/1430 diff --git a/packages/binding-coap/src/coap-server.ts b/packages/binding-coap/src/coap-server.ts index bcebb179d..d31acfa72 100644 --- a/packages/binding-coap/src/coap-server.ts +++ b/packages/binding-coap/src/coap-server.ts @@ -201,9 +201,7 @@ export default class CoapServer implements ProtocolServer { return; } - if (thing.forms == null) { - thing.forms = []; - } + thing.forms ??= []; const form = this.createAffordanceForm(base, this.PROPERTY_DIR, offeredMediaType, opValues, thing.uriVariables); diff --git a/packages/binding-http/src/http-client-impl.ts b/packages/binding-http/src/http-client-impl.ts index 3344c4afa..584badd64 100644 --- a/packages/binding-http/src/http-client-impl.ts +++ b/packages/binding-http/src/http-client-impl.ts @@ -356,7 +356,7 @@ export default class HttpClient implements ProtocolClient { const url = HttpClient.fixLocalhostName(form.href); - requestInit.method = form["htv:methodName"] ? form["htv:methodName"] : defaultMethod; + requestInit.method = form["htv:methodName"] ?? defaultMethod; requestInit.headers = requestInit.headers ?? []; requestInit.headers = requestInit.headers as string[][]; diff --git a/packages/binding-http/src/http-server.ts b/packages/binding-http/src/http-server.ts index 83159b2f9..4a46e529e 100644 --- a/packages/binding-http/src/http-server.ts +++ b/packages/binding-http/src/http-server.ts @@ -369,9 +369,7 @@ export default class HttpServer implements ProtocolServer { "writemultipleproperties", ]; } - if (thing.forms == null) { - thing.forms = []; - } + thing.forms ??= []; thing.forms.push(form); this.addUrlRewriteEndpoints(form, thing.forms); } diff --git a/packages/binding-modbus/src/modbus-client.ts b/packages/binding-modbus/src/modbus-client.ts index ad06f03a8..017fbf49f 100644 --- a/packages/binding-modbus/src/modbus-client.ts +++ b/packages/binding-modbus/src/modbus-client.ts @@ -39,11 +39,9 @@ class ModbusSubscription { error?: (error: Error) => void, complete?: () => void ) { - if (!complete) { - complete = () => { - // do nothing. - }; - } + complete ??= () => { + // do nothing. + }; this.interval = global.setInterval(async () => { try { const result = await client.readResource(form); diff --git a/packages/binding-opcua/src/opcua-protocol-client.ts b/packages/binding-opcua/src/opcua-protocol-client.ts index 08bb99278..60afb8745 100644 --- a/packages/binding-opcua/src/opcua-protocol-client.ts +++ b/packages/binding-opcua/src/opcua-protocol-client.ts @@ -257,9 +257,7 @@ export class OPCUAProtocolClient implements ProtocolClient { private async _getNamespaceArray(form: OPCUAForm): Promise { return this._withConnection(form, async (c: OPCUAConnection) => { - if (!c.namespaceArray) { - c.namespaceArray = await readNamespaceArray(c.session); - } + c.namespaceArray ??= await readNamespaceArray(c.session); return c.namespaceArray; }); } From 8089283c6b3364a626f045c3bbc1706396910761 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Tue, 7 Oct 2025 19:29:55 +0200 Subject: [PATCH 05/16] refactor: activate eslint typescript-eslint/no-floating-promises --- eslint.config.mjs | 2 +- packages/core/src/consumed-thing.ts | 2 +- packages/core/src/exposed-thing.ts | 12 ++++++------ packages/core/src/protocol-helpers.ts | 26 ++++++++++++++++++-------- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 6ffc45ab9..31194e93d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -124,7 +124,7 @@ export default defineConfig([ "@typescript-eslint/no-require-imports": "off", // https://github.com/eclipse-thingweb/node-wot/issues/1430 "@typescript-eslint/prefer-nullish-coalescing": "off", // https://github.com/eclipse-thingweb/node-wot/issues/1430 "@typescript-eslint/no-empty-object-type": "off", // https://github.com/eclipse-thingweb/node-wot/issues/1430 - "@typescript-eslint/no-floating-promises": "off", // https://github.com/eclipse-thingweb/node-wot/issues/1430 + "@typescript-eslint/no-floating-promises": "error", // **************** Enforce usage of `const` over `let` wherever possible, to prevent accidental reassignments "prefer-const": "error", diff --git a/packages/core/src/consumed-thing.ts b/packages/core/src/consumed-thing.ts index c340b9ad8..c05c1633a 100644 --- a/packages/core/src/consumed-thing.ts +++ b/packages/core/src/consumed-thing.ts @@ -306,7 +306,7 @@ class InternalEventSubscription extends InternalSubscription { const formWithoutURIvariables = handleUriVariables(this.thing, te, form, options); debug(`ConsumedThing '${this.thing.title}' unsubscribing to ${form.href}`); - this.client.unlinkResource(formWithoutURIvariables); + await this.client.unlinkResource(formWithoutURIvariables); this.active = false; } diff --git a/packages/core/src/exposed-thing.ts b/packages/core/src/exposed-thing.ts index 51401068d..935a090c6 100644 --- a/packages/core/src/exposed-thing.ts +++ b/packages/core/src/exposed-thing.ts @@ -572,11 +572,11 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { * * @experimental */ - public handleUnsubscribeEvent( + public async handleUnsubscribeEvent( name: string, listener: ContentListener, options: WoT.InteractionOptions & { formIndex: number } - ): void { + ): Promise { if (this.events[name] != null) { Helpers.validateInteractionOptions(this, this.events[name], options); @@ -595,7 +595,7 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { } const unsubscribe = this.#eventHandlers.get(name)?.unsubscribe; if (unsubscribe) { - unsubscribe(options); + await unsubscribe(options); } debug(`ExposedThing '${this.title}' unsubscribes from event '${name}'`); } else { @@ -639,11 +639,11 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { } } - public handleUnobserveProperty( + public async handleUnobserveProperty( name: string, listener: ContentListener, options: WoT.InteractionOptions & { formIndex: number } - ): void { + ): Promise { if (this.properties[name] != null) { Helpers.validateInteractionOptions(this, this.properties[name], options); const formIndex = ProtocolHelpers.getFormIndexForOperation( @@ -663,7 +663,7 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { const unobserveHandler = this.#propertyHandlers.get(name)?.unobserveHandler; if (unobserveHandler) { - unobserveHandler(options); + await unobserveHandler(options); } } else { throw new Error(`ExposedThing '${this.title}', no property found for '${name}'`); diff --git a/packages/core/src/protocol-helpers.ts b/packages/core/src/protocol-helpers.ts index 1b568e122..97a90a9a1 100644 --- a/packages/core/src/protocol-helpers.ts +++ b/packages/core/src/protocol-helpers.ts @@ -215,17 +215,27 @@ export default class ProtocolHelpers { const reader = stream.getReader(); const result = new ManagedReadable({ read: (size) => { - reader.read().then((data) => { - result.push(data.value); - if (data.done) { - // signal end - result.push(null); - } - }); + reader + .read() + .then((data) => { + result.push(data.value); + if (data.done) { + // signal end + result.push(null); + } + }) + .catch((error) => { + throw error; + }); }, destroy: (error, callback) => { reader.releaseLock(); - stream.cancel(error).then(() => callback(error)); + stream + .cancel(error) + .then(() => callback(error)) + .catch((error) => { + throw error; + }); }, }); result.wotStream = stream as ReadableStream; From c656b00f67248838531e3968ff0eb8ed7de50a40 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Thu, 9 Oct 2025 12:12:41 +0200 Subject: [PATCH 06/16] fix: issues in core --- packages/core/test/client-test.ts | 65 +++++++++++++++++++++---------- packages/core/test/server-test.ts | 19 ++++++--- 2 files changed, 58 insertions(+), 26 deletions(-) diff --git a/packages/core/test/client-test.ts b/packages/core/test/client-test.ts index b640b8f5e..824b470c0 100644 --- a/packages/core/test/client-test.ts +++ b/packages/core/test/client-test.ts @@ -358,9 +358,14 @@ class WoTClientTest { this.clientFactory = new TrapClientFactory(); this.servient.addClientFactory(this.clientFactory); this.servient.addClientFactory(new TDClientFactory()); - this.servient.start().then((myWoT) => { - this.WoT = myWoT; - }); + this.servient + .start() + .then((myWoT) => { + this.WoT = myWoT; + }) + .catch((error) => { + throw error; + }); debug("started test suite"); } @@ -570,11 +575,17 @@ class WoTClientTest { expect(thing).to.have.property("title").that.equals("aThing"); expect(thing).to.have.property("events").that.has.property("anEvent"); return new Promise((resolve) => { - thing.subscribeEvent("anEvent", async (x) => { - const value = await x.value(); - expect(value).to.equal("triggered"); - resolve(true); - }); + thing + .subscribeEvent("anEvent", async (x) => { + const value = await x.value(); + expect(value).to.equal("triggered"); + resolve(true); + }) + .catch((error) => { + throw error; + }); + }).catch((error) => { + throw error; }); } @@ -626,17 +637,25 @@ class WoTClientTest { expect(thing).to.have.property("title").that.equals("aThing"); expect(thing).to.have.property("events").that.has.property("anEvent"); - const subscription = await thing.subscribeEvent("anEvent", () => { - /** */ - }); + const subscription = await thing + .subscribeEvent("anEvent", () => { + /** */ + }) + .catch((error) => { + throw error; + }); await subscription.stop(); return new Promise((resolve) => { - thing.subscribeEvent("anEvent", async (x) => { - const value = await x.value(); - expect(value).to.equal("triggered"); - resolve(true); - }); + thing + .subscribeEvent("anEvent", async (x) => { + const value = await x.value(); + expect(value).to.equal("triggered"); + resolve(true); + }) + .catch((error) => { + throw error; + }); }); } @@ -649,11 +668,15 @@ class WoTClientTest { expect(thing).to.have.property("title").that.equals("aThing"); expect(thing).to.have.property("properties").that.has.property("aPropertyToObserve"); return new Promise((resolve) => { - thing.observeProperty("aPropertyToObserve", async (data) => { - const value = await data.value(); - expect(value).to.equal(12); - resolve(true); - }); + thing + .observeProperty("aPropertyToObserve", async (data) => { + const value = await data.value(); + expect(value).to.equal(12); + resolve(true); + }) + .catch((error) => { + throw error; + }); }); } diff --git a/packages/core/test/server-test.ts b/packages/core/test/server-test.ts index 8216aa370..225cede49 100644 --- a/packages/core/test/server-test.ts +++ b/packages/core/test/server-test.ts @@ -68,9 +68,14 @@ class WoTServerTest { this.servient = new Servient(); this.server = new TestProtocolServer(); this.servient.addServer(this.server); - this.servient.start().then((WoTruntime) => { - this.WoT = WoTruntime; - }); + this.servient + .start() + .then((WoTruntime) => { + this.WoT = WoTruntime; + }) + .catch((error) => { + throw error; + }); debug("started test suite"); } @@ -889,7 +894,9 @@ class WoTServerTest { thing.setPropertyReadHandler("test", callback); - (thing as ExposedThing).handleObserveProperty("test", protocolListener, { formIndex: 0 }); + (thing as ExposedThing).handleObserveProperty("test", protocolListener, { formIndex: 0 }).catch((error) => { + throw error; + }); await (thing).emitPropertyChange("test"); @@ -964,7 +971,9 @@ class WoTServerTest { thing.setEventSubscribeHandler("test", handler); await (thing).handleSubscribeEvent("test", callback, { formIndex: 0 }); (thing).emitEvent("test", null); - (thing).handleUnsubscribeEvent("test", callback, { formIndex: 0 }); + (thing).handleUnsubscribeEvent("test", callback, { formIndex: 0 }).catch((error) => { + throw error; + }); (thing).emitEvent("test", null); return expect(callback).to.have.been.called.once; From c454306f53792f1b8cfaf7d20049d6c88204e895 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Thu, 9 Oct 2025 14:26:59 +0200 Subject: [PATCH 07/16] refactor: revert changing function signature --- packages/core/src/exposed-thing.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/core/src/exposed-thing.ts b/packages/core/src/exposed-thing.ts index 935a090c6..92e79ae94 100644 --- a/packages/core/src/exposed-thing.ts +++ b/packages/core/src/exposed-thing.ts @@ -572,11 +572,11 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { * * @experimental */ - public async handleUnsubscribeEvent( + public handleUnsubscribeEvent( name: string, listener: ContentListener, options: WoT.InteractionOptions & { formIndex: number } - ): Promise { + ): void { if (this.events[name] != null) { Helpers.validateInteractionOptions(this, this.events[name], options); @@ -595,7 +595,9 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { } const unsubscribe = this.#eventHandlers.get(name)?.unsubscribe; if (unsubscribe) { - await unsubscribe(options); + unsubscribe(options).catch((error) => { + throw error; + }); } debug(`ExposedThing '${this.title}' unsubscribes from event '${name}'`); } else { @@ -639,11 +641,11 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { } } - public async handleUnobserveProperty( + public handleUnobserveProperty( name: string, listener: ContentListener, options: WoT.InteractionOptions & { formIndex: number } - ): Promise { + ): void { if (this.properties[name] != null) { Helpers.validateInteractionOptions(this, this.properties[name], options); const formIndex = ProtocolHelpers.getFormIndexForOperation( @@ -663,7 +665,9 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { const unobserveHandler = this.#propertyHandlers.get(name)?.unobserveHandler; if (unobserveHandler) { - await unobserveHandler(options); + unobserveHandler(options).catch((error) => { + throw error; + }); } } else { throw new Error(`ExposedThing '${this.title}', no property found for '${name}'`); From 5ef9dea85158065d5ad921c501fa729e8a196515 Mon Sep 17 00:00:00 2001 From: Matthias Kovatsch Date: Fri, 31 Oct 2025 13:01:21 +0000 Subject: [PATCH 08/16] docs: revise README Trying to streamline the information given in the README for "new" users (including people who have not used it in a long time). --- README.md | 49 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 6200191f7..fdd1e0b38 100644 --- a/README.md +++ b/README.md @@ -56,13 +56,9 @@ For further information please refer to the official [W3C Web of Things](https:/ ## Installation -The framework can be used in two ways: as a library or as a CLI tool. In this section we will explain how to install the framework in both ways. +The framework is composed by different packages that users can use as they please. The core package is @node-wot/core and it is the only mandatory package to install. The other packages are bindings that allow the framework to communicate with different protocols. -### As a library - -The framework is composed by different packages that users can use as they please. The core package is `@node-wot/core` and it is the only mandatory package to install. The other packages are bindings that allow the framework to communicate with different protocols. - -#### Node.js +### Prerequisite: Node.js with build tools > [!WARNING] > We no longer actively support Node.js version 18 and lower. @@ -81,27 +77,52 @@ Platforms specific prerequisites: - Mac OS: Meet the [node-gyp](https://github.com/nodejs/node-gyp#installation) requirements: - `xcode-select --install` -If you want to use node-wot as a library in your Node.js application, you can use npm to install the node-wot packages that you need. To do so, `cd` inside your application folder, and run: +### As a library + +If you want to use node-wot as a library in your Node.js application, you can use npm to install the node-wot packages that you need. +Todo so, `cd` inside your application folder and install at least the mandatory core package: ``` -npm i @node-wot/core @node-wot/binding-http --save +npm i @node-wot/core ``` -#### Browser +Usually, your application needs at least one protocol binding to commmunicate, e.g.,: -To use node-wot as a browser-side JavaScript Library, the browser needs to support ECMAScript 2015. +``` +npm i @node-wot/binding-http +``` -Using a browser with only ES5 support (e.g., IE 11) might be possible if you add polyfills. If you want to use node-wot as a library in your browser application, you can install the `@node-wot/browser-bundle` as following: +In case the application shall consume Thing Descriptions that are stored locally, you would also need the `file` binding: ``` -npm i @node-wot/browser-bundle --save +npm i @node-wot/binding-file ``` -you can find more installation options in the specific [package README](./packages/browser-bundle/README.md). +You see other available bindings in the [packages folder](./packages), which you can install via `npm i @node-wot/`. + +### In the browser + +To use node-wot as JavaScript library insde the Web browser, it needs to support ECMAScript 2015+. Using a browser with only ES5 support (e.g., IE 11) might be possible if you add polyfills. + +If you want to use node-wot as a library in your browser application,`cd` inside your application folder and install the browser bundle: + +``` +npm i @node-wot/browser-bundle +``` + +You can find more (non-)installation options in the specific [package README](./packages/browser-bundle/README.md). ### As a CLI tool -You can alternatively use node-wot via its command line interface (CLI). Please visit the [CLI tool's Readme](<[url](https://github.com/eclipse-thingweb/node-wot/tree/master/packages/cli)>) to find out more. +You can alternatively use node-wot via its command line interface (CLI). Please visit the [CLI tool README](./packages/cli/README.md) to find out more. + +#### As global tool + +To make the `wot-servient` command available on your machine, install the CLI tool in the global scope: + +``` +npm i @node-wot/cli -g +``` #### As a docker image From 04d17fee9e852cf2e131465f5ac35682bbeb1be4 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Mon, 1 Dec 2025 15:30:13 +0100 Subject: [PATCH 09/16] refactor: remove unused private members --- packages/core/src/consumed-thing.ts | 36 +++++------------------------ 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/packages/core/src/consumed-thing.ts b/packages/core/src/consumed-thing.ts index 1a762633b..7676ddc15 100644 --- a/packages/core/src/consumed-thing.ts +++ b/packages/core/src/consumed-thing.ts @@ -65,44 +65,20 @@ export interface ClientAndForm { } class ConsumedThingProperty extends ThingProperty implements ThingProperty, BaseSchema { - // eslint-disable-next-line no-unused-private-class-members - #name: string; - // eslint-disable-next-line no-unused-private-class-members - #thing: ConsumedThing; - - constructor(name: string, thing: ConsumedThing) { + constructor() { super(); - - this.#name = name; - this.#thing = thing; } } class ConsumedThingAction extends ThingAction implements ThingAction { - // eslint-disable-next-line no-unused-private-class-members - #name: string; - // eslint-disable-next-line no-unused-private-class-members - #thing: ConsumedThing; - - constructor(name: string, thing: ConsumedThing) { + constructor() { super(); - - this.#name = name; - this.#thing = thing; } } class ConsumedThingEvent extends ThingEvent { - // eslint-disable-next-line no-unused-private-class-members - #name: string; - // eslint-disable-next-line no-unused-private-class-members - #thing: ConsumedThing; - - constructor(name: string, thing: ConsumedThing) { + constructor() { super(); - - this.#name = name; - this.#thing = thing; } } @@ -404,15 +380,15 @@ export default class ConsumedThing extends Thing implements IConsumedThing { extendInteractions(): void { for (const [propertyName, property] of Object.entries(this.properties)) { - const newProp = Helpers.extend(property, new ConsumedThingProperty(propertyName, this)); + const newProp = Helpers.extend(property, new ConsumedThingProperty()); this.properties[propertyName] = newProp; } for (const [actionName, action] of Object.entries(this.actions)) { - const newAction = Helpers.extend(action, new ConsumedThingAction(actionName, this)); + const newAction = Helpers.extend(action, new ConsumedThingAction()); this.actions[actionName] = newAction; } for (const [eventName, event] of Object.entries(this.events)) { - const newEvent = Helpers.extend(event, new ConsumedThingEvent(eventName, this)); + const newEvent = Helpers.extend(event, new ConsumedThingEvent()); this.events[eventName] = newEvent; } } From e02fb15e4578a8e731bc7adc55ef17ee8be3d034 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 06:05:24 +0000 Subject: [PATCH 10/16] chore(deps): bump js-yaml Bumps and [js-yaml](https://github.com/nodeca/js-yaml). These dependencies needed to be updated together. Updates `js-yaml` from 4.1.0 to 4.1.1 - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1) Updates `js-yaml` from 3.14.1 to 3.14.2 - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1) --- updated-dependencies: - dependency-name: js-yaml dependency-version: 4.1.1 dependency-type: indirect - dependency-name: js-yaml dependency-version: 3.14.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index e9afb9754..f2f111f1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1533,6 +1533,7 @@ "node_modules/@types/node": { "version": "18.19.100", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -1670,6 +1671,7 @@ "version": "8.44.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.44.1", @@ -1698,6 +1700,7 @@ "version": "8.44.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.44.1", "@typescript-eslint/types": "8.44.1", @@ -2143,6 +2146,7 @@ "node_modules/acorn": { "version": "8.15.0", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3070,6 +3074,7 @@ "version": "4.5.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", @@ -3850,7 +3855,8 @@ "node_modules/devtools-protocol": { "version": "0.0.1367902", "dev": true, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/diff": { "version": "5.2.0", @@ -4126,6 +4132,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -4228,6 +4235,7 @@ "version": "9.36.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4301,6 +4309,7 @@ "version": "10.1.8", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -6488,7 +6497,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -6506,6 +6517,7 @@ "version": "1.4.0", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 10.16.0" } @@ -6554,7 +6566,9 @@ } }, "node_modules/json-schema-ref-parser/node_modules/js-yaml": { - "version": "3.14.1", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -7635,6 +7649,7 @@ "node_modules/node-opcua-address-space": { "version": "2.143.0", "license": "MIT", + "peer": true, "dependencies": { "@types/lodash": "4.17.15", "@types/semver": "^7.5.8", @@ -7773,6 +7788,7 @@ "node_modules/node-opcua-basic-types": { "version": "2.139.0", "license": "MIT", + "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-binary-stream": "2.139.0", @@ -7786,6 +7802,7 @@ "node_modules/node-opcua-binary-stream": { "version": "2.139.0", "license": "MIT", + "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-buffer-utils": "2.139.0" @@ -7793,7 +7810,8 @@ }, "node_modules/node-opcua-buffer-utils": { "version": "2.139.0", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/node-opcua-certificate-manager": { "version": "2.143.0", @@ -7956,6 +7974,7 @@ "node_modules/node-opcua-data-model": { "version": "2.139.0", "license": "MIT", + "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-basic-types": "2.139.0", @@ -7969,6 +7988,7 @@ "node_modules/node-opcua-data-value": { "version": "2.142.0", "license": "MIT", + "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-basic-types": "2.139.0", @@ -7984,6 +8004,7 @@ "node_modules/node-opcua-date-time": { "version": "2.139.0", "license": "MIT", + "peer": true, "dependencies": { "@types/long": "4.0.2", "long": "4.0.0", @@ -7995,6 +8016,7 @@ "node_modules/node-opcua-debug": { "version": "2.139.0", "license": "MIT", + "peer": true, "dependencies": { "chalk": "4.1.2", "hexy": "0.3.5", @@ -8009,6 +8031,7 @@ "node_modules/node-opcua-extension-object": { "version": "2.139.0", "license": "MIT", + "peer": true, "dependencies": { "chalk": "4.1.2", "node-opcua-basic-types": "2.139.0", @@ -8021,6 +8044,7 @@ "node_modules/node-opcua-factory": { "version": "2.139.0", "license": "MIT", + "peer": true, "dependencies": { "chalk": "4.1.2", "node-opcua-assert": "2.139.0", @@ -8081,6 +8105,7 @@ "node_modules/node-opcua-nodeid": { "version": "2.139.0", "license": "MIT", + "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-constants": "2.139.0", @@ -8109,6 +8134,7 @@ "node_modules/node-opcua-numeric-range": { "version": "2.139.0", "license": "MIT", + "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-basic-types": "2.139.0", @@ -8573,6 +8599,7 @@ "node_modules/node-opcua-status-code": { "version": "2.139.0", "license": "MIT", + "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-binary-stream": "2.139.0" @@ -8599,6 +8626,7 @@ "node_modules/node-opcua-types": { "version": "2.143.0", "license": "MIT", + "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-basic-types": "2.139.0", @@ -8626,6 +8654,7 @@ "node_modules/node-opcua-variant": { "version": "2.142.0", "license": "MIT", + "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-basic-types": "2.139.0", @@ -9231,6 +9260,7 @@ "version": "3.3.3", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9809,6 +9839,7 @@ "version": "4.40.2", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.7" }, @@ -10066,6 +10097,7 @@ "node_modules/servie": { "version": "4.3.3", "license": "Apache-2.0", + "peer": true, "dependencies": { "@servie/events": "^1.0.0", "byte-length": "^1.0.2", @@ -11159,7 +11191,9 @@ } }, "node_modules/tslint/node_modules/js-yaml": { - "version": "3.14.1", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -11373,6 +11407,7 @@ "version": "5.8.3", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" From 61228d6c55b77f06ee2cb1c1893a7e752bb9853e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 10 Jan 2026 03:59:28 +0000 Subject: [PATCH 11/16] chore(deps): bump vm2 from 3.9.18 to 3.10.0 Bumps [vm2](https://github.com/patriksimek/vm2) from 3.9.18 to 3.10.0. - [Release notes](https://github.com/patriksimek/vm2/releases) - [Commits](https://github.com/patriksimek/vm2/compare/3.9.18...v3.10.0) --- updated-dependencies: - dependency-name: vm2 dependency-version: 3.10.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package-lock.json | 43 ++++++++------------------------------- packages/cli/package.json | 2 +- 2 files changed, 9 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index f2f111f1f..8a7fce9e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1533,7 +1533,6 @@ "node_modules/@types/node": { "version": "18.19.100", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -1671,7 +1670,6 @@ "version": "8.44.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.44.1", @@ -1700,7 +1698,6 @@ "version": "8.44.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.44.1", "@typescript-eslint/types": "8.44.1", @@ -2146,7 +2143,6 @@ "node_modules/acorn": { "version": "8.15.0", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3074,7 +3070,6 @@ "version": "4.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", @@ -3855,8 +3850,7 @@ "node_modules/devtools-protocol": { "version": "0.0.1367902", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/diff": { "version": "5.2.0", @@ -4132,7 +4126,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -4235,7 +4228,6 @@ "version": "9.36.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4309,7 +4301,6 @@ "version": "10.1.8", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -6517,7 +6508,6 @@ "version": "1.4.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 10.16.0" } @@ -7649,7 +7639,6 @@ "node_modules/node-opcua-address-space": { "version": "2.143.0", "license": "MIT", - "peer": true, "dependencies": { "@types/lodash": "4.17.15", "@types/semver": "^7.5.8", @@ -7788,7 +7777,6 @@ "node_modules/node-opcua-basic-types": { "version": "2.139.0", "license": "MIT", - "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-binary-stream": "2.139.0", @@ -7802,7 +7790,6 @@ "node_modules/node-opcua-binary-stream": { "version": "2.139.0", "license": "MIT", - "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-buffer-utils": "2.139.0" @@ -7810,8 +7797,7 @@ }, "node_modules/node-opcua-buffer-utils": { "version": "2.139.0", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/node-opcua-certificate-manager": { "version": "2.143.0", @@ -7974,7 +7960,6 @@ "node_modules/node-opcua-data-model": { "version": "2.139.0", "license": "MIT", - "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-basic-types": "2.139.0", @@ -7988,7 +7973,6 @@ "node_modules/node-opcua-data-value": { "version": "2.142.0", "license": "MIT", - "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-basic-types": "2.139.0", @@ -8004,7 +7988,6 @@ "node_modules/node-opcua-date-time": { "version": "2.139.0", "license": "MIT", - "peer": true, "dependencies": { "@types/long": "4.0.2", "long": "4.0.0", @@ -8016,7 +7999,6 @@ "node_modules/node-opcua-debug": { "version": "2.139.0", "license": "MIT", - "peer": true, "dependencies": { "chalk": "4.1.2", "hexy": "0.3.5", @@ -8031,7 +8013,6 @@ "node_modules/node-opcua-extension-object": { "version": "2.139.0", "license": "MIT", - "peer": true, "dependencies": { "chalk": "4.1.2", "node-opcua-basic-types": "2.139.0", @@ -8044,7 +8025,6 @@ "node_modules/node-opcua-factory": { "version": "2.139.0", "license": "MIT", - "peer": true, "dependencies": { "chalk": "4.1.2", "node-opcua-assert": "2.139.0", @@ -8105,7 +8085,6 @@ "node_modules/node-opcua-nodeid": { "version": "2.139.0", "license": "MIT", - "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-constants": "2.139.0", @@ -8134,7 +8113,6 @@ "node_modules/node-opcua-numeric-range": { "version": "2.139.0", "license": "MIT", - "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-basic-types": "2.139.0", @@ -8599,7 +8577,6 @@ "node_modules/node-opcua-status-code": { "version": "2.139.0", "license": "MIT", - "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-binary-stream": "2.139.0" @@ -8626,7 +8603,6 @@ "node_modules/node-opcua-types": { "version": "2.143.0", "license": "MIT", - "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-basic-types": "2.139.0", @@ -8654,7 +8630,6 @@ "node_modules/node-opcua-variant": { "version": "2.142.0", "license": "MIT", - "peer": true, "dependencies": { "node-opcua-assert": "2.139.0", "node-opcua-basic-types": "2.139.0", @@ -9260,7 +9235,6 @@ "version": "3.3.3", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9839,7 +9813,6 @@ "version": "4.40.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.7" }, @@ -10097,7 +10070,6 @@ "node_modules/servie": { "version": "4.3.3", "license": "Apache-2.0", - "peer": true, "dependencies": { "@servie/events": "^1.0.0", "byte-length": "^1.0.2", @@ -11407,7 +11379,6 @@ "version": "5.8.3", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11693,11 +11664,13 @@ } }, "node_modules/vm2": { - "version": "3.9.18", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.10.0.tgz", + "integrity": "sha512-3ggF4Bs0cw4M7Rxn19/Cv3nJi04xrgHwt4uLto+zkcZocaKwP/nKP9wPx6ggN2X0DSXxOOIc63BV1jvES19wXQ==", "license": "MIT", "dependencies": { - "acorn": "^8.7.0", - "acorn-walk": "^8.2.0" + "acorn": "^8.14.1", + "acorn-walk": "^8.3.4" }, "bin": { "vm2": "bin/vm2" @@ -12489,7 +12462,7 @@ "commander": "^9.1.0", "dotenv": "^16.4.7", "lodash": "^4.17.21", - "vm2": "3.9.18" + "vm2": "3.10.0" }, "bin": { "wot-servient": "bin/index.js" diff --git a/packages/cli/package.json b/packages/cli/package.json index 53d9047e3..537a55401 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -30,7 +30,7 @@ "commander": "^9.1.0", "dotenv": "^16.4.7", "lodash": "^4.17.21", - "vm2": "3.9.18" + "vm2": "3.10.0" }, "scripts": { "build": "tsc -b", From a213417e75356d0e228782c9e29bb763637b9e83 Mon Sep 17 00:00:00 2001 From: Sterfive's NodeWoT team Date: Sat, 18 Oct 2025 16:29:36 +0200 Subject: [PATCH 12/16] chore: revisit opcua binding examples --- packages/examples/package.json | 13 +- packages/examples/src/bindings/README.md | 15 ++ .../examples/src/bindings/opcua/README.md | 26 +++ .../opcua/demo-opcua-thing-description.ts | 5 +- .../opcua/opcua-coffee-machine-demo.ts | 171 +++++++++++++++--- .../opcua-coffee-machine-thing-description.ts | 157 ++++++++++++++-- 6 files changed, 339 insertions(+), 48 deletions(-) create mode 100644 packages/examples/src/bindings/README.md create mode 100644 packages/examples/src/bindings/opcua/README.md diff --git a/packages/examples/package.json b/packages/examples/package.json index d63ff530a..ee4979e46 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -17,7 +17,18 @@ "build": "tsc -b", "lint": "eslint .", "lint:fix": "eslint . --fix", - "format": "prettier --write \"src/**/*.ts\" \"**/*.json\"" + "format": "prettier --write \"src/**/*.ts\" \"**/*.json\"", + "bindings:coap:server": "node dist/bindings/coap/example-server.js", + "bindings:coap:client": "node dist/bindings/coap/example-client.js", + "bindings:http:server": "node dist/bindings/http/example-server.js", + "bindings:http:server-secure": "node dist/bindings/http/example-server-secure.js", + "bindings:http:client": "node dist/bindings/http/example-client.js", + "bindings:opcua:1": "node dist/bindings/opcua/opcua-demo1.js", + "bindings:opcua:2": "node dist/bindings/opcua/opcua-demo2.js", + "bindings:opcua:coffee-machine": "node dist/bindings/opcua/opcua-coffee-machine-demo.js", + "quickstart:smart-clock": "node dist/quickstart/smart-clock.js", + "quickstart:simple-coffee-machine": "node dist/quickstart/simple-coffee-machine.js", + "quickstart:presence-sensor": "node dist/quickstart/presence-sensor.js" }, "bugs": { "url": "https://github.com/eclipse-thingweb/node-wot/issues" diff --git a/packages/examples/src/bindings/README.md b/packages/examples/src/bindings/README.md new file mode 100644 index 000000000..f877d5ce8 --- /dev/null +++ b/packages/examples/src/bindings/README.md @@ -0,0 +1,15 @@ +## Binding Examples + +This folder contains examples for different binding protocols. + +It demonstrates how to create Things that take their properties, actions, and events from different protocol bindings. + +For each use case a Thing Description is provided that describes the Thing in a protocol-agnostic way. +Then a Servient is created that uses the respective binding protocol to expose the Thing. +A console client is also provided to interact with the Thing. + +Examples are located in + +- `bindings\coap` +- `bindings\http` +- [`bindings\opcua`](./opcua/README.md) diff --git a/packages/examples/src/bindings/opcua/README.md b/packages/examples/src/bindings/opcua/README.md new file mode 100644 index 000000000..ec7d4cb63 --- /dev/null +++ b/packages/examples/src/bindings/opcua/README.md @@ -0,0 +1,26 @@ +## OPCUA + +For inializing an OPCUA client Servient, we need to import the `OPCUAClientFactory` from the `@node-wot/binding-opcua` package. + +```typescript +const servient = new Servient(); +servient.addClientFactory(new OPCUAClientFactory()); +const wot = await servient.start(); +const thing = await wot.consume(thingDescription); +``` + +Then we can interact with the Thing as usual: + +```typescript +// now interact with the things +await thing.invokeAction(...); +await thing.readProperty(...); +await thing.subscribeEvent(...); + +``` + +Finally, we can shutdown the servient: + +```typescript +await servient.shutdown(); +``` diff --git a/packages/examples/src/bindings/opcua/demo-opcua-thing-description.ts b/packages/examples/src/bindings/opcua/demo-opcua-thing-description.ts index bec245db5..ed6721bb5 100644 --- a/packages/examples/src/bindings/opcua/demo-opcua-thing-description.ts +++ b/packages/examples/src/bindings/opcua/demo-opcua-thing-description.ts @@ -25,6 +25,7 @@ export const thingDescription: WoT.ThingDescription = { security: "nosec_sc", title: "servient", description: "node-wot CLI Servient", + base: endpointUrl, properties: { pumpSpeed: { description: "the pump speed", @@ -34,7 +35,7 @@ export const thingDescription: WoT.ThingDescription = { type: "number", forms: [ { - href: endpointUrl + "?id=ns=1;s=PumpSpeed", + href: "?id=ns=1;s=PumpSpeed", op: ["readproperty", "observeproperty"], }, ], @@ -47,7 +48,7 @@ export const thingDescription: WoT.ThingDescription = { type: "number", forms: [ { - href: endpointUrl + "?id=ns=1;s=Temperature", + href: "?id=ns=1;s=Temperature", op: ["readproperty", "observeproperty"], }, ], diff --git a/packages/examples/src/bindings/opcua/opcua-coffee-machine-demo.ts b/packages/examples/src/bindings/opcua/opcua-coffee-machine-demo.ts index 57788c5d0..0c990a799 100644 --- a/packages/examples/src/bindings/opcua/opcua-coffee-machine-demo.ts +++ b/packages/examples/src/bindings/opcua/opcua-coffee-machine-demo.ts @@ -14,7 +14,7 @@ ********************************************************************************/ /* eslint no-console: "off" */ - +import util from "util"; import { Servient } from "@node-wot/core"; import { OPCUAClientFactory } from "@node-wot/binding-opcua"; import { thingDescription } from "./opcua-coffee-machine-thing-description"; @@ -25,37 +25,156 @@ const pause = async (ms: number) => new Promise((resolve) => setTimeout(resolve, servient.addClientFactory(new OPCUAClientFactory()); const wot = await servient.start(); + const thing = await wot.consume(thingDescription); + let lastTemperature = NaN; + let lastWaterTankLevel = NaN; + let lastCoffeeBeanLevel = NaN; + let lastCurrentState = NaN; + let lastGrindingDuration = NaN; + let lastGrinderStatus = NaN; + let lastHeaterStatus = NaN; + let lastPumpStatus = NaN; + let lastValveStatus = NaN; + + const recordedActions: string[] = []; + const recordAction = (actionName: string) => { + recordedActions.push(`${new Date().toISOString()} - ${actionName}`); + }; + process.stdout.write("\x1Bc"); // clear console + process.stdout.write("\x1B[?25l"); // hide cursor + const currentStateEnum = ["Off", "Standby", "Error", "Cleaning", "Serving Coffee", "Under Maintenance"]; + const grinderStates = ["Off", "On", "Jammed", "Malfunctioning"]; + const heaterStates = ["Off", "Heating", "Ready", "Malfunctioning"]; + const pumpStates = ["Off", "On", "Malfunctioning"]; + const valveStates = ["Open", "Opening", "Close", "Closing", "Malfunctioning"]; + + const waitingMachineCoffeeStandByState = async () => { + await pause(1000); + let state = lastCurrentState; + while (state !== 1) { + // Standby + await pause(1000); + state = lastCurrentState; + } + }; + const writeLine = (...args: unknown[]) => { + process.stdout.write(util.format(...args) + " \n"); + }; + const displayOnlineStatus = () => { + process.stdout.write("\x1B[1;1H"); // move cursor to top left + writeLine(`======== Coffee Machine Status ======== ${new Date().toISOString()}`); + writeLine( + ` 🔄 Current State : ${ + isNaN(lastCurrentState) ? "n/a" : (currentStateEnum[lastCurrentState] ?? lastCurrentState) + }` + ); + writeLine( + ` 🔥 Heater Status : ${ + isNaN(lastHeaterStatus) ? "n/a" : (heaterStates[lastHeaterStatus] ?? lastHeaterStatus) + }` + ); + writeLine( + ` 🌡️ Boiler Temperature : ${isNaN(lastTemperature) ? "n/a" : lastTemperature.toFixed(2) + " °C"}` + ); + writeLine( + ` 🚰 Pump Status : ${ + isNaN(lastPumpStatus) ? "n/a" : (pumpStates[lastPumpStatus] ?? lastPumpStatus) + }` + ); + writeLine( + ` 🚪 Valve Status : ${ + isNaN(lastValveStatus) ? "n/a" : (valveStates[lastValveStatus] ?? lastValveStatus) + }` + ); + writeLine( + ` 💧 Water Tank Level : ${isNaN(lastWaterTankLevel) ? "n/a" : lastWaterTankLevel.toFixed(2) + " ml"}` + ); + writeLine( + ` ⚙️ Grinder Status : ${ + isNaN(lastGrinderStatus) ? "n/a" : (grinderStates[lastGrinderStatus] ?? lastGrinderStatus) + }` + ); + writeLine( + ` ⏱️ Grinding Duration : ${ + isNaN(lastGrindingDuration) ? "n/a" : lastGrindingDuration.toFixed(2) + " s" + }` + ); + writeLine( + ` ☕ Coffee Bean Level : ${isNaN(lastCoffeeBeanLevel) ? "n/a" : lastCoffeeBeanLevel.toFixed(2) + " g"}` + ); + writeLine("========================================"); + writeLine("---- Recorded Actions (last 5) ----"); + recordedActions + .slice(-5) + .forEach((action) => writeLine(action + " ")); + writeLine("-----------------------------------"); + }; try { - thing - .observeProperty("waterTankLevel", async (data) => { - const waterTankLevel = await data.value(); - console.log("------------------------------"); - console.log("tankLevel : ", waterTankLevel, "ml"); - console.log("------------------------------"); - }) - .catch((err) => { - console.error("Error observing waterTankLevel property:", err); - }); - thing - .observeProperty("coffeeBeanLevel", async (data) => { - const coffeBeanLevel = await data.value(); - console.log("------------------------------"); - console.log("bean level : ", coffeBeanLevel, "g"); - console.log("------------------------------"); - }) - .catch((err) => { - console.error("Error observing coffeeBeanLevel property:", err); - }); + await thing.observeProperty("waterTankLevel", async (data) => { + lastWaterTankLevel = (await data.value()) as number; + displayOnlineStatus(); + }); + await thing.observeProperty("coffeeBeanLevel", async (data) => { + lastCoffeeBeanLevel = (await data.value()) as number; + displayOnlineStatus(); + }); + await thing.observeProperty("temperature", async (data) => { + lastTemperature = (await data.value()) as number; + displayOnlineStatus(); + }); + await thing.observeProperty("currentState", async (data) => { + lastCurrentState = (await data.value()) as number; + displayOnlineStatus(); + }); + await thing.observeProperty("grinderStatus", async (data) => { + lastGrinderStatus = (await data.value()) as number; + displayOnlineStatus(); + }); + await thing.observeProperty("grindingDuration", async (data) => { + lastGrindingDuration = (await data.value()) as number; + displayOnlineStatus(); + }); + await thing.observeProperty("heaterStatus", async (data) => { + lastHeaterStatus = (await data.value()) as number; + displayOnlineStatus(); + }); + await thing.observeProperty("pumpStatus", async (data) => { + lastPumpStatus = (await data.value()) as number; + displayOnlineStatus(); + }); + await thing.observeProperty("valveStatus", async (data) => { + lastValveStatus = (await data.value()) as number; + displayOnlineStatus(); + }); + + // give some time to gather initial values + await pause(2000); + await waitingMachineCoffeeStandByState(); + recordAction("Machine is ready !"); + + await pause(10000); + + recordAction("Invoking brewCoffee(Mocha) action..."); + await thing.invokeAction("brewCoffee", { RecipeName: "Mocha" }); + await waitingMachineCoffeeStandByState(); + recordAction("Coffee is ready !"); + + await pause(10000); + + recordAction("Invoking brewCoffee(Americano) action..."); + await thing.invokeAction("brewCoffee", { RecipeName: "Americano" }); + await waitingMachineCoffeeStandByState(); + recordAction("Coffee is ready !"); - await thing.invokeAction("brewCoffee", { CoffeeType: 1 }); - await pause(5000); - await thing.invokeAction("brewCoffee", { CoffeeType: 0 }); - await pause(5000); + await pause(10000); + recordAction("Invoking fillTank action..."); await thing.invokeAction("fillTank"); - await pause(5000); + await waitingMachineCoffeeStandByState(); + recordAction("Tank is refilled !"); + recordAction("Done !"); } finally { await servient.shutdown(); } diff --git a/packages/examples/src/bindings/opcua/opcua-coffee-machine-thing-description.ts b/packages/examples/src/bindings/opcua/opcua-coffee-machine-thing-description.ts index 099a92566..b21132c69 100644 --- a/packages/examples/src/bindings/opcua/opcua-coffee-machine-thing-description.ts +++ b/packages/examples/src/bindings/opcua/opcua-coffee-machine-thing-description.ts @@ -14,9 +14,21 @@ ********************************************************************************/ const endpointUrl = "opc.tcp://opcuademo.sterfive.com:26543"; +const coffeeMachine = "1:CoffeeMachineA"; + export const thingDescription: WoT.ThingDescription = { - "@context": "https://www.w3.org/2019/wot/td/v1", + "@context": [ + "https://www.w3.org/2019/wot/td/v1", + { + uav: "http://opcfoundation.org/UA/WoT-Binding/", + "1": "http://example.namespace.com/demo/pump", + "2": "http://opcfoundation.org/UA/DI/", + "7": "http://opcfoundation.org/UA/CommercialKitchenEquipment/", + "17": "http://sterfive.com/UA/CoffeeMachine/", + }, + ], "@type": ["Thing"], + base: endpointUrl, securityDefinitions: { nosec_sc: { scheme: "nosec", @@ -27,12 +39,11 @@ export const thingDescription: WoT.ThingDescription = { description: "node-wot CLI Servient", properties: { deviceHealth: { - // type: "number", observable: true, readOnly: true, forms: [ { - href: endpointUrl, + href: "/", op: ["readproperty", "observeproperty"], "opcua:nodeId": { root: "i=84", @@ -42,34 +53,139 @@ export const thingDescription: WoT.ThingDescription = { ], }, waterTankLevel: { - // type: "number", observable: true, readOnly: true, forms: [ { - href: endpointUrl, + href: "/", op: ["readproperty", "observeproperty"], "opcua:nodeId": { root: "i=84", - path: "/Objects/2:DeviceSet/1:CoffeeMachine/2:ParameterSet/9:WaterTankLevel", + path: `/Objects/2:DeviceSet/${coffeeMachine}/7:Parameters/17:WaterTankLevel`, }, }, ], + type: "number", }, coffeeBeanLevel: { - // type: "number", observable: true, readOnly: true, forms: [ { - href: endpointUrl, + href: "/", + op: ["readproperty", "observeproperty"], + "opcua:nodeId": { + root: "i=84", + path: `/Objects/2:DeviceSet/${coffeeMachine}/7:Parameters/17:CoffeeBeanLevel`, + }, + }, + ], + type: "number", + }, + temperature: { + observable: true, + readOnly: true, + forms: [ + { + href: "/", + op: ["readproperty", "observeproperty"], + "opcua:nodeId": { + root: "i=84", + path: `/Objects/2:DeviceSet/${coffeeMachine}/7:Parameters/7:BoilerTempWater`, + }, + }, + ], + type: "number", + }, + currentState: { + observable: true, + readOnly: true, + forms: [ + { + href: "/", + op: ["readproperty", "observeproperty"], + "opcua:nodeId": { + root: "i=84", + path: `/Objects/2:DeviceSet/${coffeeMachine}/7:Parameters/7:CurrentState`, + }, + }, + ], + type: "number", + }, + grinderStatus: { + observable: true, + readOnly: true, + forms: [ + { + href: "/", + op: ["readproperty", "observeproperty"], + "opcua:nodeId": { + root: "i=84", + path: `/Objects/2:DeviceSet/${coffeeMachine}/7:Parameters/17:GrinderStatus`, + }, + }, + ], + type: "number", + }, + heaterStatus: { + observable: true, + readOnly: true, + forms: [ + { + href: "/", op: ["readproperty", "observeproperty"], "opcua:nodeId": { root: "i=84", - path: "/Objects/2:DeviceSet/1:CoffeeMachine/2:ParameterSet/9:CoffeeBeanLevel", + path: `/Objects/2:DeviceSet/${coffeeMachine}/7:Parameters/17:HeaterStatus`, }, }, ], + type: "number", + }, + pumpStatus: { + observable: true, + readOnly: true, + forms: [ + { + href: "/", + op: ["readproperty", "observeproperty"], + "opcua:nodeId": { + root: "i=84", + path: `/Objects/2:DeviceSet/${coffeeMachine}/7:Parameters/17:PumpStatus`, + }, + }, + ], + type: "number", + }, + valveStatus: { + observable: true, + readOnly: true, + forms: [ + { + href: "/", + op: ["readproperty", "observeproperty"], + "opcua:nodeId": { + root: "i=84", + path: `/Objects/2:DeviceSet/${coffeeMachine}/7:Parameters/17:ValveStatus`, + }, + }, + ], + type: "number", + }, + grindingDuration: { + observable: true, + readOnly: true, + forms: [ + { + href: "/", + op: ["readproperty", "observeproperty"], + "opcua:nodeId": { + root: "i=84", + path: `/Objects/2:DeviceSet/${coffeeMachine}/7:Parameters/17:GrindingDuration`, + }, + }, + ], + type: "number", }, }, actions: { @@ -77,33 +193,36 @@ export const thingDescription: WoT.ThingDescription = { forms: [ { type: "object", - href: endpointUrl, + href: "/", op: ["invokeaction"], - "opcua:nodeId": { root: "i=84", path: "/Objects/2:DeviceSet/1:CoffeeMachine" }, - "opcua:method": { root: "i=84", path: "/Objects/2:DeviceSet/1:CoffeeMachine/2:MethodSet/9:Start" }, + "opcua:nodeId": { root: "i=84", path: `/Objects/2:DeviceSet/${coffeeMachine}` }, + "opcua:method": { + root: "i=84", + path: `/Objects/2:DeviceSet/${coffeeMachine}/2:MethodSet/17:MakeCoffee`, + }, }, ], input: { type: "object", properties: { - CoffeeType: { - title: "1 for Americano, 2 for Expressp", - type: "number", + RecipeName: { + title: "Americano or Espresso or Mocha (see available Recipes in OPCUA server)", + type: "string", }, }, - required: ["CoffeeType"], + required: ["RecipeName"], }, }, fillTank: { forms: [ { type: "object", - href: endpointUrl, + href: "/", op: ["invokeaction"], - "opcua:nodeId": { root: "i=84", path: "/Objects/2:DeviceSet/1:CoffeeMachine" }, + "opcua:nodeId": { root: "i=84", path: `/Objects/2:DeviceSet/${coffeeMachine}` }, "opcua:method": { root: "i=84", - path: "/Objects/2:DeviceSet/1:CoffeeMachine/2:MethodSet/9:FillTank", + path: `/Objects/2:DeviceSet/${coffeeMachine}/2:MethodSet/17:FillTank`, }, }, ], From 3bcd2e91a03d33fad4a9b947cabf1140ba0edbd9 Mon Sep 17 00:00:00 2001 From: Cristiano Aguzzi Date: Thu, 22 Jan 2026 10:13:59 +0100 Subject: [PATCH 13/16] docs(README): fix link to cli README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fdd1e0b38..c30da7439 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ You can find more (non-)installation options in the specific [package README](./ ### As a CLI tool -You can alternatively use node-wot via its command line interface (CLI). Please visit the [CLI tool README](./packages/cli/README.md) to find out more. +You can alternatively use node-wot via its command line interface (CLI). Please visit the [CLI tool README](https://github.com/eclipse-thingweb/node-wot/tree/master/packages/cli) to find out more. #### As global tool From 5e5f9c7231c97e9c2c7e76621e42dc6e8ab4353c Mon Sep 17 00:00:00 2001 From: Cristiano Aguzzi Date: Thu, 22 Jan 2026 11:40:23 +0100 Subject: [PATCH 14/16] Support ID-based HTTP paths for exposed Things (#1464) * feat(binding-http/http-server): generate id based http paths fix #1458 * chore: remove files from another branch --- packages/binding-http/src/http-server.ts | 60 ++++++++++++------- packages/binding-http/src/http.ts | 1 + .../binding-http/test/http-server-test.ts | 49 +++++++++++++++ 3 files changed, 89 insertions(+), 21 deletions(-) diff --git a/packages/binding-http/src/http-server.ts b/packages/binding-http/src/http-server.ts index 4a46e529e..4f4f7dd62 100644 --- a/packages/binding-http/src/http-server.ts +++ b/packages/binding-http/src/http-server.ts @@ -64,6 +64,7 @@ export default class HttpServer implements ProtocolServer { private readonly address?: string; private readonly baseUri?: string; private readonly urlRewrite?: Record; + private readonly devFriendlyUri: boolean; private readonly supportedSecuritySchemes: string[] = ["nosec"]; private readonly validOAuthClients: RegExp = /.*/g; private readonly server: http.Server | https.Server; @@ -83,6 +84,7 @@ export default class HttpServer implements ProtocolServer { this.baseUri = config.baseUri; this.urlRewrite = config.urlRewrite; this.middleware = config.middleware; + this.devFriendlyUri = config.devFriendlyUri ?? true; const router = Router({ ignoreTrailingSlash: true, @@ -267,21 +269,32 @@ export default class HttpServer implements ProtocolServer { } public async expose(thing: ExposedThing, tdTemplate: WoT.ExposedThingInit = {}): Promise { - let urlPath = slugify(thing.title, { lower: true }); - - // avoid URL clashes - if (this.things.has(urlPath)) { - let uniqueUrlPath; - let nameClashCnt = 2; - do { - uniqueUrlPath = urlPath + "_" + nameClashCnt++; - } while (this.things.has(uniqueUrlPath)); - urlPath = uniqueUrlPath; - } - if (this.getPort() !== -1) { - debug(`HttpServer on port ${this.getPort()} exposes '${thing.title}' as unique '/${urlPath}'`); - this.things.set(urlPath, thing); + const paths: string[] = []; + // If not id is given we create the path using the title even if devFriendlyUri is false. + // in Thing Description 1.1 id is optional + if (this.devFriendlyUri || thing.id == null) { + let urlPath = slugify(thing.title, { lower: true }); + + // avoid URL clashes + if (this.things.has(urlPath)) { + let uniqueUrlPath; + let nameClashCnt = 2; + do { + uniqueUrlPath = urlPath + "_" + nameClashCnt++; + } while (this.things.has(uniqueUrlPath)); + urlPath = uniqueUrlPath; + } + this.things.set(urlPath, thing); + paths.push(urlPath); + debug("HttpServer on port %d exposes %s as unique '/%s'", this.getPort(), thing.name, urlPath); + } + + if (thing.id != null) { + this.things.set(thing.id, thing); + paths.push(thing.id); + debug("HttpServer on port %d exposes %s as unique '/%s'", this.getPort(), thing.name, thing.id); + } if (this.scheme === "http" && Object.keys(thing.securityDefinitions).length !== 0) { warn(`HTTP Server will attempt to use your security schemes even if you are not using HTTPS.`); @@ -290,16 +303,20 @@ export default class HttpServer implements ProtocolServer { this.fillSecurityScheme(thing); if (this.baseUri !== undefined) { - const base: string = this.baseUri.concat("/", encodeURIComponent(urlPath)); - info("HttpServer TD hrefs using baseUri " + this.baseUri); - this.addEndpoint(thing, tdTemplate, base); + for (const path of paths) { + info("HttpServer TD hrefs using baseUri %s and path %s", this.baseUri, path); + const base: string = this.baseUri.concat("/", encodeURIComponent(path)); + this.addEndpoint(thing, tdTemplate, base); + } } else { // fill in binding data for (const address of Helpers.getAddresses()) { - const base: string = - this.scheme + "://" + address + ":" + this.getPort() + "/" + encodeURIComponent(urlPath); - - this.addEndpoint(thing, tdTemplate, base); + for (const path of paths) { + const base: string = + this.scheme + "://" + address + ":" + this.getPort() + "/" + encodeURIComponent(path); + info("HttpServer TD hrefs using address %s and path %s", address, path); + this.addEndpoint(thing, tdTemplate, base); + } } } } @@ -311,6 +328,7 @@ export default class HttpServer implements ProtocolServer { for (const [name, thing] of this.things.entries()) { if (thing.id === thingId) { this.things.delete(name); + this.things.delete(thingId); info(`HttpServer successfully destroyed '${thing.title}'`); return true; diff --git a/packages/binding-http/src/http.ts b/packages/binding-http/src/http.ts index 0dae9fdd5..5d4b3e839 100644 --- a/packages/binding-http/src/http.ts +++ b/packages/binding-http/src/http.ts @@ -45,6 +45,7 @@ export interface HttpConfig { serverKey?: string; serverCert?: string; security?: SecurityScheme[]; + devFriendlyUri?: boolean; middleware?: MiddlewareRequestHandler; } diff --git a/packages/binding-http/test/http-server-test.ts b/packages/binding-http/test/http-server-test.ts index b03fafe1a..509fc507f 100644 --- a/packages/binding-http/test/http-server-test.ts +++ b/packages/binding-http/test/http-server-test.ts @@ -1020,4 +1020,53 @@ class HttpServerTest { return httpServer.stop(); } + + @test async "should expose Thing with id and title and be reachable from both"() { + const httpServer = new HttpServer({ port: 0 }); + + await httpServer.start(new Servient()); + + const testThing = new ExposedThing(new Servient(), { + title: "TestThing", + id: "urn:dev:wot:test-thing-1234", + properties: { + test: { + type: "string", + forms: [], + }, + }, + actions: { + test: { + output: { type: "string" }, + forms: [], + }, + }, + }); + + await httpServer.expose(testThing); + + const uriByTitle = `http://localhost:${httpServer.getPort()}/testthing`; + const uriById = `http://localhost:${httpServer.getPort()}/urn:dev:wot:test-thing-1234`; + + let resp; + resp = await (await fetch(uriByTitle)).json(); + expect(resp.title).to.be.eq("TestThing"); + expect(resp.properties.test.forms.some((form: { href: string }) => form.href.includes("testthing"))).to.be.true; + expect(resp.actions.test.forms.some((form: { href: string }) => form.href.includes("testthing"))).to.be.true; + + resp = await (await fetch(uriById)).json(); + expect(resp.id).to.be.eq("urn:dev:wot:test-thing-1234"); + expect( + resp.properties.test.forms.some((form: { href: string }) => + form.href.includes(encodeURIComponent("urn:dev:wot:test-thing-1234")) + ) + ).to.be.true; + expect( + resp.actions.test.forms.some((form: { href: string }) => + form.href.includes(encodeURIComponent("urn:dev:wot:test-thing-1234")) + ) + ).to.be.true; + + return httpServer.stop(); + } } From 2e8f3b166693e01167b36d3a8d51da655fee91ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 10:51:52 +0000 Subject: [PATCH 15/16] chore(deps): bump lodash from 4.17.21 to 4.17.23 Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23) --- updated-dependencies: - dependency-name: lodash dependency-version: 4.17.23 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package-lock.json | 18 ++++++++++++++++-- packages/cli/package.json | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8a7fce9e5..292c5f939 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6852,7 +6852,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lodash.camelcase": { @@ -7725,6 +7727,12 @@ "node-opcua-variant": "2.142.0" } }, + "node_modules/node-opcua-address-space/node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/node-opcua-aggregates": { "version": "2.143.0", "license": "MIT", @@ -8431,6 +8439,12 @@ "thenify-ex": "4.4.0" } }, + "node_modules/node-opcua-server/node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/node-opcua-service-browse": { "version": "2.143.0", "license": "MIT", @@ -12461,7 +12475,7 @@ "ajv": "^8.11.0", "commander": "^9.1.0", "dotenv": "^16.4.7", - "lodash": "^4.17.21", + "lodash": "^4.17.23", "vm2": "3.10.0" }, "bin": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 537a55401..2fe74df3f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -29,7 +29,7 @@ "ajv": "^8.11.0", "commander": "^9.1.0", "dotenv": "^16.4.7", - "lodash": "^4.17.21", + "lodash": "^4.17.23", "vm2": "3.10.0" }, "scripts": { From a27587bdf09ef784cb06cab05f6d6d08efcd3032 Mon Sep 17 00:00:00 2001 From: reluc Date: Thu, 22 Jan 2026 12:47:56 +0100 Subject: [PATCH 16/16] test(core): revert promise catches --- packages/core/test/server-test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/test/server-test.ts b/packages/core/test/server-test.ts index 225cede49..619f54a26 100644 --- a/packages/core/test/server-test.ts +++ b/packages/core/test/server-test.ts @@ -971,9 +971,7 @@ class WoTServerTest { thing.setEventSubscribeHandler("test", handler); await (thing).handleSubscribeEvent("test", callback, { formIndex: 0 }); (thing).emitEvent("test", null); - (thing).handleUnsubscribeEvent("test", callback, { formIndex: 0 }).catch((error) => { - throw error; - }); + (thing).handleUnsubscribeEvent("test", callback, { formIndex: 0 }); (thing).emitEvent("test", null); return expect(callback).to.have.been.called.once;