diff --git a/README.md b/README.md index f86a4b3bc..bf8d9935f 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ > A fast and extensible framework to connect any device with your application [![Default CI Pipeline](https://github.com/eclipse-thingweb/node-wot/actions/workflows/ci.yaml/badge.svg)](https://github.com/eclipse-thingweb/node-wot/actions/workflows/ci.yaml) -[npm](https://npm-stat.com/charts.html?package=%40node-wot%2Ftd-tools) +[npm](https://npm-stat.com/charts.html?package=%40node-wot%2Fcore) [![codecov](https://codecov.io/gh/eclipse-thingweb/node-wot/branch/master/graph/badge.svg)](https://codecov.io/gh/eclipse-thingweb/node-wot) [![Telegram Group](https://img.shields.io/endpoint?color=neon&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fnodewot)](https://t.me/nodewot) [![Discord](https://img.shields.io/badge/Discord-7289DA?logo=discord&logoColor=white&label=node-wot)](https://discord.gg/JXY2Jzefz3) @@ -59,7 +59,7 @@ The framework can be used in two ways: as a library or as a CLI tool. In this se ### 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 and optionally we offer a set of tools in the `@node-wot/td-tools` package. +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 diff --git a/package-lock.json b/package-lock.json index 357ddf67f..a8192a1e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -924,6 +924,45 @@ "testdeck-watch": "bin/watch" } }, + "node_modules/@thingweb/thing-model": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@thingweb/thing-model/-/thing-model-1.0.3.tgz", + "integrity": "sha512-KAm2ux3A7B2lq0LkQw1Eze4g1w1c1zvhlaCA/TQFtX5LQoMsm6uqMuW6X+PcOzSsyKIekxYQjcgwC4rFZpfDbg==", + "dependencies": { + "ajv": "^8.11.0", + "ajv-formats": "^3.0.1", + "debug": "^4.3.4", + "json-placeholder-replacer": "^2.0.4", + "wot-thing-description-types": "1.1.0-09-November-2023", + "wot-thing-model-types": "^1.1.0-09-November-2023", + "wot-typescript-definitions": "0.8.0-SNAPSHOT.29" + } + }, + "node_modules/@thingweb/thing-model/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@thingweb/thing-model/node_modules/json-placeholder-replacer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/json-placeholder-replacer/-/json-placeholder-replacer-2.0.5.tgz", + "integrity": "sha512-pK/MgeylpZ1VAMO7s/tp5H6VVsbCIc9i+3cUILYnLTw2MT7xqKLowBQGQmFTtcz/O1414oK+f9C8jT79IADdag==", + "bin": { + "jpr": "dist/index.js", + "json-placeholder-replacer": "dist/index.js" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -13104,7 +13143,6 @@ "license": "EPL-2.0 OR W3C-20150513", "dependencies": { "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "@types/node": "16.18.35", "coap": "^1.4.0", "multicast-dns": "^7.2.5", @@ -13120,9 +13158,6 @@ "license": "EPL-2.0 OR W3C-20150513", "dependencies": { "@node-wot/core": "0.8.15" - }, - "devDependencies": { - "@node-wot/td-tools": "0.8.15" } }, "packages/binding-http": { @@ -13131,7 +13166,6 @@ "license": "EPL-2.0 OR W3C-20150513", "dependencies": { "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "@types/eventsource": "1.1.10", "accept-language-parser": "1.5.0", "basic-auth": "2.0.1", @@ -13160,7 +13194,6 @@ "license": "EPL-2.0 OR W3C-20150513", "dependencies": { "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "node-mbus": "^2.2.4", "wot-typescript-definitions": "0.8.0-SNAPSHOT.29" } @@ -13171,7 +13204,6 @@ "license": "EPL-2.0 OR W3C-20150513", "dependencies": { "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "modbus-serial": "^8.0.17", "rxjs": "5.5.11", "wot-typescript-definitions": "0.8.0-SNAPSHOT.29" @@ -13183,7 +13215,6 @@ "license": "EPL-2.0 OR W3C-20150513", "dependencies": { "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "aedes": "^0.46.2", "mqtt": "^5.3.2", "rxjs": "5.5.11" @@ -13195,7 +13226,6 @@ "license": "EPL-2.0 OR W3C-20150513", "dependencies": { "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "@types/node-netconf": "npm:@types/netconf@^2.0.0", "@types/url-parse": "^1.4.3", "case-1.5.3": "npm:case@^1.5.3", @@ -13209,7 +13239,6 @@ "license": "EPL-2.0 OR W3C-20150513", "dependencies": { "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "node-opcua": "2.113.0", "node-opcua-address-space": "2.113.0", "node-opcua-basic-types": "2.113.0", @@ -13246,7 +13275,6 @@ "dependencies": { "@node-wot/binding-http": "0.8.15", "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "slugify": "^1.4.5", "ws": "^7.5.10" } @@ -13264,7 +13292,6 @@ "@node-wot/binding-http": "0.8.15", "@node-wot/binding-websockets": "0.8.15", "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "browserify": "^17.0.0", "readable-stream4": "npm:readable-stream@^4.0.0" } @@ -13280,7 +13307,7 @@ "@node-wot/binding-mqtt": "0.8.15", "@node-wot/binding-websockets": "0.8.15", "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", + "@thingweb/thing-model": "^1.0.1", "@types/lodash": "^4.14.199", "ajv": "^8.11.0", "commander": "^9.1.0", @@ -13354,8 +13381,8 @@ "version": "0.8.15", "license": "EPL-2.0 OR W3C-20150513", "dependencies": { - "@node-wot/td-tools": "0.8.15", "@petamoriken/float16": "^3.1.1", + "@thingweb/thing-model": "^1.0.3", "ajv": "^8.11.0", "ajv-formats": "^2.1.1", "cbor": "^8.1.0", @@ -13383,7 +13410,6 @@ "@node-wot/binding-mqtt": "0.8.15", "@node-wot/binding-opcua": "0.8.15", "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "rxjs": "5.5.11" } }, diff --git a/packages/binding-coap/package.json b/packages/binding-coap/package.json index 48ba4ee9b..391381ea0 100644 --- a/packages/binding-coap/package.json +++ b/packages/binding-coap/package.json @@ -15,7 +15,6 @@ "types": "dist/coap.d.ts", "dependencies": { "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "@types/node": "16.18.35", "coap": "^1.4.0", "multicast-dns": "^7.2.5", diff --git a/packages/binding-coap/src/coap-client.ts b/packages/binding-coap/src/coap-client.ts index 9cddc9033..0fd3f0cf3 100644 --- a/packages/binding-coap/src/coap-client.ts +++ b/packages/binding-coap/src/coap-client.ts @@ -22,10 +22,7 @@ import * as net from "net"; import { Subscription } from "rxjs/Subscription"; -// for Security definition -import * as TD from "@node-wot/td-tools"; - -import { ProtocolClient, Content, ContentSerdes, createLoggers } from "@node-wot/core"; +import { ProtocolClient, Content, ContentSerdes, SecurityScheme, createLoggers } from "@node-wot/core"; import { BlockSize, blockSizeToOptionValue, CoapForm, CoapMethodName } from "./coap"; import CoapServer from "./coap-server"; import { Readable } from "stream"; @@ -209,7 +206,7 @@ export default class CoapClient implements ProtocolClient { this.agent.close(); } - public setSecurity = (metadata: Array): boolean => true; + public setSecurity = (metadata: Array): boolean => true; private uriToOptions(uri: string): CoapRequestParams { // eslint-disable-next-line n/no-deprecated-api diff --git a/packages/binding-coap/src/coap-server.ts b/packages/binding-coap/src/coap-server.ts index 6340f9265..b94d91a9f 100644 --- a/packages/binding-coap/src/coap-server.ts +++ b/packages/binding-coap/src/coap-server.ts @@ -17,7 +17,6 @@ * CoAP Server based on coap by mcollina */ -import * as TD from "@node-wot/td-tools"; import Servient, { ProtocolServer, ContentSerdes, @@ -26,6 +25,7 @@ import Servient, { ProtocolHelpers, Content, createLoggers, + Form, } from "@node-wot/core"; import { Socket } from "dgram"; import { Server, createServer, registerFormat, IncomingMessage, OutgoingMessage } from "coap"; @@ -231,7 +231,7 @@ export default class CoapServer implements ProtocolServer { return opValues; } - private addFormToAffordance(form: TD.Form, affordance: AffordanceElement): void { + private addFormToAffordance(form: Form, affordance: AffordanceElement): void { const affordanceForms = affordance.forms; if (affordanceForms == null) { affordance.forms = [form]; @@ -317,7 +317,7 @@ export default class CoapServer implements ProtocolServer { affordanceName?: string, affordanceUriVariables?: PropertyElement["uriVariables"], subprotocol?: string - ): TD.Form { + ): Form { const affordanceNamePattern = Helpers.updateInteractionNameWithUriVariablePattern( affordanceName ?? "", affordanceUriVariables, @@ -330,14 +330,14 @@ export default class CoapServer implements ProtocolServer { href += `/${encodeURIComponent(affordanceNamePattern)}`; } - const form = new TD.Form(href, offeredMediaType); + const form = new Form(href, offeredMediaType); form.op = opValues; form.subprotocol = subprotocol; return form; } - private logHrefAssignment(form: TD.Form, affordanceType: string, affordanceName: string) { + private logHrefAssignment(form: Form, affordanceType: string, affordanceName: string) { debug(`CoapServer on port ${this.port} assigns '${form.href}' to ${affordanceType} '${affordanceName}'`); } @@ -610,7 +610,7 @@ export default class CoapServer implements ProtocolServer { } private async handleReadMultipleProperties( - forms: TD.Form[], + forms: Form[], req: IncomingMessage, contentType: string, thing: ExposedThing, @@ -817,7 +817,7 @@ export default class CoapServer implements ProtocolServer { } private createInteractionOptions( - forms: TD.Form[], + forms: Form[], thing: ExposedThing, req: IncomingMessage, contentType: string, diff --git a/packages/binding-coap/src/coap.ts b/packages/binding-coap/src/coap.ts index 7c96505db..4273185ae 100644 --- a/packages/binding-coap/src/coap.ts +++ b/packages/binding-coap/src/coap.ts @@ -13,7 +13,7 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import { Form } from "@node-wot/td-tools"; +import { Form } from "@node-wot/core"; export { default as CoapServer } from "./coap-server"; export { default as CoapClientFactory } from "./coap-client-factory"; diff --git a/packages/binding-coap/src/coaps-client.ts b/packages/binding-coap/src/coaps-client.ts index 6379f6a9c..0120e831f 100644 --- a/packages/binding-coap/src/coaps-client.ts +++ b/packages/binding-coap/src/coaps-client.ts @@ -17,11 +17,9 @@ * CoAPS client based on node-coap-client by AlCalzone */ -import * as TD from "@node-wot/td-tools"; - import { Subscription } from "rxjs/Subscription"; -import { ProtocolClient, Content, createLoggers, ContentSerdes } from "@node-wot/core"; +import { ProtocolClient, Content, createLoggers, ContentSerdes, SecurityScheme } from "@node-wot/core"; import { CoapForm, CoapMethodName, isValidCoapMethod, isSupportedCoapMethod } from "./coap"; import { CoapClient as coaps, CoapResponse, RequestMethod, SecurityParameters } from "node-coap-client"; import { Readable } from "stream"; @@ -164,13 +162,13 @@ export default class CoapsClient implements ProtocolClient { // FIXME coap does not provide proper API to close Agent } - public setSecurity(metadata: Array, credentials?: pskSecurityParameters): boolean { + public setSecurity(metadata: Array, credentials?: pskSecurityParameters): boolean { if (metadata === undefined || !Array.isArray(metadata) || metadata.length === 0) { warn(`CoapsClient received empty security metadata`); return false; } - const security: TD.SecurityScheme = metadata[0]; + const security: SecurityScheme = metadata[0]; if (security.scheme === "psk" && credentials != null) { this.authorization = { psk: {} }; diff --git a/packages/binding-coap/test/coap-server-test.ts b/packages/binding-coap/test/coap-server-test.ts index ec9b76743..dc91d4da7 100644 --- a/packages/binding-coap/test/coap-server-test.ts +++ b/packages/binding-coap/test/coap-server-test.ts @@ -1,4 +1,3 @@ -import Servient, { ExposedThing, Content } from "@node-wot/core"; /******************************************************************************** * Copyright (c) 2018 Contributors to the Eclipse Foundation * @@ -18,10 +17,10 @@ import Servient, { ExposedThing, Content } from "@node-wot/core"; * Protocol test suite to test protocol implementations */ +import Servient, { ExposedThing, Content, Form } from "@node-wot/core"; import { suite, test } from "@testdeck/mocha"; import { expect, should } from "chai"; import { DataSchemaValue, InteractionInput, InteractionOptions, ThingDescription } from "wot-typescript-definitions"; -import * as TD from "@node-wot/td-tools"; import CoapServer from "../src/coap-server"; import { CoapClient } from "../src/coap"; import { Readable } from "stream"; @@ -69,7 +68,7 @@ class CoapServerTest { const uri = `coap://localhost:${coapServer.getPort()}/test/`; const coapClient = new CoapClient(coapServer); - const resp = await coapClient.readResource(new TD.Form(uri + "properties/test")); + const resp = await coapClient.readResource(new Form(uri + "properties/test")); expect((await resp.toBuffer()).toString()).to.equal('"testValue"'); await coapServer.stop(); @@ -104,10 +103,10 @@ class CoapServerTest { const coapClient = new CoapClient(coapServer); await coapClient.writeResource( - new TD.Form(uri + "properties/test"), + new Form(uri + "properties/test"), new Content("text/plain", Readable.from(Buffer.from("testValue1", "utf-8"))) ); - const resp = await coapClient.readResource(new TD.Form(uri + "properties/test")); + const resp = await coapClient.readResource(new Form(uri + "properties/test")); const data = (await resp.toBuffer()).toString(); expect(data).to.equal('"testValue1"'); @@ -141,7 +140,7 @@ class CoapServerTest { const coapClient = new CoapClient(coapServer); const resp = await coapClient.invokeResource( - new TD.Form(uri + "actions/try"), + new Form(uri + "actions/try"), new Content("text/plain", Readable.from(Buffer.from("testValue1", "utf-8"))) ); expect((await resp.toBuffer()).toString()).to.equal('"TEST"'); @@ -173,7 +172,7 @@ class CoapServerTest { const uri = `coap://localhost:${coapServer.getPort()}/test/`; const coapClient = new CoapClient(coapServer); - const form = new TD.Form(uri + "events/eventTest"); + const form = new Form(uri + "events/eventTest"); const subscription = await coapClient.subscribeResource(form, (value) => { /** */ }); @@ -223,7 +222,7 @@ class CoapServerTest { const uri = `coap://[::1]:${coapServer.getPort()}/test/`; const coapClient = new CoapClient(coapServer); - const resp = await coapClient.readResource(new TD.Form(uri + "properties/test")); + const resp = await coapClient.readResource(new Form(uri + "properties/test")); expect((await resp.toBuffer()).toString()).to.equal('"testValue"'); await coapClient.stop(); @@ -276,7 +275,7 @@ class CoapServerTest { const uri = `coap://localhost:${coapServer.getPort()}/test/`; const coapClient = new CoapClient(coapServer); - const resp = await coapClient.readResource(new TD.Form(uri + "properties/test?id=testId&globalVarTest=test1")); + const resp = await coapClient.readResource(new Form(uri + "properties/test?id=testId&globalVarTest=test1")); expect((await resp.toBuffer()).toString()).to.equal('"testValue"'); await coapServer.stop(); @@ -301,7 +300,7 @@ class CoapServerTest { const uri = `coap://localhost:${coapServer.getPort()}/.well-known/core`; const coapClient = new CoapClient(coapServer); - const resp = await coapClient.readResource(new TD.Form(uri)); + const resp = await coapClient.readResource(new Form(uri)); expect((await resp.toBuffer()).toString()).to.equal( ';rt="wot.thing";ct="50 432",;rt="wot.thing";ct="50 432"' ); @@ -457,12 +456,12 @@ class CoapServerTest { const propertyUri = `${baseUri}/properties/test?id=testId`; - await coapClient.writeResource(new TD.Form(propertyUri), new Content("text/plain", Readable.from("on"))); + await coapClient.writeResource(new Form(propertyUri), new Content("text/plain", Readable.from("on"))); - const response1 = await coapClient.readResource(new TD.Form(propertyUri)); + const response1 = await coapClient.readResource(new Form(propertyUri)); expect((await response1.toBuffer()).toString()).to.equal('"on"'); - const response2 = await coapClient.invokeResource(new TD.Form(`${baseUri}/actions/try?step=5`)); + const response2 = await coapClient.invokeResource(new Form(`${baseUri}/actions/try?step=5`)); expect((await response2.toBuffer()).toString()).to.equal('"TEST"'); await coapClient.stop(); @@ -546,19 +545,19 @@ class CoapServerTest { const baseUri = `coap://localhost:${port}/testa/properties`; // check values one by one first - const responseInteger = await coapClient.readResource(new TD.Form(`${baseUri}/testInteger`)); + const responseInteger = await coapClient.readResource(new Form(`${baseUri}/testInteger`)); expect(await decodeContent(responseInteger)).to.equal(integer); - const responseBoolean = await coapClient.readResource(new TD.Form(`${baseUri}/testBoolean`)); + const responseBoolean = await coapClient.readResource(new Form(`${baseUri}/testBoolean`)); expect(await decodeContent(responseBoolean)).to.equal(boolean); - const responseString = await coapClient.readResource(new TD.Form(`${baseUri}/testString`)); + const responseString = await coapClient.readResource(new Form(`${baseUri}/testString`)); expect(await decodeContent(responseString)).to.equal(string); - const responseObject = await coapClient.readResource(new TD.Form(`${baseUri}/testObject`)); + const responseObject = await coapClient.readResource(new Form(`${baseUri}/testObject`)); expect(await decodeContent(responseObject)).to.deep.equal(object); - const responseArray = await coapClient.readResource(new TD.Form(`${baseUri}/testArray`)); + const responseArray = await coapClient.readResource(new Form(`${baseUri}/testArray`)); expect(await decodeContent(responseArray)).to.deep.equal(array); // check values of readallproperties - const responseAll = await coapClient.readResource(new TD.Form(baseUri)); + const responseAll = await coapClient.readResource(new Form(baseUri)); expect(await decodeContent(responseAll)).to.deep.equal({ image, testInteger: integer, diff --git a/packages/binding-file/package.json b/packages/binding-file/package.json index 885d945f8..672d78215 100644 --- a/packages/binding-file/package.json +++ b/packages/binding-file/package.json @@ -13,9 +13,6 @@ ], "main": "dist/file.js", "types": "dist/file.d.ts", - "devDependencies": { - "@node-wot/td-tools": "0.8.15" - }, "dependencies": { "@node-wot/core": "0.8.15" }, diff --git a/packages/binding-file/src/file-client.ts b/packages/binding-file/src/file-client.ts index 47334139d..d77ab21a1 100644 --- a/packages/binding-file/src/file-client.ts +++ b/packages/binding-file/src/file-client.ts @@ -16,8 +16,7 @@ /** * File protocol binding */ -import { Form, SecurityScheme } from "@node-wot/td-tools"; -import { ProtocolClient, Content, createLoggers, ContentSerdes } from "@node-wot/core"; +import { ProtocolClient, Content, createLoggers, ContentSerdes, Form, SecurityScheme } from "@node-wot/core"; import { Subscription } from "rxjs/Subscription"; import { promises as asyncFs } from "fs"; import { fileURLToPath } from "node:url"; diff --git a/packages/binding-file/test/file-client-test.ts b/packages/binding-file/test/file-client-test.ts index 080797ed5..2b995a1f9 100644 --- a/packages/binding-file/test/file-client-test.ts +++ b/packages/binding-file/test/file-client-test.ts @@ -13,10 +13,9 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import { Content, ContentSerdes } from "@node-wot/core"; +import { Content, ContentSerdes, Form } from "@node-wot/core"; import FileClient from "../src/file-client"; -import { Form } from "@node-wot/td-tools"; import { expect } from "chai"; import { promises as asyncFs } from "fs"; import { fileURLToPath } from "node:url"; diff --git a/packages/binding-http/package.json b/packages/binding-http/package.json index 6a6a8a0f0..decd85759 100644 --- a/packages/binding-http/package.json +++ b/packages/binding-http/package.json @@ -29,7 +29,6 @@ }, "dependencies": { "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "@types/eventsource": "1.1.10", "accept-language-parser": "1.5.0", "basic-auth": "2.0.1", diff --git a/packages/binding-http/src/codecs/tuya-codec.ts b/packages/binding-http/src/codecs/tuya-codec.ts index d6165cd45..ea932c951 100644 --- a/packages/binding-http/src/codecs/tuya-codec.ts +++ b/packages/binding-http/src/codecs/tuya-codec.ts @@ -13,8 +13,7 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import { ContentCodec } from "@node-wot/core"; -import * as TD from "@node-wot/td-tools"; +import { ContentCodec, DataSchema } from "@node-wot/core"; import { DataSchemaValue } from "wot-typescript-definitions"; interface TuyaOutput { @@ -30,7 +29,7 @@ export default class HttpTuyaCodec implements ContentCodec { return "application/json+tuya"; } - bytesToValue(bytes: Buffer, schema: TD.DataSchema, parameters: { [key: string]: string }): DataSchemaValue { + bytesToValue(bytes: Buffer, schema: DataSchema, parameters: { [key: string]: string }): DataSchemaValue { const parsedBody: TuyaOutput = JSON.parse(bytes.toString()); const success = parsedBody.success ?? false; @@ -46,7 +45,7 @@ export default class HttpTuyaCodec implements ContentCodec { throw new Error("Property not found"); } - valueToBytes(value: unknown, schema: TD.DataSchema, parameters?: { [key: string]: string }): Buffer { + valueToBytes(value: unknown, schema: DataSchema, parameters?: { [key: string]: string }): Buffer { const obj = { commands: [ { diff --git a/packages/binding-http/src/credential.ts b/packages/binding-http/src/credential.ts index 540688b4b..4414a1746 100644 --- a/packages/binding-http/src/credential.ts +++ b/packages/binding-http/src/credential.ts @@ -15,7 +15,7 @@ import { Token } from "client-oauth2"; import fetch, { Request } from "node-fetch"; -import { BasicSecurityScheme, APIKeySecurityScheme, BearerSecurityScheme } from "@node-wot/td-tools"; +import { BasicSecurityScheme, APIKeySecurityScheme, BearerSecurityScheme } from "@node-wot/core"; import * as crypto from "crypto"; import * as queryString from "query-string"; import { TuyaCustomBearerSecurityScheme } from "./http"; diff --git a/packages/binding-http/src/http-browser.ts b/packages/binding-http/src/http-browser.ts index 05c6392bc..77365bf57 100644 --- a/packages/binding-http/src/http-browser.ts +++ b/packages/binding-http/src/http-browser.ts @@ -13,7 +13,7 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import * as TD from "@node-wot/td-tools"; +import { Form, SecurityScheme } from "@node-wot/core"; import { Headers, Response } from "node-fetch"; @@ -39,7 +39,7 @@ export interface HttpConfig { allowSelfSigned?: boolean; serverKey?: string; serverCert?: string; - security?: TD.SecurityScheme; + security?: SecurityScheme; } export class HttpHeader { @@ -47,7 +47,7 @@ export class HttpHeader { public "http:fieldValue": unknown; } -export class HttpForm extends TD.Form { +export class HttpForm extends Form { public "http:methodName"?: string; // "GET", "PUT", "POST", "DELETE" public "http:headers"?: Array | HttpHeader; } diff --git a/packages/binding-http/src/http-client-impl.ts b/packages/binding-http/src/http-client-impl.ts index 14c7ec9ed..e6e7abbad 100644 --- a/packages/binding-http/src/http-client-impl.ts +++ b/packages/binding-http/src/http-client-impl.ts @@ -22,10 +22,18 @@ import * as https from "https"; import { Subscription } from "rxjs/Subscription"; -import * as TD from "@node-wot/td-tools"; -// for Security definition - -import { ProtocolClient, Content, ProtocolHelpers, createLoggers, ContentSerdes } from "@node-wot/core"; +import { + ProtocolClient, + Content, + ProtocolHelpers, + createLoggers, + ContentSerdes, + SecurityScheme, + BasicSecurityScheme, + BearerSecurityScheme, + APIKeySecurityScheme, + OAuth2SecurityScheme, +} from "@node-wot/core"; import { HttpForm, HttpHeader, HttpConfig, HTTPMethodName, TuyaCustomBearerSecurityScheme } from "./http"; import fetch, { Request, RequestInit, Response } from "node-fetch"; import { Buffer } from "buffer"; @@ -251,29 +259,29 @@ export default class HttpClient implements ProtocolClient { this.agent?.destroy?.(); } - public setSecurity(metadata: Array, credentials?: unknown): boolean { + public setSecurity(metadata: Array, credentials?: unknown): boolean { if (metadata === undefined || !Array.isArray(metadata) || metadata.length === 0) { warn("HttpClient without security"); return false; } // TODO support for multiple security schemes - const security: TD.SecurityScheme = metadata[0]; + const security: SecurityScheme = metadata[0]; switch (security.scheme) { case "basic": { - const securityBasic: TD.BasicSecurityScheme = security; + const securityBasic: BasicSecurityScheme = security; this.credential = new BasicCredential(credentials as BasicCredentialConfiguration, securityBasic); break; } case "bearer": { - const securityBearer: TD.BearerSecurityScheme = security; + const securityBearer: BearerSecurityScheme = security; this.credential = new BearerCredential(credentials as BearerCredentialConfiguration, securityBearer); break; } case "apikey": { - const securityAPIKey: TD.APIKeySecurityScheme = security; + const securityAPIKey: APIKeySecurityScheme = security; this.credential = new BasicKeyCredential( credentials as BasicKeyCredentialConfiguration, @@ -282,7 +290,7 @@ export default class HttpClient implements ProtocolClient { break; } case "oauth2": { - const securityOAuth: TD.OAuth2SecurityScheme = security; + const securityOAuth: OAuth2SecurityScheme = security; if (securityOAuth.flow === "client") { securityOAuth.flow = "client_credentials"; diff --git a/packages/binding-http/src/http-server.ts b/packages/binding-http/src/http-server.ts index 414bd4ba6..6b17203a6 100644 --- a/packages/binding-http/src/http-server.ts +++ b/packages/binding-http/src/http-server.ts @@ -22,18 +22,18 @@ import * as http from "http"; import * as https from "https"; import bauth from "basic-auth"; -import * as TD from "@node-wot/td-tools"; import Servient, { ProtocolServer, ContentSerdes, Helpers, ExposedThing, ProtocolHelpers, + Form, + OAuth2SecurityScheme, createLoggers, } from "@node-wot/core"; import { HttpConfig, HttpForm, OAuth2ServerConfig } from "./http"; import createValidator, { Validator } from "./oauth-token-validation"; -import { OAuth2SecurityScheme } from "@node-wot/td-tools"; import slugify from "slugify"; import { ActionElement, EventElement, PropertyElement } from "wot-thing-description-types"; import { MiddlewareRequestHandler } from "./http-server-middleware"; @@ -315,7 +315,7 @@ export default class HttpServer implements ProtocolServer { return false; } - private addUrlRewriteEndpoints(form: TD.Form, forms: Array): void { + private addUrlRewriteEndpoints(form: Form, forms: Array
): void { if (this.urlRewrite != null) { for (const [inUri, toUri] of Object.entries(this.urlRewrite)) { const endsWithToUri: boolean = form.href.endsWith(toUri); @@ -350,7 +350,7 @@ export default class HttpServer implements ProtocolServer { if (properties.length > 0) { const href = base + "/" + this.PROPERTY_DIR; - const form = new TD.Form(href, type); + const form = new Form(href, type); if (allReadOnly && !allWriteOnly) { form.op = ["readallproperties", "readmultipleproperties"]; } else if (allWriteOnly && !allReadOnly) { @@ -377,7 +377,7 @@ export default class HttpServer implements ProtocolServer { thing.uriVariables ); const href = base + "/" + this.PROPERTY_DIR + "/" + propertyNamePattern; - const form = new TD.Form(href, type); + const form = new Form(href, type); ProtocolHelpers.updatePropertyFormWithTemplate( form, (tdTemplate.properties?.[propertyName] ?? {}) as PropertyElement @@ -412,7 +412,7 @@ export default class HttpServer implements ProtocolServer { encodeURIComponent(propertyName) + "/" + this.OBSERVABLE_DIR; - const form = new TD.Form(href, type); + const form = new Form(href, type); form.op = ["observeproperty", "unobserveproperty"]; form.subprotocol = "longpoll"; property.forms.push(form); @@ -430,7 +430,7 @@ export default class HttpServer implements ProtocolServer { thing.uriVariables ); const href = base + "/" + this.ACTION_DIR + "/" + actionNamePattern; - const form = new TD.Form(href, type); + const form = new Form(href, type); ProtocolHelpers.updateActionFormWithTemplate( form, (tdTemplate.actions?.[actionName] ?? {}) as ActionElement @@ -451,7 +451,7 @@ export default class HttpServer implements ProtocolServer { thing.uriVariables ); const href = base + "/" + this.EVENT_DIR + "/" + eventNamePattern; - const form = new TD.Form(href, type); + const form = new Form(href, type); ProtocolHelpers.updateEventFormWithTemplate( form, (tdTemplate.events?.[eventName] ?? {}) as EventElement diff --git a/packages/binding-http/src/http.ts b/packages/binding-http/src/http.ts index a44ffc20a..0dae9fdd5 100644 --- a/packages/binding-http/src/http.ts +++ b/packages/binding-http/src/http.ts @@ -13,7 +13,7 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import * as TD from "@node-wot/td-tools"; +import { SecurityScheme, Form } from "@node-wot/core"; import { Method } from "./oauth-token-validation"; import { MiddlewareRequestHandler } from "./http-server-middleware"; @@ -44,11 +44,11 @@ export interface HttpConfig { allowSelfSigned?: boolean; serverKey?: string; serverCert?: string; - security?: TD.SecurityScheme[]; + security?: SecurityScheme[]; middleware?: MiddlewareRequestHandler; } -export interface OAuth2ServerConfig extends TD.SecurityScheme { +export interface OAuth2ServerConfig extends SecurityScheme { method: Method; /** * Regex to select the valid clients ids. Default: .* @@ -56,7 +56,7 @@ export interface OAuth2ServerConfig extends TD.SecurityScheme { allowedClients?: string; } -export interface TuyaCustomBearerSecurityScheme extends TD.SecurityScheme { +export interface TuyaCustomBearerSecurityScheme extends SecurityScheme { /** * This scheme is necessary because of the Tuya binding. * The Tuya Apis are implementing a custom security protocol that needs a custom handling @@ -72,7 +72,7 @@ export class HttpHeader { public "htv:fieldValue": string; } -export class HttpForm extends TD.Form { +export class HttpForm extends Form { public "htv:methodName"?: HTTPMethodName; public "htv:headers"?: Array | HttpHeader; } diff --git a/packages/binding-http/src/oauth-manager.ts b/packages/binding-http/src/oauth-manager.ts index 9f6e032f8..33cc7103d 100644 --- a/packages/binding-http/src/oauth-manager.ts +++ b/packages/binding-http/src/oauth-manager.ts @@ -13,8 +13,7 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import { OAuth2SecurityScheme } from "@node-wot/td-tools"; -import { Helpers } from "@node-wot/core"; +import { Helpers, OAuth2SecurityScheme } from "@node-wot/core"; import ClientOAuth2 from "client-oauth2"; import { request, RequestOptions } from "https"; import { OAuthCredential } from "./credential"; diff --git a/packages/binding-http/src/routes/thing-description.ts b/packages/binding-http/src/routes/thing-description.ts index 74759ed9b..161c1b2c1 100644 --- a/packages/binding-http/src/routes/thing-description.ts +++ b/packages/binding-http/src/routes/thing-description.ts @@ -13,11 +13,10 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import { ContentSerdes, createLoggers } from "@node-wot/core"; +import { ContentSerdes, setContextLanguage, createLoggers } from "@node-wot/core"; import { IncomingMessage, ServerResponse } from "http"; import { ExposedThing, ThingDescription } from "wot-typescript-definitions"; import * as acceptLanguageParser from "accept-language-parser"; -import * as TD from "@node-wot/td-tools"; import HttpServer from "../http-server"; const { debug } = createLoggers("binding-http", "routes", "thing-description"); @@ -59,7 +58,7 @@ function resetMultiLangThing(thing: ThingDescription, prefLang: string) { // TODO can we reset "title" to another name given that title is used in URI creation? // set @language in @context - TD.setContextLanguage(thing, prefLang, true); + setContextLanguage(thing, prefLang, true); // use new language title if (thing.titles) { diff --git a/packages/binding-http/test/credential-test.ts b/packages/binding-http/test/credential-test.ts index d490edc29..84a1a5720 100644 --- a/packages/binding-http/test/credential-test.ts +++ b/packages/binding-http/test/credential-test.ts @@ -14,7 +14,7 @@ ********************************************************************************/ import { suite, test } from "@testdeck/mocha"; -import { APIKeySecurityScheme, BasicSecurityScheme, BearerSecurityScheme } from "@node-wot/td-tools"; +import { APIKeySecurityScheme, BasicSecurityScheme, BearerSecurityScheme } from "@node-wot/core"; import * as chai from "chai"; import chaiAsPromised from "chai-as-promised"; import { Request } from "node-fetch"; diff --git a/packages/binding-http/test/http-client-basic-test.ts b/packages/binding-http/test/http-client-basic-test.ts index e57424bbd..6cc56314c 100644 --- a/packages/binding-http/test/http-client-basic-test.ts +++ b/packages/binding-http/test/http-client-basic-test.ts @@ -17,7 +17,7 @@ import { suite, test } from "@testdeck/mocha"; import express from "express"; import { HttpClient } from "../src/http"; import * as https from "https"; -import { BasicSecurityScheme } from "@node-wot/td-tools"; +import { BasicSecurityScheme } from "@node-wot/core"; import * as chai from "chai"; import chaiAsPromised from "chai-as-promised"; import { fail } from "assert"; diff --git a/packages/binding-http/test/http-client-oauth-tests.ts b/packages/binding-http/test/http-client-oauth-tests.ts index 9ec3ca56c..8c52144df 100644 --- a/packages/binding-http/test/http-client-oauth-tests.ts +++ b/packages/binding-http/test/http-client-oauth-tests.ts @@ -17,7 +17,7 @@ import * as https from "https"; import { suite, test } from "@testdeck/mocha"; import express from "express"; import { HttpClient } from "../src/http"; -import { OAuth2SecurityScheme } from "@node-wot/td-tools"; +import { OAuth2SecurityScheme } from "@node-wot/core"; import InMemoryModel from "./memory-model"; import { promisify } from "util"; diff --git a/packages/binding-mbus/package.json b/packages/binding-mbus/package.json index 1dbd20acf..892b810d7 100644 --- a/packages/binding-mbus/package.json +++ b/packages/binding-mbus/package.json @@ -15,7 +15,6 @@ "types": "dist/mbus.d.ts", "dependencies": { "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "node-mbus": "^2.2.4", "wot-typescript-definitions": "0.8.0-SNAPSHOT.29" }, diff --git a/packages/binding-mbus/src/mbus-client.ts b/packages/binding-mbus/src/mbus-client.ts index 740940106..5d4f05499 100644 --- a/packages/binding-mbus/src/mbus-client.ts +++ b/packages/binding-mbus/src/mbus-client.ts @@ -18,8 +18,7 @@ */ import { MBusForm } from "./mbus"; -import { ProtocolClient, Content, createDebugLogger } from "@node-wot/core"; -import { SecurityScheme } from "@node-wot/td-tools"; +import { ProtocolClient, Content, SecurityScheme, createDebugLogger } from "@node-wot/core"; import { MBusConnection, PropertyOperation } from "./mbus-connection"; import { Subscription } from "rxjs/Subscription"; diff --git a/packages/binding-mbus/src/mbus.ts b/packages/binding-mbus/src/mbus.ts index 425ce5a23..8484d1c08 100644 --- a/packages/binding-mbus/src/mbus.ts +++ b/packages/binding-mbus/src/mbus.ts @@ -13,7 +13,7 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import { Form } from "@node-wot/td-tools"; +import { Form } from "@node-wot/core"; export { default as MBusClientFactory } from "./mbus-client-factory"; export { default as MBusClient } from "./mbus-client"; export * from "./mbus-client"; diff --git a/packages/binding-modbus/package.json b/packages/binding-modbus/package.json index cfa4ff53c..f4cd01ad0 100644 --- a/packages/binding-modbus/package.json +++ b/packages/binding-modbus/package.json @@ -18,7 +18,6 @@ "types": "dist/modbus.d.ts", "dependencies": { "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "modbus-serial": "^8.0.17", "rxjs": "5.5.11", "wot-typescript-definitions": "0.8.0-SNAPSHOT.29" diff --git a/packages/binding-modbus/src/modbus-client.ts b/packages/binding-modbus/src/modbus-client.ts index b4f5830ad..ff31d50bb 100644 --- a/packages/binding-modbus/src/modbus-client.ts +++ b/packages/binding-modbus/src/modbus-client.ts @@ -17,8 +17,7 @@ */ import { ModbusForm, ModbusFunction } from "./modbus"; -import { ProtocolClient, Content, DefaultContent, createDebugLogger, Endianness } from "@node-wot/core"; -import { SecurityScheme } from "@node-wot/td-tools"; +import { ProtocolClient, Content, DefaultContent, SecurityScheme, createDebugLogger, Endianness } from "@node-wot/core"; import { modbusFunctionToEntity } from "./utils"; import { ModbusConnection, ModbusFormWithDefaults, PropertyOperation } from "./modbus-connection"; import { Readable } from "stream"; diff --git a/packages/binding-modbus/src/modbus.ts b/packages/binding-modbus/src/modbus.ts index e3986191f..9abef5c66 100644 --- a/packages/binding-modbus/src/modbus.ts +++ b/packages/binding-modbus/src/modbus.ts @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import { Form } from "@node-wot/td-tools"; +import { Form } from "@node-wot/core"; export { default as ModbusClientFactory } from "./modbus-client-factory"; export { default as ModbusClient } from "./modbus-client"; export * from "./modbus-client"; diff --git a/packages/binding-mqtt/package.json b/packages/binding-mqtt/package.json index f924fa5b0..c99c0604f 100644 --- a/packages/binding-mqtt/package.json +++ b/packages/binding-mqtt/package.json @@ -15,7 +15,6 @@ "types": "dist/mqtt.d.ts", "dependencies": { "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "aedes": "^0.46.2", "mqtt": "^5.3.2", "rxjs": "5.5.11" diff --git a/packages/binding-mqtt/src/mqtt-broker-server.ts b/packages/binding-mqtt/src/mqtt-broker-server.ts index 3821bc88b..9c179b8d7 100644 --- a/packages/binding-mqtt/src/mqtt-broker-server.ts +++ b/packages/binding-mqtt/src/mqtt-broker-server.ts @@ -23,7 +23,6 @@ import * as url from "url"; import { AuthenticateError, Client, Server, Aedes } from "aedes"; import * as net from "net"; import * as tls from "tls"; -import * as TD from "@node-wot/td-tools"; import { MqttBrokerServerConfig, MqttForm } from "./mqtt"; import { ProtocolServer, @@ -32,6 +31,7 @@ import { ContentSerdes, ProtocolHelpers, Content, + Form, createLoggers, } from "@node-wot/core"; import { InteractionOptions } from "wot-typescript-definitions"; @@ -134,7 +134,7 @@ export default class MqttBrokerServer implements ProtocolServer { const writeOnly: boolean = property.writeOnly ?? false; if (!writeOnly) { const href = this.brokerURI + "/" + topic; - const form = new TD.Form(href, ContentSerdes.DEFAULT); + const form = new Form(href, ContentSerdes.DEFAULT); form.op = ["readproperty", "observeproperty", "unobserveproperty"]; property.forms.push(form); debug(`MqttBrokerServer at ${this.brokerURI} assigns '${href}' to property '${propertyName}'`); @@ -156,7 +156,7 @@ export default class MqttBrokerServer implements ProtocolServer { if (!readOnly) { const href = this.brokerURI + "/" + topic + "/writeproperty"; this.broker.subscribe(topic + "/writeproperty"); - const form = new TD.Form(href, ContentSerdes.DEFAULT); + const form = new Form(href, ContentSerdes.DEFAULT); form.op = ["writeproperty"]; thing.properties[propertyName].forms.push(form); debug(`MqttBrokerServer at ${this.brokerURI} assigns '${href}' to property '${propertyName}'`); @@ -170,7 +170,7 @@ export default class MqttBrokerServer implements ProtocolServer { this.broker.subscribe(topic); const href = this.brokerURI + "/" + topic; - const form = new TD.Form(href, ContentSerdes.DEFAULT); + const form = new Form(href, ContentSerdes.DEFAULT); form.op = ["invokeaction"]; thing.actions[actionName].forms.push(form); debug(`MqttBrokerServer at ${this.brokerURI} assigns '${href}' to Action '${actionName}'`); diff --git a/packages/binding-mqtt/src/mqtt-client.ts b/packages/binding-mqtt/src/mqtt-client.ts index 7cdc12d2a..18a1d4fe4 100644 --- a/packages/binding-mqtt/src/mqtt-client.ts +++ b/packages/binding-mqtt/src/mqtt-client.ts @@ -17,8 +17,15 @@ * Protocol test suite to test protocol implementations */ -import { ProtocolClient, Content, DefaultContent, createLoggers, ContentSerdes } from "@node-wot/core"; -import * as TD from "@node-wot/td-tools"; +import { + ProtocolClient, + Content, + DefaultContent, + createLoggers, + ContentSerdes, + Form, + SecurityScheme, +} from "@node-wot/core"; import * as mqtt from "mqtt"; import { MqttClientConfig, MqttForm } from "./mqtt"; import * as url from "url"; @@ -158,7 +165,7 @@ export default class MqttClient implements ProtocolClient { return new DefaultContent(Readable.from([])); } - public async unlinkResource(form: TD.Form): Promise { + public async unlinkResource(form: Form): Promise { const requestUri = new url.URL(form.href); const brokerUri: string = `${this.scheme}://` + requestUri.host; const topic = requestUri.pathname.slice(1); @@ -188,12 +195,12 @@ export default class MqttClient implements ProtocolClient { if (this.client) return this.client.endAsync(); } - public setSecurity(metadata: Array, credentials?: MqttClientSecurityParameters): boolean { + public setSecurity(metadata: Array, credentials?: MqttClientSecurityParameters): boolean { if (metadata === undefined || !Array.isArray(metadata) || metadata.length === 0) { warn(`MqttClient received empty security metadata`); return false; } - const security: TD.SecurityScheme = metadata[0]; + const security: SecurityScheme = metadata[0]; if (security.scheme === "basic") { if (credentials === undefined) { diff --git a/packages/binding-mqtt/src/mqtt.ts b/packages/binding-mqtt/src/mqtt.ts index 355c4f29a..9df00c9a1 100644 --- a/packages/binding-mqtt/src/mqtt.ts +++ b/packages/binding-mqtt/src/mqtt.ts @@ -17,7 +17,7 @@ * Protocol test suite to test protocol implementations */ -import { Form } from "@node-wot/td-tools"; +import { Form } from "@node-wot/core"; export { default as MqttClient } from "./mqtt-client"; export { default as MqttClientFactory } from "./mqtt-client-factory"; diff --git a/packages/binding-netconf/package.json b/packages/binding-netconf/package.json index 28ac6e6cd..1bca41d32 100644 --- a/packages/binding-netconf/package.json +++ b/packages/binding-netconf/package.json @@ -15,7 +15,6 @@ "types": "dist/netconf.d.ts", "dependencies": { "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "@types/node-netconf": "npm:@types/netconf@^2.0.0", "@types/url-parse": "^1.4.3", "case-1.5.3": "npm:case@^1.5.3", diff --git a/packages/binding-netconf/src/codecs/netconf-codec.ts b/packages/binding-netconf/src/codecs/netconf-codec.ts index a0af778e0..8f0eef158 100644 --- a/packages/binding-netconf/src/codecs/netconf-codec.ts +++ b/packages/binding-netconf/src/codecs/netconf-codec.ts @@ -13,8 +13,7 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import { createDebugLogger } from "@node-wot/core"; -import * as TD from "@node-wot/td-tools"; +import { DataSchema, createDebugLogger } from "@node-wot/core"; import Url from "url-parse"; import { DataSchemaValue } from "wot-typescript-definitions"; @@ -31,7 +30,7 @@ export default class NetconfCodec { return "application/yang-data+xml"; } - bytesToValue(bytes: Buffer, schema: TD.DataSchema, parameters: { [key: string]: string }): DataSchemaValue { + bytesToValue(bytes: Buffer, schema: DataSchema, parameters: { [key: string]: string }): DataSchemaValue { debug(`NetconfCodec parsing '${bytes.toString()}'`); try { @@ -91,7 +90,7 @@ export default class NetconfCodec { } } - valueToBytes(value: unknown, schema: TD.DataSchema, parameters?: { [key: string]: string }): Buffer { + valueToBytes(value: unknown, schema: DataSchema, parameters?: { [key: string]: string }): Buffer { debug(`NetconfCodec serializing ${value}`); let body = ""; if (value !== undefined) { diff --git a/packages/binding-netconf/src/netconf-client.ts b/packages/binding-netconf/src/netconf-client.ts index 872e2e90d..94f71b9bd 100644 --- a/packages/binding-netconf/src/netconf-client.ts +++ b/packages/binding-netconf/src/netconf-client.ts @@ -16,9 +16,8 @@ /** * Netconf protocol binding */ -import { ProtocolClient, Content, createLoggers } from "@node-wot/core"; +import { ProtocolClient, Content, SecurityScheme, createLoggers } from "@node-wot/core"; import { NetconfForm, NetConfCredentials, RpcMethod, isRpcMethod } from "./netconf"; -import * as TD from "@node-wot/td-tools"; import * as AsyncNodeNetcon from "./async-node-netconf"; import Url from "url-parse"; import { Readable } from "stream"; @@ -170,7 +169,7 @@ export default class NetconfClient implements ProtocolClient { // do nothing } - public setSecurity(metadata: Array, credentials?: NetConfCredentials): boolean { + public setSecurity(metadata: Array, credentials?: NetConfCredentials): boolean { if (metadata === undefined || !Array.isArray(metadata) || metadata.length === 0) { warn(`NetconfClient without security`); return false; diff --git a/packages/binding-netconf/src/netconf.ts b/packages/binding-netconf/src/netconf.ts index 575893d50..18ea1d47f 100644 --- a/packages/binding-netconf/src/netconf.ts +++ b/packages/binding-netconf/src/netconf.ts @@ -13,7 +13,7 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import { Form } from "@node-wot/td-tools"; +import { Form } from "@node-wot/core"; export { default as NetconfClient } from "./netconf-client"; export { default as NetconfClientFactory } from "./netconf-client-factory"; export * from "./netconf"; diff --git a/packages/binding-opcua/package.json b/packages/binding-opcua/package.json index 1ed541dc6..6c08981d7 100644 --- a/packages/binding-opcua/package.json +++ b/packages/binding-opcua/package.json @@ -19,7 +19,6 @@ }, "dependencies": { "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "node-opcua": "2.113.0", "node-opcua-address-space": "2.113.0", "node-opcua-basic-types": "2.113.0", diff --git a/packages/binding-opcua/src/codec.ts b/packages/binding-opcua/src/codec.ts index f4cbdf3e0..77add05b2 100644 --- a/packages/binding-opcua/src/codec.ts +++ b/packages/binding-opcua/src/codec.ts @@ -13,8 +13,7 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import { ContentCodec, createLoggers } from "@node-wot/core"; -import { DataSchema } from "@node-wot/td-tools"; +import { ContentCodec, DataSchema, createLoggers } from "@node-wot/core"; import { DataValue } from "node-opcua-data-value"; import { DataType, Variant } from "node-opcua-variant"; import Ajv from "ajv"; diff --git a/packages/binding-opcua/src/opcua-protocol-client.ts b/packages/binding-opcua/src/opcua-protocol-client.ts index 85ac6813d..e30bee8f4 100644 --- a/packages/binding-opcua/src/opcua-protocol-client.ts +++ b/packages/binding-opcua/src/opcua-protocol-client.ts @@ -17,8 +17,7 @@ import { Subscription } from "rxjs/Subscription"; import { promisify } from "util"; import { Readable } from "stream"; -import { ProtocolClient, Content, ContentSerdes, createLoggers } from "@node-wot/core"; -import { Form, SecurityScheme } from "@node-wot/td-tools"; +import { ProtocolClient, Content, ContentSerdes, Form, SecurityScheme, createLoggers } from "@node-wot/core"; import { ClientSession, diff --git a/packages/binding-opcua/test/opcua-codec-test.ts b/packages/binding-opcua/test/opcua-codec-test.ts index e751606db..7a97f165a 100644 --- a/packages/binding-opcua/test/opcua-codec-test.ts +++ b/packages/binding-opcua/test/opcua-codec-test.ts @@ -16,8 +16,7 @@ import { exist } from "should"; import { expect } from "chai"; -import { ContentSerdes, Helpers, createLoggers } from "@node-wot/core"; -import { ObjectSchema } from "@node-wot/td-tools"; +import { ContentSerdes, Helpers, ObjectSchema, createLoggers } from "@node-wot/core"; import { DataValue } from "node-opcua-data-value"; import { DataType, VariantArrayType } from "node-opcua-variant"; diff --git a/packages/binding-websockets/package.json b/packages/binding-websockets/package.json index 8f5843796..e4d986813 100644 --- a/packages/binding-websockets/package.json +++ b/packages/binding-websockets/package.json @@ -17,7 +17,6 @@ "dependencies": { "@node-wot/binding-http": "0.8.15", "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "slugify": "^1.4.5", "ws": "^7.5.10" }, diff --git a/packages/binding-websockets/src/ws-client.ts b/packages/binding-websockets/src/ws-client.ts index 3671ace3c..f6f5d6ff2 100644 --- a/packages/binding-websockets/src/ws-client.ts +++ b/packages/binding-websockets/src/ws-client.ts @@ -17,8 +17,7 @@ * WebSockets client */ -import { ProtocolClient, Content, createLoggers } from "@node-wot/core"; -import { Form, SecurityScheme } from "@node-wot/td-tools"; +import { ProtocolClient, Content, Form, SecurityScheme, createLoggers } from "@node-wot/core"; import { Subscription } from "rxjs/Subscription"; const { debug, warn } = createLoggers("binding-websockets", "ws-client"); diff --git a/packages/binding-websockets/src/ws-server.ts b/packages/binding-websockets/src/ws-server.ts index d3b7bd2fc..eef138332 100644 --- a/packages/binding-websockets/src/ws-server.ts +++ b/packages/binding-websockets/src/ws-server.ts @@ -26,8 +26,16 @@ import * as net from "net"; import * as WebSocket from "ws"; import { AddressInfo } from "net"; -import * as TD from "@node-wot/td-tools"; -import { ProtocolServer, Servient, ExposedThing, ContentSerdes, Helpers, Content, createLoggers } from "@node-wot/core"; +import { + ProtocolServer, + Servient, + ExposedThing, + ContentSerdes, + Helpers, + Content, + Form, + createLoggers, +} from "@node-wot/core"; import { HttpServer, HttpConfig } from "@node-wot/binding-http"; import slugify from "slugify"; @@ -174,7 +182,7 @@ export default class WebSocketServer implements ProtocolServer { // Populate forms related to the property for (const address of Helpers.getAddresses()) { const href = `${this.scheme}://${address}:${this.getPort()}${path}`; - const form = new TD.Form(href, ContentSerdes.DEFAULT); + const form = new Form(href, ContentSerdes.DEFAULT); const ops = []; const writeOnly: boolean = property.writeOnly ?? false; @@ -236,7 +244,7 @@ export default class WebSocketServer implements ProtocolServer { for (const address of Helpers.getAddresses()) { const href = `${this.scheme}://${address}:${this.getPort()}${path}`; - const form = new TD.Form(href, ContentSerdes.DEFAULT); + const form = new Form(href, ContentSerdes.DEFAULT); form.op = ["invokeaction"]; action.forms.push(form); debug(`WebSocketServer on port ${this.getPort()} assigns '${href}' to Action '${actionName}'`); @@ -250,7 +258,7 @@ export default class WebSocketServer implements ProtocolServer { // Populate forms related to the event for (const address of Helpers.getAddresses()) { const href = `${this.scheme}://${address}:${this.getPort()}${path}`; - const form = new TD.Form(href, ContentSerdes.DEFAULT); + const form = new Form(href, ContentSerdes.DEFAULT); form.op = "subscribeevent"; event.forms.push(form); debug(`WebSocketServer on port ${this.getPort()} assigns '${href}' to Event '${eventName}'`); diff --git a/packages/browser-bundle/index.js b/packages/browser-bundle/index.js index 6867756ca..452c2b51d 100644 --- a/packages/browser-bundle/index.js +++ b/packages/browser-bundle/index.js @@ -1,7 +1,6 @@ "use strict"; var Wot = {}; -Wot.Tools = require("@node-wot/td-tools"); Wot.Core = require("@node-wot/core"); Wot.Http = require("@node-wot/binding-http"); Wot.WebSocket = require("@node-wot/binding-websockets"); diff --git a/packages/browser-bundle/package.json b/packages/browser-bundle/package.json index 9b018ca07..c16ba1313 100644 --- a/packages/browser-bundle/package.json +++ b/packages/browser-bundle/package.json @@ -16,7 +16,6 @@ "@node-wot/binding-http": "0.8.15", "@node-wot/binding-websockets": "0.8.15", "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "browserify": "^17.0.0", "readable-stream4": "npm:readable-stream@^4.0.0" }, diff --git a/packages/cli/package.json b/packages/cli/package.json index e945860f8..239195a41 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -26,7 +26,7 @@ "@node-wot/binding-mqtt": "0.8.15", "@node-wot/binding-websockets": "0.8.15", "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", + "@thingweb/thing-model": "^1.0.1", "@types/lodash": "^4.14.199", "ajv": "^8.11.0", "commander": "^9.1.0", diff --git a/packages/cli/src/cli-default-servient.ts b/packages/cli/src/cli-default-servient.ts index 5f10eaa30..dfb798235 100644 --- a/packages/cli/src/cli-default-servient.ts +++ b/packages/cli/src/cli-default-servient.ts @@ -25,7 +25,7 @@ import { CoapServer, CoapClientFactory, CoapsClientFactory } from "@node-wot/bin import { MqttBrokerServer, MqttClientFactory } from "@node-wot/binding-mqtt"; import { FileClientFactory } from "@node-wot/binding-file"; import { CompilerFunction, NodeVM } from "vm2"; -import { ThingModelHelpers } from "@node-wot/td-tools"; +import { ThingModelHelpers } from "@thingweb/thing-model"; const { debug, error, info } = createLoggers("cli", "cli-default-servient"); diff --git a/packages/core/package.json b/packages/core/package.json index 6f3f1a02e..7bd280189 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -21,8 +21,8 @@ "@types/uuid": "^8.3.1" }, "dependencies": { - "@node-wot/td-tools": "0.8.15", "@petamoriken/float16": "^3.1.1", + "@thingweb/thing-model": "^1.0.3", "ajv": "^8.11.0", "ajv-formats": "^2.1.1", "cbor": "^8.1.0", diff --git a/packages/core/src/consumed-thing.ts b/packages/core/src/consumed-thing.ts index 6b5e42063..3f976d614 100644 --- a/packages/core/src/consumed-thing.ts +++ b/packages/core/src/consumed-thing.ts @@ -15,7 +15,18 @@ import { ConsumedThing as IConsumedThing, InteractionInput, Subscription } from "wot-typescript-definitions"; -import * as TD from "@node-wot/td-tools"; +import { + Form, + Thing, + ThingProperty, + BaseSchema, + ThingInteraction, + ThingAction, + ThingEvent, + SecurityScheme, +} from "./thing-description"; + +import { ThingModel } from "wot-thing-model-types"; import Servient from "./servient"; import Helpers from "./helpers"; @@ -35,7 +46,6 @@ import { FormElementProperty, PropertyElement, } from "wot-thing-description-types"; -import { ThingInteraction } from "@node-wot/td-tools"; import { createLoggers } from "./logger"; const { debug, warn } = createLoggers("core", "consumed-thing"); @@ -48,10 +58,10 @@ enum Affordance { export interface ClientAndForm { client: ProtocolClient; - form?: TD.Form; + form?: Form; } -class ConsumedThingProperty extends TD.ThingProperty implements TD.ThingProperty, TD.BaseSchema { +class ConsumedThingProperty extends ThingProperty implements ThingProperty, BaseSchema { #name: string; #thing: ConsumedThing; @@ -63,7 +73,7 @@ class ConsumedThingProperty extends TD.ThingProperty implements TD.ThingProperty } } -class ConsumedThingAction extends TD.ThingAction implements TD.ThingAction { +class ConsumedThingAction extends ThingAction implements ThingAction { #name: string; #thing: ConsumedThing; @@ -75,7 +85,7 @@ class ConsumedThingAction extends TD.ThingAction implements TD.ThingAction { } } -class ConsumedThingEvent extends TD.ThingEvent { +class ConsumedThingEvent extends ThingEvent { #name: string; #thing: ConsumedThing; @@ -108,9 +118,9 @@ abstract class InternalSubscription implements Subscription { function handleUriVariables( thing: ConsumedThing, ti: ThingInteraction, - form: TD.Form, + form: Form, options?: WoT.InteractionOptions -): TD.Form { +): Form { const ut = UriTemplate.parse(form.href); const uriVariables = Helpers.parseInteractionOptions(thing, ti, options).uriVariables; const updatedHref = ut.expand(uriVariables ?? {}); @@ -135,7 +145,7 @@ class InternalPropertySubscription extends InternalSubscription { private readonly form: FormElementProperty ) { super(thing, name, client); - const index = this.thing.properties?.[name].forms.indexOf(form as TD.Form); + const index = this.thing.properties?.[name].forms.indexOf(form as Form); if (index === undefined || index < 0) { throw new Error(`Could not find form ${form.href} in property ${name}`); } @@ -191,7 +201,7 @@ class InternalPropertySubscription extends InternalSubscription { */ private findFormIndexWithScoring( formIndex: number, - forms: TD.Form[], + forms: Form[], operation: "unsubscribeevent" | "unobserveproperty" ): number { const refForm = forms[formIndex]; @@ -228,7 +238,7 @@ class InternalPropertySubscription extends InternalSubscription { */ function findFormIndexWithScoring( formIndex: number, - forms: TD.Form[], + forms: Form[], operation: "unsubscribeevent" | "unobserveproperty" ): number { const refForm = forms[formIndex]; @@ -262,7 +272,7 @@ class InternalEventSubscription extends InternalSubscription { private formIndex: number; constructor(thing: ConsumedThing, name: string, client: ProtocolClient, private readonly form: FormElementEvent) { super(thing, name, client); - const index = this.thing.events?.[name].forms.indexOf(form as TD.Form); + const index = this.thing.events?.[name].forms.indexOf(form as Form); if (index === undefined || index < 0) { throw new Error(`Could not find form ${form.href} in event ${name}`); } @@ -316,7 +326,7 @@ class InternalEventSubscription extends InternalSubscription { } } -export default class ConsumedThing extends TD.Thing implements IConsumedThing { +export default class ConsumedThing extends Thing implements IConsumedThing { /** A map of interactable Thing Properties with read()/write()/subscribe() functions */ properties: { [key: string]: PropertyElement; @@ -357,7 +367,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { private subscribedEvents: Map = new Map(); private observedProperties: Map = new Map(); - constructor(servient: Servient, thingModel: TD.ThingModel = {}) { + constructor(servient: Servient, thingModel?: ThingModel) { super(); this.#servient = servient; @@ -367,7 +377,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { this.actions = {}; this.events = {}; - const deepClonedModel = Helpers.structuredClone(thingModel); + const deepClonedModel = Helpers.structuredClone(thingModel ?? {}); Object.assign(this, deepClonedModel); this.extendInteractions(); } @@ -395,13 +405,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { } } - findForm( - forms: Array, - op: string, - affordance: Affordance, - schemes: string[], - idx: number - ): TD.Form | undefined { + findForm(forms: Array, op: string, affordance: Affordance, schemes: string[], idx: number): Form | undefined { let form; // find right operation and corresponding scheme in the array form @@ -434,8 +438,8 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { return form; } - getSecuritySchemes(security: Array): Array { - const scs: Array = []; + getSecuritySchemes(security: Array): Array { + const scs: Array = []; for (const s of security) { const ws = this.securityDefinitions[s + ""]; // String vs. string (fix wot-typescript-definitions?) // also push nosec in case of proxy @@ -446,7 +450,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { return scs; } - ensureClientSecurity(client: ProtocolClient, form: TD.Form | undefined): void { + 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`); @@ -469,7 +473,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { // utility for Property, Action, and Event getClientFor( - forms: Array, + forms: Array, op: string, affordance: Affordance, options?: WoT.InteractionOptions @@ -478,7 +482,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { throw new Error(`ConsumedThing '${this.title}' has no links for this interaction`); } - let form: TD.Form | undefined; + let form: Form | undefined; let client: ProtocolClient; if (options?.formIndex !== undefined) { @@ -564,7 +568,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { private handleInteractionOutput( content: Content, - form: TD.Form, + form: Form, outputDataSchema: WoT.DataSchema | undefined, ignoreValidation: boolean ): InteractionOutput { @@ -825,7 +829,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { // creates new form (if needed) for URI Variables // http://192.168.178.24:8080/counter/actions/increment{?step} with options {uriVariables: {'step' : 3}} --> http://192.168.178.24:8080/counter/actions/increment?step=3 // see RFC6570 (https://tools.ietf.org/html/rfc6570) for URI Template syntax - handleUriVariables(ti: ThingInteraction, form: TD.Form, options?: WoT.InteractionOptions): TD.Form { + handleUriVariables(ti: ThingInteraction, form: Form, options?: WoT.InteractionOptions): Form { return handleUriVariables(this, ti, form, options); } } diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 2cd63ef67..6db52ee39 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -21,6 +21,12 @@ import Servient from "./servient"; export default Servient; export { Servient }; +// TD definitions +export * from "./thing-description"; + +// SerDes functionality +export * from "./serdes"; + // ContentSerdes + built-in codecs export * from "./content-serdes"; export { default as JsonCodec } from "./codecs/json-codec"; diff --git a/packages/core/src/exposed-thing.ts b/packages/core/src/exposed-thing.ts index b8c44b778..d64d9103e 100644 --- a/packages/core/src/exposed-thing.ts +++ b/packages/core/src/exposed-thing.ts @@ -16,7 +16,8 @@ import * as WoT from "wot-typescript-definitions"; import * as TDT from "wot-thing-description-types"; -import * as TD from "@node-wot/td-tools"; +import * as TD from "./thing-description"; +import { serializeTD, setContextLanguage } from "./serdes"; import Servient from "./servient"; import Helpers from "./helpers"; @@ -143,11 +144,11 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { } } */ // set @language to "en" if no @language available - TD.setContextLanguage(this, TD.DEFAULT_CONTEXT_LANGUAGE, false); + setContextLanguage(this as WoT.ThingDescription, TD.DEFAULT_CONTEXT_LANGUAGE, false); } public getThingDescription(): WoT.ThingDescription { - return JSON.parse(TD.serializeTD(this)); + return JSON.parse(serializeTD(this)); } public emitEvent(name: string, data: WoT.InteractionInput): void { diff --git a/packages/core/src/helpers.ts b/packages/core/src/helpers.ts index 0514698ca..6fc75745f 100644 --- a/packages/core/src/helpers.ts +++ b/packages/core/src/helpers.ts @@ -29,7 +29,8 @@ import * as os from "os"; // imports for fetchTD import Servient from "./servient"; -import * as TD from "@node-wot/td-tools"; +import { ThingModelHelpers, Resolver } from "@thingweb/thing-model"; +import { Form, Thing, ThingInteraction } from "./thing-description"; import * as TDT from "wot-thing-description-types"; import { ContentSerdes } from "./content-serdes"; import Ajv, { ValidateFunction, ErrorObject } from "ajv"; @@ -37,8 +38,6 @@ import addFormats from "ajv-formats"; import TDSchema from "wot-thing-description-types/schema/td-json-schema-validation.json"; import { DataSchemaValue, ExposedThingInit } from "wot-typescript-definitions"; import { SomeJSONSchema } from "ajv/dist/types/json-schema"; -import { ThingInteraction, ThingModelHelpers } from "@node-wot/td-tools"; -import { Resolver } from "@node-wot/td-tools/src/resolver-interface"; import { PropertyElement, DataSchema } from "wot-thing-description-types"; import { createLoggers } from "./logger"; @@ -161,7 +160,7 @@ export default class Helpers implements Resolver { const client = this.srv.getClientFor(Helpers.extractScheme(uri)); debug(`WoTImpl fetching TD from '${uri}' with ${client}`); client - .readResource(new TD.Form(uri, ContentSerdes.TD)) + .readResource(new Form(uri, ContentSerdes.TD)) .then(async (content) => { if (content.type !== ContentSerdes.TD && content.type !== ContentSerdes.JSON_LD) { warn(`WoTImpl received TD with media type '${content.type}' from ${uri}`); @@ -318,7 +317,7 @@ export default class Helpers implements Resolver { } public static validateInteractionOptions( - thing: TD.Thing, + thing: Thing, ti: ThingInteraction, options?: WoT.InteractionOptions ): boolean { @@ -347,7 +346,7 @@ export default class Helpers implements Resolver { */ static parseUrlParameters( url: string | undefined, - globalUriVariables: { [key: string]: TD.DataSchema } = {}, + globalUriVariables: { [key: string]: DataSchema } = {}, uriVariables: { [k: string]: DataSchema } = {} ): Record { const params: Record = {}; diff --git a/packages/core/src/protocol-helpers.ts b/packages/core/src/protocol-helpers.ts index 55ed06547..e08889893 100644 --- a/packages/core/src/protocol-helpers.ts +++ b/packages/core/src/protocol-helpers.ts @@ -13,7 +13,7 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import * as TD from "@node-wot/td-tools"; +import { Form, ThingInteraction } from "./thing-description"; import { Readable } from "stream"; import { ReadableStream as PolyfillStream } from "web-streams-polyfill/ponyfill/es2018"; import { ActionElement, EventElement, PropertyElement } from "wot-thing-description-types"; @@ -62,7 +62,7 @@ function isManaged(obj: unknown): obj is IManagedStream { } export default class ProtocolHelpers { // set contentType (extend with more?) - public static updatePropertyFormWithTemplate(form: TD.Form, property: PropertyElement): void { + public static updatePropertyFormWithTemplate(form: Form, property: PropertyElement): void { for (const formTemplate of property.forms ?? []) { // 1. Try to find match with correct href scheme if (formTemplate.href) { @@ -76,7 +76,7 @@ export default class ProtocolHelpers { } } - public static updateActionFormWithTemplate(form: TD.Form, action: ActionElement): void { + public static updateActionFormWithTemplate(form: Form, action: ActionElement): void { for (const formTemplate of action.forms ?? []) { // 1. Try to find match with correct href scheme if (formTemplate.href) { @@ -90,7 +90,7 @@ export default class ProtocolHelpers { } } - public static updateEventFormWithTemplate(form: TD.Form, event: EventElement): void { + public static updateEventFormWithTemplate(form: Form, event: EventElement): void { for (const formTemplate of event.forms ?? []) { // 1. Try to find match with correct href scheme if (formTemplate.href) { @@ -259,7 +259,7 @@ export default class ProtocolHelpers { } public static findRequestMatchingFormIndex( - forms: TD.Form[] | undefined, + forms: Form[] | undefined, uriScheme: string, requestUrl: string | undefined, contentType?: string @@ -267,7 +267,7 @@ export default class ProtocolHelpers { if (forms === undefined) return 0; // first find forms with matching url protocol and path - let matchingForms: TD.Form[] = forms.filter((form) => { + let matchingForms: Form[] = forms.filter((form) => { // remove optional uriVariables from href Form const formUrl = new URL(form.href.replace(/(\{[\S]*\})/, "")); @@ -283,7 +283,7 @@ export default class ProtocolHelpers { }); // optionally try to match form's content type to the request's one if (contentType != null) { - const contentTypeMatchingForms: TD.Form[] = matchingForms.filter((form) => { + const contentTypeMatchingForms: Form[] = matchingForms.filter((form) => { return form.contentType === contentType; }); if (contentTypeMatchingForms.length > 0) matchingForms = contentTypeMatchingForms; @@ -292,7 +292,7 @@ export default class ProtocolHelpers { } public static getFormIndexForOperation( - interaction: TD.ThingInteraction, + interaction: ThingInteraction, type: "property" | "action" | "event", operationName?: | "writeproperty" @@ -355,7 +355,7 @@ export default class ProtocolHelpers { // If no form was found yet, loop through all forms if (interaction.forms !== undefined && finalFormIndex === -1) { if (operationName !== undefined) { - interaction.forms.every((form: TD.Form) => { + interaction.forms.every((form: Form) => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- operationName !== undefined if (form.op?.includes(operationName!) === true) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- interaction.forms !== undefined @@ -364,7 +364,7 @@ export default class ProtocolHelpers { return finalFormIndex === -1; }); } else { - interaction.forms.every((form: TD.Form) => { + interaction.forms.every((form: Form) => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- interaction.forms !== undefined finalFormIndex = interaction.forms!.indexOf(form); return false; diff --git a/packages/core/src/protocol-interfaces.ts b/packages/core/src/protocol-interfaces.ts index 44e41b007..05e56d801 100644 --- a/packages/core/src/protocol-interfaces.ts +++ b/packages/core/src/protocol-interfaces.ts @@ -13,10 +13,9 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import * as TD from "@node-wot/td-tools"; - import { Subscription } from "rxjs/Subscription"; +import { Form, SecurityScheme } from "./thing-description"; import Servient from "./servient"; import ExposedThing from "./exposed-thing"; import { Content } from "./content"; @@ -48,19 +47,19 @@ export type ListenerMap = Map; export interface ProtocolClient { /** this client is requested to perform a "read" on the resource with the given URI */ - readResource(form: TD.Form): Promise; + readResource(form: Form): Promise; /** this client is requested to perform a "write" on the resource with the given URI */ - writeResource(form: TD.Form, content: Content): Promise; + writeResource(form: Form, content: Content): Promise; /** this client is requested to perform an "invoke" on the resource with the given URI */ - invokeResource(form: TD.Form, content?: Content): Promise; + invokeResource(form: Form, content?: Content): Promise; /** this client is requested to perform an "unlink" on the resource with the given URI */ - unlinkResource(form: TD.Form): Promise; + unlinkResource(form: Form): Promise; subscribeResource( - form: TD.Form, + form: Form, next: (content: Content) => void, error?: (error: Error) => void, complete?: () => void @@ -82,7 +81,7 @@ export interface ProtocolClient { stop(): Promise; /** apply TD security metadata */ - setSecurity(metadata: Array, credentials?: unknown): boolean; + setSecurity(metadata: Array, credentials?: unknown): boolean; } export interface ProtocolClientFactory { diff --git a/packages/core/src/protocol-listener-registry.ts b/packages/core/src/protocol-listener-registry.ts index c3aed494c..b5575488f 100644 --- a/packages/core/src/protocol-listener-registry.ts +++ b/packages/core/src/protocol-listener-registry.ts @@ -14,7 +14,7 @@ ********************************************************************************/ import { DataSchema, InteractionInput } from "wot-typescript-definitions"; import { ContentListener } from "./protocol-interfaces"; -import { ThingInteraction } from "@node-wot/td-tools"; +import { ThingInteraction } from "./thing-description"; import contentSerdes from "./content-serdes"; export default class ProtocolListenerRegistry { diff --git a/packages/core/src/serdes.ts b/packages/core/src/serdes.ts new file mode 100644 index 000000000..81dabb642 --- /dev/null +++ b/packages/core/src/serdes.ts @@ -0,0 +1,297 @@ +/******************************************************************************** + * Copyright (c) 2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the W3C Software Notice and + * Document License (2015-05-13) which is available at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document. + * + * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 + ********************************************************************************/ + +import { Thing } from "./thing-description"; +import * as TD from "./thing-description"; +import { createLoggers } from "./logger"; + +import isAbsoluteUrl = require("is-absolute-url"); +import URLToolkit = require("url-toolkit"); +import { + ThingContext, + PropertyElement, + ActionElement, + EventElement, + ThingDescription, +} from "wot-thing-description-types"; + +const { debug, warn } = createLoggers("core", "serdes"); + +type AffordanceElement = PropertyElement | ActionElement | EventElement; + +/** + * Initializes the affordances field of a thing with an empty object if its + * type should be incorrect or undefined. + * + * This avoids potential errors that could occur due to an undefined + * affordance field. + * + * @param thing The Thing whose affordance field is being adjusted. + * @param affordanceKey The key of the affordance field. + */ +function adjustAffordanceField(thing: Thing, affordanceKey: string) { + const affordance = thing[affordanceKey]; + + if (typeof affordance !== "object" || affordance == null) { + thing[affordanceKey] = {}; + } +} + +function adjustBooleanField(affordance: AffordanceElement, key: string) { + const currentValue = affordance[key]; + + if (currentValue === undefined || typeof currentValue !== "boolean") { + affordance[key] = false; + } +} + +export function setContextLanguage(thing: ThingDescription, language: string, forceOverride: boolean): void { + // forceOverride == false -> set @language if no @language set + // forceOverride == true -> set/override @language in any case + if (Array.isArray(thing["@context"])) { + const arrayContext = thing["@context"]; + let languageSet = false; + for (const arrayEntry of arrayContext) { + if (typeof arrayEntry === "object") { + if ((arrayEntry as Record)["@language"] !== undefined) { + if (forceOverride) { + (arrayEntry as Record)["@language"] = language; + } + languageSet = true; + } + } + } + if (!languageSet) { + (arrayContext as unknown[]).push({ + "@language": language, + }); + } + } +} + +/** Parses a TD into a Thing object */ +export function parseTD(td: string, normalize?: boolean): Thing { + debug(`parseTD() parsing\n\`\`\`\n${td}\n\`\`\``); + + // remove a potential Byte Order Mark (BOM) + // see https://github.com/eclipse-thingweb/node-wot/issues/109 + const thing: Thing = JSON.parse(td.replace(/^\uFEFF/, "")); + + // apply defaults as per WoT Thing Description spec + + if (thing["@context"] === undefined) { + thing["@context"] = [TD.DEFAULT_CONTEXT_V1, TD.DEFAULT_CONTEXT_V11]; + } else if (Array.isArray(thing["@context"])) { + let semContext = thing["@context"] as Array; + const indexV1 = semContext.indexOf(TD.DEFAULT_CONTEXT_V1); + const indexV11 = semContext.indexOf(TD.DEFAULT_CONTEXT_V11); + if (indexV1 === -1 && indexV11 === -1) { + // insert default contexts as first entries + semContext.unshift(TD.DEFAULT_CONTEXT_V11); + semContext.unshift(TD.DEFAULT_CONTEXT_V1); + } else { + if (indexV1 !== -1 && indexV11 !== -1) { + // both default contexts are present (V1 & V11) + // -> remove both and add them to the top of the array + semContext = semContext.filter(function (e) { + return e !== TD.DEFAULT_CONTEXT_V1; + }); + semContext = semContext.filter(function (e) { + return e !== TD.DEFAULT_CONTEXT_V11; + }); + semContext.unshift(TD.DEFAULT_CONTEXT_V11); + semContext.unshift(TD.DEFAULT_CONTEXT_V1); + } else { + if (indexV1 !== -1 && indexV1 !== 0) { + // V1 present + semContext = semContext.filter(function (e) { + return e !== TD.DEFAULT_CONTEXT_V1; + }); + semContext.unshift(TD.DEFAULT_CONTEXT_V1); + } + if (indexV11 !== -1 && indexV11 !== 0) { + // V11 present + semContext = semContext.filter(function (e) { + return e !== TD.DEFAULT_CONTEXT_V11; + }); + semContext.unshift(TD.DEFAULT_CONTEXT_V11); + } + } + thing["@context"] = semContext as ThingContext; + } + } else if (thing["@context"] !== TD.DEFAULT_CONTEXT_V1 && thing["@context"] !== TD.DEFAULT_CONTEXT_V11) { + const semContext = thing["@context"]; + // insert default contexts as first entries + thing["@context"] = [TD.DEFAULT_CONTEXT_V1, TD.DEFAULT_CONTEXT_V11, semContext]; + } + // set @language to "en" if no @language available + setContextLanguage(thing, TD.DEFAULT_CONTEXT_LANGUAGE, false); + + if (thing["@type"] === undefined) { + thing["@type"] = TD.DEFAULT_THING_TYPE; + } else if (Array.isArray(thing["@type"])) { + const semTypes: Array = thing["@type"]; + if (semTypes.indexOf(TD.DEFAULT_THING_TYPE) === -1) { + // insert first + semTypes.unshift(TD.DEFAULT_THING_TYPE); + } + } else if (thing["@type"] !== TD.DEFAULT_THING_TYPE) { + const semType = thing["@type"]; + thing["@type"] = [TD.DEFAULT_THING_TYPE, semType]; + } + + for (const property of Object.values(thing.properties ?? {})) { + for (const key of ["readOnly", "writeOnly", "observable"]) { + adjustBooleanField(property, key); + } + } + + for (const action of Object.values(thing.actions ?? {})) { + for (const key of ["safe", "idempotent"]) { + adjustBooleanField(action, key); + } + } + + for (const affordanceKey of ["properties", "actions", "events"]) { + 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]; + } + + // collect all forms for normalization and use iterations also for checking + const allForms = []; + // properties + for (const [propName, prop] of Object.entries(thing.properties ?? {})) { + // ensure forms mandatory forms field + if (prop.forms == null) { + throw new Error(`Property '${propName}' has no forms field`); + } + for (const form of prop.forms) { + if (!form.href) { + throw new Error(`Form of Property '${propName}' has no href field`); + } + // check if base field required + if (!isAbsoluteUrl(form.href) && thing.base == null) + throw new Error(`Form of Property '${propName}' has relative URI while TD has no base field`); + // add + allForms.push(form); + } + } + // actions + for (const [actName, act] of Object.entries(thing.actions ?? {})) { + // ensure forms mandatory forms field + if (act.forms == null) { + throw new Error(`Action '${actName}' has no forms field`); + } + for (const form of act.forms) { + if (!form.href) { + throw new Error(`Form of Action '${actName}' has no href field`); + } + // check if base field required + if (!isAbsoluteUrl(form.href) && thing.base == null) + throw new Error(`Form of Action '${actName}' has relative URI while TD has no base field`); + // add + allForms.push(form); + } + } + // events + for (const [evtName, evt] of Object.entries(thing.events ?? {})) { + // ensure forms mandatory forms field + if (evt.forms == null) { + throw new Error(`Event '${evtName}' has no forms field`); + } + for (const form of evt.forms) { + if (!form.href) { + throw new Error(`Form of Event '${evtName}' has no href field`); + } + // check if base field required + if (!isAbsoluteUrl(form.href) && thing.base == null) + throw new Error(`Form of Event '${evtName}' has relative URI while TD has no base field`); + // add + allForms.push(form); + } + } + + if (Object.prototype.hasOwnProperty.call(thing, "base")) { + if (normalize === undefined || normalize === true) { + debug("parseTD() normalizing 'base' into 'forms'"); + + for (const form of allForms) { + if (!form.href.match(/^([a-z0-9+-.]+:).+/i)) { + debug(`parseTDString() applying base '${thing.base}' to '${form.href}'`); + form.href = URLToolkit.buildAbsoluteURL(thing.base, form.href); + } + } + } + } + + return thing; +} + +/** Serializes a Thing object into a TD */ +export function serializeTD(thing: Thing): string { + const copy: Thing = JSON.parse(JSON.stringify(thing)); + + // clean-ups + if (copy.security == null || copy.security.length === 0) { + copy.securityDefinitions = { + nosec_sc: { scheme: "nosec" }, + }; + copy.security = ["nosec_sc"]; + } + + if (copy.forms?.length === 0) { + delete copy.forms; + } + + if (copy.properties != null && Object.keys(copy.properties).length === 0) { + delete copy.properties; + } else if (copy.properties != null) { + // add mandatory fields (if missing): observable, writeOnly, and readOnly + for (const property of Object.values(copy.properties)) { + for (const key of ["readOnly", "writeOnly", "observable"]) { + adjustBooleanField(property, key); + } + } + } + + if (copy.actions != null && Object.keys(copy.actions).length === 0) { + delete copy.actions; + } else if (copy.actions != null) { + // add mandatory fields (if missing): idempotent and safe + for (const action of Object.values(copy.actions)) { + for (const key of ["safe", "idempotent"]) { + adjustBooleanField(action, key); + } + } + } + if (copy.events != null && Object.keys(copy.events).length === 0) { + delete copy.events; + } + + if (copy?.links.length === 0) { + delete copy.links; + } + + const td: string = JSON.stringify(copy); + + return td; +} diff --git a/packages/core/src/thing-description.ts b/packages/core/src/thing-description.ts new file mode 100644 index 000000000..61fc2433e --- /dev/null +++ b/packages/core/src/thing-description.ts @@ -0,0 +1,251 @@ +/******************************************************************************** + * Copyright (c) 2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the W3C Software Notice and + * Document License (2015-05-13) which is available at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document. + * + * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 + ********************************************************************************/ + +// global W3C WoT Scripting API definitions +import * as WoT from "wot-typescript-definitions"; +import * as TDT from "wot-thing-description-types"; + +export const DEFAULT_CONTEXT_V1 = "https://www.w3.org/2019/wot/td/v1"; +export const DEFAULT_CONTEXT_V11 = "https://www.w3.org/2022/wot/td/v1.1"; +export const DEFAULT_CONTEXT_LANGUAGE = "en"; +export const DEFAULT_THING_TYPE = "Thing"; + +/** Implements the Thing Description as software object */ +export class Thing implements TDT.ThingDescription { + title: TDT.Title; + securityDefinitions: { + [key: string]: TDT.SecurityScheme; + }; + + security: string | [string, ...string[]]; + + "@context": TDT.ThingContext; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; + + properties?: { [k: string]: TDT.PropertyElement } | undefined; + + actions?: { [k: string]: TDT.ActionElement } | undefined; + + events?: { [k: string]: TDT.EventElement } | undefined; + + constructor() { + this["@context"] = [DEFAULT_CONTEXT_V1, DEFAULT_CONTEXT_V11]; + this["@type"] = DEFAULT_THING_TYPE; + this.title = ""; + this.securityDefinitions = {}; + this.security = ""; + this.properties = {}; + this.actions = {}; + this.events = {}; + this.links = []; + } +} + +/** Basis from implementing the Thing Interaction descriptions for Property, Action, and Event */ +export type ThingInteraction = TDT.PropertyElement | TDT.ActionElement | TDT.EventElement; + +/** Implements the Interaction Form description */ +export class Form implements TDT.FormElementBase { + op?: string | string[]; + href: TDT.AnyUri; + contentType?: string; + contentCoding?: string; + subprotocol?: TDT.Subprotocol; + security?: TDT.Security; + scopes?: TDT.Scopes; + response?: TDT.ExpectedResponse; + additionalResponses?: TDT.AdditionalResponsesDefinition; + [k: string]: unknown; + + constructor(href: string, contentType?: string) { + this.href = href; + if (contentType != null) this.contentType = contentType; + } +} +export interface ExpectedResponse { + contentType?: string; +} + +export type DataSchema = WoT.DataSchema & + (BooleanSchema | IntegerSchema | NumberSchema | StringSchema | ObjectSchema | ArraySchema | NullSchema); + +export class BaseSchema { + type?: string; + title?: TDT.Title; + titles?: TDT.Titles; + description?: TDT.Description; + descriptions?: TDT.Descriptions; + writeOnly?: boolean; + readOnly?: boolean; + oneOf?: Array; + unit?: string; + const?: unknown; + enum?: Array; +} + +export interface BooleanSchema extends BaseSchema { + type: "boolean"; +} + +export interface IntegerSchema extends BaseSchema { + type: "integer"; + minimum?: number; + maximum?: number; +} + +export interface NumberSchema extends BaseSchema { + type: "number"; + minimum?: number; + maximum?: number; +} + +export interface StringSchema extends BaseSchema { + type: "string"; +} + +export interface ObjectSchema extends BaseSchema { + type: "object"; + properties: { [key: string]: DataSchema }; + required?: Array; +} + +export interface ArraySchema extends BaseSchema { + type: "array"; + items: DataSchema; + minItems?: number; + maxItems?: number; +} + +export interface NullSchema extends BaseSchema { + type: "null"; +} + +// TODO AutoSecurityScheme +// TODO ComboSecurityScheme +export type SecurityType = + | NoSecurityScheme + | BasicSecurityScheme + | DigestSecurityScheme + | BearerSecurityScheme + | APIKeySecurityScheme + | OAuth2SecurityScheme + | PSKSecurityScheme; + +export interface SecurityScheme { + scheme: string; + description?: TDT.Description; + descriptions?: TDT.Descriptions; + proxy?: TDT.AnyUri; + [k: string]: unknown; +} + +export interface NoSecurityScheme extends SecurityScheme, TDT.NoSecurityScheme { + scheme: "nosec"; +} + +export interface BasicSecurityScheme extends SecurityScheme, TDT.BasicSecurityScheme { + scheme: "basic"; +} + +export interface DigestSecurityScheme extends SecurityScheme, TDT.DigestSecurityScheme { + scheme: "digest"; +} + +export interface APIKeySecurityScheme extends SecurityScheme, TDT.ApiKeySecurityScheme { + scheme: "apikey"; +} + +export interface BearerSecurityScheme extends SecurityScheme, TDT.BearerSecurityScheme { + scheme: "bearer"; +} + +export interface PSKSecurityScheme extends SecurityScheme, TDT.PskSecurityScheme { + scheme: "psk"; +} + +export interface OAuth2SecurityScheme extends SecurityScheme, TDT.OAuth2SecurityScheme { + scheme: "oauth2"; +} + +/** Implements the Thing Property description */ +export abstract class ThingProperty extends BaseSchema { + // writable: boolean; + observable?: boolean; + type?: string; + + // ThingInteraction + forms?: Array; + title?: TDT.Title; + titles?: TDT.Titles; + description?: TDT.Description; + descriptions?: TDT.Descriptions; + scopes?: Array; + uriVariables?: { + [key: string]: DataSchema; + }; + + security?: Array; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; +} + +/** Implements the Thing Action description */ +export abstract class ThingAction { + input?: DataSchema; + output?: DataSchema; + safe?: boolean; + idempotent?: boolean; + + // ThingInteraction + forms?: Array; + title?: TDT.Title; + titles?: TDT.Titles; + description?: TDT.Description; + descriptions?: TDT.Descriptions; + scopes?: Array; + uriVariables?: { + [key: string]: DataSchema; + }; + + security?: Array; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; +} +/** Implements the Thing Event description */ +export abstract class ThingEvent { + subscription?: DataSchema; + data?: DataSchema; + cancellation?: DataSchema; + + // ThingInteraction + forms?: Array; + title?: TDT.Title; + titles?: TDT.Titles; + description?: TDT.Description; + descriptions?: TDT.Descriptions; + scopes?: Array; + uriVariables?: { + [key: string]: DataSchema; + }; + + security?: Array; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; +} diff --git a/packages/core/src/wot-impl.ts b/packages/core/src/wot-impl.ts index 4d878ed5e..2228bad35 100644 --- a/packages/core/src/wot-impl.ts +++ b/packages/core/src/wot-impl.ts @@ -14,7 +14,8 @@ ********************************************************************************/ import * as WoT from "wot-typescript-definitions"; -import * as TD from "@node-wot/td-tools"; +import { ThingModel } from "wot-thing-model-types"; +import { parseTD } from "./serdes"; import Servient from "./servient"; import ExposedThing from "./exposed-thing"; import ConsumedThing from "./consumed-thing"; @@ -102,8 +103,8 @@ export default class WoTImpl { /** @inheritDoc */ async consume(td: WoT.ThingDescription): Promise { try { - const thing = TD.parseTD(JSON.stringify(td), true); - const newThing: ConsumedThing = new ConsumedThing(this.srv, thing); + const thing = parseTD(JSON.stringify(td), true); + const newThing: ConsumedThing = new ConsumedThing(this.srv, thing as ThingModel); debug( `WoTImpl consuming TD ${ diff --git a/packages/core/test/ClientTest.ts b/packages/core/test/ClientTest.ts index ec7bfecac..87f98fb38 100644 --- a/packages/core/test/ClientTest.ts +++ b/packages/core/test/ClientTest.ts @@ -27,7 +27,7 @@ import { Subscription } from "rxjs/Subscription"; import Servient from "../src/servient"; import ConsumedThing from "../src/consumed-thing"; -import { Form, SecurityScheme } from "@node-wot/td-tools"; +import { Form, SecurityScheme } from "../src/thing-description"; import { ProtocolClient, ProtocolClientFactory } from "../src/protocol-interfaces"; import { Content } from "../src/content"; import { ContentSerdes } from "../src/content-serdes"; diff --git a/packages/core/test/DiscoveryTest.ts b/packages/core/test/DiscoveryTest.ts index c00444b2d..0700f6124 100644 --- a/packages/core/test/DiscoveryTest.ts +++ b/packages/core/test/DiscoveryTest.ts @@ -13,8 +13,8 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import { Form, SecurityScheme } from "@node-wot/td-tools"; import { Subscription } from "rxjs/Subscription"; +import { Form, SecurityScheme } from "../src/thing-description"; import { Content } from "../src/content"; import { createLoggers } from "../src/logger"; import { ProtocolClient, ProtocolClientFactory } from "../src/protocol-interfaces"; diff --git a/packages/core/test/ProtocolHelpersTest.ts b/packages/core/test/ProtocolHelpersTest.ts index 5cc0a9a45..4ae26ec96 100644 --- a/packages/core/test/ProtocolHelpersTest.ts +++ b/packages/core/test/ProtocolHelpersTest.ts @@ -21,7 +21,7 @@ */ import { suite, test } from "@testdeck/mocha"; -import * as TD from "@node-wot/td-tools"; +import { Form } from "../src/thing-description"; import { expect } from "chai"; import { ProtocolHelpers } from ".."; import { ActionElement, EventElement, PropertyElement } from "wot-thing-description-types"; @@ -29,7 +29,7 @@ import { ActionElement, EventElement, PropertyElement } from "wot-thing-descript @suite("Protocol Helpers") class ProtocolHelpersTest { @test "should get form index "() { - const forms: TD.Form[] = [ + const forms: Form[] = [ { href: "http://example.com/test/properties/test1?param=test", contentType: "text/plain", diff --git a/packages/core/test/ProtocolListenerRegistryTest.ts b/packages/core/test/ProtocolListenerRegistryTest.ts index 8ea76df11..f14619aa9 100644 --- a/packages/core/test/ProtocolListenerRegistryTest.ts +++ b/packages/core/test/ProtocolListenerRegistryTest.ts @@ -13,7 +13,7 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import { ThingInteraction } from "@node-wot/td-tools"; +import { ThingInteraction } from "../src/thing-description"; import { suite, test } from "@testdeck/mocha"; import { expect, should, spy, use as chaiUse } from "chai"; import spies from "chai-spies"; diff --git a/packages/core/test/SerDesTest.ts b/packages/core/test/SerDesTest.ts new file mode 100644 index 000000000..fe48bf536 --- /dev/null +++ b/packages/core/test/SerDesTest.ts @@ -0,0 +1,862 @@ +/******************************************************************************** + * Copyright (c) 2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the W3C Software Notice and + * Document License (2015-05-13) which is available at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document. + * + * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 + ********************************************************************************/ + +/** + * Basic test suite for TD parsing + */ + +import { suite, test } from "@testdeck/mocha"; +import { expect, should, assert } from "chai"; + +import { + Thing, + DEFAULT_CONTEXT_V1, + DEFAULT_CONTEXT_V11, + DEFAULT_THING_TYPE, + DEFAULT_CONTEXT_LANGUAGE, +} from "../src/thing-description"; + +/* +import * as TDParser from "../src/td-parser"; +import * as TDHelpers from "../src/td-helpers"; +*/ + +import { parseTD, serializeTD, setContextLanguage } from "../src/serdes"; + +import { createLoggers } from "../src/core"; + +const { debug } = createLoggers("core", "SerDesTest"); + +// should must be called to augment all variables +should(); + +/** sample TD json-ld string from the CP page */ +const tdSample1 = `{ + "title": "MyTemperatureThing", + "properties": { + "temperature": { + "type": "number", + "forms": [{ + "href": "coap://mytemp.example.com:5683/temp", + "contentType": "application/json" + }] + } + } +}`; +/** sample TD json-ld string from the CP page */ +const tdSample2 = `{ + "@type": ["Thing"], + "title": "MyTemperatureThing2", + "properties": { + "temperature": { + "type": "number", + "observable": false, + "forms": [{ + "href": "coap://mytemp.example.com:5683/temp", + "contentType": "application/json" + }] + } + } +}`; +/** sample TD json-ld string from the CP page */ +const tdSample3 = `{ + "@context": ["https://www.w3.org/2019/wot/td/v1"], + "@type": ["Thing"], + "title": "MyTemperatureThing3", + "base": "coap://mytemp.example.com:5683/interactions/", + "properties": { + "temperature": { + "type": "number", + "observable": false, + "forms": [{ + "href": "temp", + "contentType": "application/json" + }] + }, + "temperature2": { + "type": "number", + "readOnly": true, + "observable": false, + "forms": [{ + "href": "./temp", + "contentType": "application/json" + }] + }, + "humidity": { + "type": "number", + "readOnly": false, + "forms": [{ + "href": "/humid", + "contentType": "application/json" + }] + } + }, + "actions": { + "reset": { + "forms": [{ + "href": "/actions/reset" + }] + } + }, + "events": { + "update": { + "forms": [{ + "href": "events/update" + }] + } + } +}`; + +/** sample TD json-ld string from the CP page */ +const tdSampleLemonbeatBurlingame = `{ + "@context": [ + "https://www.w3.org/2019/wot/td/v1", + { + "actuator": "http://example.org/actuator#", + "sensor": "http://example.org/sensors#" + } + ], + "@type": ["Thing"], + "title": "LemonbeatThings", + "base": "http://192.168.1.176:8080/", + "properties": { + "luminance": { + "@type": ["sensor:luminance"], + "sensor:unit": "sensor:Candela", + "type": "number", + "readOnly": true, + "observable": true, + "forms": [{ + "href" : "sensors/luminance", + "contentType": "application/json" + }] + }, + "humidity": { + "@type": ["sensor:humidity"], + "sensor:unit": "sensor:Percent", + type": "number", + "readOnly": true, + "observable": true, + "forms": [{ + "href" : "sensors/humidity", + "contentType": "application/json" + }] + }, + "temperature": { + "@type": ["sensor:temperature"], + "sensor:unit": "sensor:Celsius", + "type": "number", + "readOnly": true, + "observable": true, + "forms": [{ + "href" : "sensors/temperature", + "contentType": "application/json" + }] + }, + "status": { + "@type": ["actuator:onOffStatus"], + "type": "boolean", + "readOnly": true, + "observable": true, + "forms": [{ + "href" : "fan/status", + "contentType": "application/json" + }] + } + }, + "actions": { + "turnOn": { + "@type": ["actuator:turnOn"], + "forms": [{ + "href" : "fan/turnon", + "contentType": "application/json" + }] + }, + "turnOff": { + "@type": ["actuator:turnOff"], + "forms": [{ + "href" : "fan/turnoff", + "contentType": "application/json" + }] + } + } +}`; + +/** sample metadata TD */ +const tdSampleMetadata1 = `{ + "@context": ["https://www.w3.org/2019/wot/td/v1"], + "@type": ["Thing"], + "reference": "myTempThing", + "title": "MyTemperatureThing3", + "base": "coap://mytemp.example.com:5683/interactions/", + "properties": { + "myTemp": { + "@type": ["Temperature"], + "unit": "celsius", + "reference": "threshold", + "schema": { + "type": "number" + }, + "readOnly": true, + "forms": [{ + "href": "temp", + "contentType": "application/json" + }] + } + } +}`; + +/** Simplified TD */ +const tdSimple1 = `{ + "@context": "https://www.w3.org/2019/wot/td/v1", + "id": "urn:dev:wot:com:example:servient:lamp", + "title": "MyLampThing", + "properties": { + "status": { + "readOnly": true, + "observable": false, + "type": "string", + "forms": [{ + "href": "coaps://mylamp.example.com:5683/status", + "contentType": "application/json" + }] + }}, + "actions": { + "toggle": { + "forms": [{ + "href": "coaps://mylamp.example.com:5683/toggle", + "contentType": "application/json" + }]}}, + "events": { + "overheating": { + "type": "string", + "forms": [{ + "href": "coaps://mylamp.example.com:5683/oh", + "contentType": "application/json" + }] + }} +}`; + +const tdSimple11 = `{ + "@context": "https://www.w3.org/2022/wot/td/v1.1", + "id": "urn:dev:wot:com:example:servient:lamp", + "title": "MyLampThing", + "properties": { + "status": { + "readOnly": true, + "observable": false, + "type": "string", + "forms": [{ + "href": "coaps://mylamp.example.com:5683/status", + "contentType": "application/json" + }] + }}, + "actions": { + "toggle": { + "forms": [{ + "href": "coaps://mylamp.example.com:5683/toggle", + "contentType": "application/json" + }]}}, + "events": { + "overheating": { + "type": "string", + "forms": [{ + "href": "coaps://mylamp.example.com:5683/oh", + "contentType": "application/json" + }] + }} +}`; + +/** Broken TDs */ +const tdBroken1 = `{ + "@context": "https://www.w3.org/2019/wot/td/v1", + "id": "urn:dev:wot:com:example:servient:lamp", + "title": "MyLampThing", + "properties": { + "status": { + "readOnly": true, + "observable": false, + "type": "string", + "form": [{ + "href": "coaps://mylamp.example.com:5683/status", + "contentType": "application/json" + }] + }}, + "actions": { + "toggle": { + "forms": [{ + "href": "coaps://mylamp.example.com:5683/toggle", + "contentType": "application/json" + }]}}, + "events": { + "overheating": { + "type": "string", + "forms": [{ + "href": "coaps://mylamp.example.com:5683/oh", + "mediaType": "application/json" + }] + }} +}`; +const tdBroken2 = `{ + "id": "urn:dev:wot:com:example:servient:lamp", + "title": "MyLampThing", + "properties": { + "status": { + "readOnly": true, + "observable": false, + "type": "string", + "forms": [{ + "href": "coaps://mylamp.example.com:5683/status", + "mediaType": "application/json" + }] + }}, + "actions": { + "toggle": { + "forms": [{ + "mediaType": "application/json" + }]}}, + "events": { + "overheating": { + "type": "string", + "forms": [{ + "href": "coaps://mylamp.example.com:5683/oh", + "mediaType": "application/json" + }] + }} +}`; +const tdBroken3 = `{ + "id": "urn:dev:wot:com:example:servient:lamp", + "title": "MyLampThing", + "properties": { + "status": { + "readOnly": true, + "observable": false, + "type": "string", + "forms": [{ + "href": "coaps://mylamp.example.com:5683/status", + "mediaType": "application/json" + }] + }}, + "actions": { + "toggle": { + "forms": [{ + "href": "coaps://mylamp.example.com:5683/toggle", + "mediaType": "application/json" + }]}}, + "events": { + "overheating": { + "type": "string", + "forms": [{ + "href": "oh", + "mediaType": "application/json" + }] + }} +}`; + +@suite("TD parsing/serialising") +class TDParserTest { + @test "should insert context"() { + const testTD = `{ "title": "NoContext" }`; + const thing: Thing = parseTD(testTD); + + debug(`${thing}`); + + expect(thing).to.have.property("@context").that.has.length(3); + expect(thing["@context"][0]).to.equal(DEFAULT_CONTEXT_V1); + expect(thing["@context"][1]).to.equal(DEFAULT_CONTEXT_V11); + expect(thing["@context"][2]).to.have.property("@language").that.equals(DEFAULT_CONTEXT_LANGUAGE); + expect(thing).to.have.property("@type").that.equals(DEFAULT_THING_TYPE); + } + + @test "should not ovverride existing @language in context"() { + const testTD = `{ "title": "NoContext", + "@context": ["https://www.w3.org/2019/wot/td/v1", { + "iot": "http://example.org/iot" + }, + { "@language" : "de" } + ] + }`; + const thing: Thing = parseTD(testTD); + + debug(`${thing}`); + + expect(thing).to.have.property("@context").that.has.length(3); + expect(thing["@context"][0]).to.equal(DEFAULT_CONTEXT_V1); + expect(thing["@context"][1]).to.have.property("iot").that.equals("http://example.org/iot"); + expect(thing["@context"][2]).to.have.property("@language").that.equals("de"); + expect(thing).to.have.property("@type").that.equals(DEFAULT_THING_TYPE); + } + + @test "should ovverride existing @language in context"() { + const testTD = `{ "title": "NoContext", + "@context": ["https://www.w3.org/2019/wot/td/v1", { + "iot": "http://example.org/iot" + }, + { "@language" : "de" } + ] + }`; + const thing: Thing = parseTD(testTD); + setContextLanguage(thing, "en", true); + + debug(`${thing}`); + + expect(thing).to.have.property("@context").that.has.length(3); + expect(thing["@context"][0]).to.equal(DEFAULT_CONTEXT_V1); + expect(thing["@context"][1]).to.have.property("iot").that.equals("http://example.org/iot"); + expect(thing["@context"][2]).to.have.property("@language").that.equals("en"); + expect(thing).to.have.property("@type").that.equals(DEFAULT_THING_TYPE); + } + + @test "should add context to single string"() { + const testTD = `{ "title": "OtherContext", "@context": "http://iot.schema.org/", "@type": "iot:Sensor" }`; + const thing: Thing = parseTD(testTD); + + debug(`${thing}`); + + expect(thing).to.have.property("@context").that.has.length(4); + expect(thing["@context"][0]).to.equal(DEFAULT_CONTEXT_V1); + expect(thing["@context"][1]).to.equal(DEFAULT_CONTEXT_V11); + expect(thing["@context"][2]).to.equal("http://iot.schema.org/"); + expect(thing["@context"][3]).to.have.property("@language").that.equals(DEFAULT_CONTEXT_LANGUAGE); + + expect(thing).to.have.property("@type").that.has.length(2); + expect(thing["@type"][0]).to.equal(DEFAULT_THING_TYPE); + expect(thing["@type"][1]).to.equal("iot:Sensor"); + } + + @test "should add context to array"() { + const testTD = `{ "title": "OtherContext", "@context": ["http://iot.schema.org/"], "@type": ["iot:Sensor"] }`; + const thing: Thing = parseTD(testTD); + + debug(`${thing}`); + + expect(thing).to.have.property("@context").that.has.length(4); + expect(thing["@context"][0]).to.equal(DEFAULT_CONTEXT_V1); + expect(thing["@context"][1]).to.equal(DEFAULT_CONTEXT_V11); + expect(thing["@context"][2]).to.equal("http://iot.schema.org/"); + expect(thing["@context"][3]).to.have.property("@language").that.equals(DEFAULT_CONTEXT_LANGUAGE); + + expect(thing).to.have.property("@type").that.has.length(2); + expect(thing["@type"][0]).to.equal(DEFAULT_THING_TYPE); + expect(thing["@type"][1]).to.equal("iot:Sensor"); + } + + @test "should move default context v1 to be first item in array"() { + const testTD = `{ "title": "OtherContext", "@context": ["http://iot.schema.org/", "https://www.w3.org/2019/wot/td/v1"], "@type": ["iot:Sensor"] }`; + const thing: Thing = parseTD(testTD); + + debug(`${thing}`); + + expect(thing).to.have.property("@context").that.has.length(3); + expect(thing["@context"][0]).to.equal(DEFAULT_CONTEXT_V1); + expect(thing["@context"][1]).to.equal("http://iot.schema.org/"); + expect(thing["@context"][2]).to.have.property("@language").that.equals(DEFAULT_CONTEXT_LANGUAGE); + } + + @test "should move default context v1.1 to be first item in array"() { + const testTD = `{ "title": "OtherContext", "@context": ["http://iot.schema.org/", "https://www.w3.org/2022/wot/td/v1.1"], "@type": ["iot:Sensor"] }`; + const thing: Thing = parseTD(testTD); + + debug(`${thing}`); + + expect(thing).to.have.property("@context").that.has.length(3); + expect(thing["@context"][0]).to.equal(DEFAULT_CONTEXT_V11); + expect(thing["@context"][1]).to.equal("http://iot.schema.org/"); + expect(thing["@context"][2]).to.have.property("@language").that.equals(DEFAULT_CONTEXT_LANGUAGE); + } + + @test "should move both default contexts to be first items in array"() { + const testTD = `{ "title": "OtherContext", "@context": ["http://iot.schema.org/", "https://www.w3.org/2022/wot/td/v1.1", "https://www.w3.org/2019/wot/td/v1"], "@type": ["iot:Sensor"] }`; + const thing: Thing = parseTD(testTD); + + debug(`${thing}`); + + expect(thing).to.have.property("@context").that.has.length(4); + expect(thing["@context"][0]).to.equal(DEFAULT_CONTEXT_V1); + expect(thing["@context"][1]).to.equal(DEFAULT_CONTEXT_V11); + expect(thing["@context"][2]).to.equal("http://iot.schema.org/"); + expect(thing["@context"][3]).to.have.property("@language").that.equals(DEFAULT_CONTEXT_LANGUAGE); + } + + @test "should add context to object"() { + const testTD = `{ "title": "OtherContext", "@context": { "iot": "http://iot.schema.org/" } }`; + const thing: Thing = parseTD(testTD); + + debug(`${thing}`); + + expect(thing).to.have.property("@context").that.has.length(4); + expect(thing["@context"][0]).to.equal(DEFAULT_CONTEXT_V1); + expect(thing["@context"][1]).to.equal(DEFAULT_CONTEXT_V11); + expect(thing["@context"][2]).to.have.property("iot"); + expect(thing["@context"][3]).to.have.property("@language").that.equals(DEFAULT_CONTEXT_LANGUAGE); + } + + @test "should parse the example from spec"() { + const thing: Thing = parseTD(tdSample1); + + expect(thing).to.have.property("@context").that.has.length(3); + expect(thing["@context"][0]).to.equal(DEFAULT_CONTEXT_V1); + expect(thing["@context"][1]).to.equal(DEFAULT_CONTEXT_V11); + expect(thing["@context"][2]).to.have.property("@language").that.equals(DEFAULT_CONTEXT_LANGUAGE); + expect(thing).to.have.property("@type").that.equals("Thing"); + expect(thing).to.have.property("title").that.equals("MyTemperatureThing"); + expect(thing).to.not.have.property("base"); + + const thingProperties = thing.properties!; + expect(thingProperties).to.have.property("temperature"); + expect(thingProperties.temperature).to.have.property("readOnly").that.equals(false); + expect(thingProperties.temperature).to.have.property("observable").that.equals(false); + + expect(thingProperties.temperature).to.have.property("forms").to.have.lengthOf(1); + expect(thingProperties.temperature.forms[0]).to.have.property("contentType").that.equals("application/json"); + expect(thingProperties.temperature.forms[0]) + .to.have.property("href") + .that.equals("coap://mytemp.example.com:5683/temp"); + } + + @test "should parse writable Property"() { + const thing: Thing = parseTD(tdSample2); + + expect(thing).to.have.property("@context").that.contains(DEFAULT_CONTEXT_V1); + expect(thing).to.have.property("@type").that.has.lengthOf(1); + expect(thing).to.have.property("@type").that.contains("Thing"); + expect(thing).to.have.property("title").that.equals("MyTemperatureThing2"); + expect(thing).to.not.have.property("base"); + + const thingProperties = thing.properties!; + expect(thingProperties).to.have.property("temperature"); + expect(thingProperties.temperature).to.have.property("readOnly").that.equals(false); + expect(thingProperties.temperature).to.have.property("observable").that.equals(false); + + expect(thingProperties.temperature).to.have.property("forms").to.have.lengthOf(1); + expect(thingProperties.temperature.forms[0]).to.have.property("contentType").that.equals("application/json"); + expect(thingProperties.temperature.forms[0]) + .to.have.property("href") + .that.equals("coap://mytemp.example.com:5683/temp"); + } + + @test "should parse TD with base field"() { + const thing: Thing = parseTD(tdSample3); + + expect(thing).to.have.property("@context").that.has.lengthOf(2); + expect(thing).to.have.property("@context").contains(DEFAULT_CONTEXT_V1); + expect(thing["@context"][1]).to.have.property("@language").that.equals(DEFAULT_CONTEXT_LANGUAGE); + expect(thing).to.have.property("title").that.equals("MyTemperatureThing3"); + expect(thing).to.have.property("base").that.equals("coap://mytemp.example.com:5683/interactions/"); + + const thingProperties = thing.properties!; + expect(thingProperties).to.have.property("temperature"); + expect(thingProperties.temperature).to.have.property("readOnly").that.equals(false); + expect(thingProperties.temperature).to.have.property("observable").that.equals(false); + expect(thingProperties.temperature).to.have.property("forms").to.have.lengthOf(1); + expect(thingProperties.temperature.forms[0]).to.have.property("contentType").that.equals("application/json"); + + expect(thingProperties).to.have.property("temperature2"); + expect(thingProperties.temperature2).to.have.property("readOnly").that.equals(true); + expect(thingProperties.temperature2).to.have.property("observable").that.equals(false); + expect(thingProperties.temperature2).to.have.property("forms").to.have.lengthOf(1); + expect(thingProperties.temperature2.forms[0]).to.have.property("contentType").that.equals("application/json"); + + expect(thingProperties).to.have.property("humidity"); + expect(thingProperties.humidity).to.have.property("readOnly").that.equals(false); + expect(thingProperties.humidity).to.have.property("observable").that.equals(false); + expect(thingProperties.humidity).to.have.property("forms").to.have.lengthOf(1); + expect(thingProperties.humidity.forms[0]).to.have.property("contentType").that.equals("application/json"); + } + + // TODO: wait for exclude https://github.com/chaijs/chai/issues/885 + @test.skip "should return equivalent TD in round-trips"() { + // sample1 + const thing1: Thing = parseTD(tdSample1); + const newJson1 = serializeTD(thing1); + + let jsonExpected = JSON.parse(tdSample1); + let jsonActual = JSON.parse(newJson1); + + expect(jsonActual).to.deep.equal(jsonExpected); + + // sample2 + const thing2: Thing = parseTD(tdSample2); + const newJson2 = serializeTD(thing2); + + jsonExpected = JSON.parse(tdSample2); + jsonActual = JSON.parse(newJson2); + + expect(jsonActual).to.deep.equal(jsonExpected); + + // sample3 + // Note: avoid href normalization in this test-case + // "href": "coap://mytemp.example.com:5683/interactions/temp" vs "href": "temp" + const thing3: Thing = parseTD(tdSample3, false); + const newJson3 = serializeTD(thing3); + + jsonExpected = JSON.parse(tdSample3); + jsonActual = JSON.parse(newJson3); + // TODO how to compare best differences in observable/writable false compared to not present ? + // expect(jsonActual).to.deep.equal(jsonExpected); + // sampleLemonbeatBurlingame + // Note: avoid href normalization in this test-case + const tdLemonbeatBurlingame: Thing = parseTD(tdSampleLemonbeatBurlingame, false); + + const newJsonLemonbeatBurlingame = serializeTD(tdLemonbeatBurlingame); + + jsonExpected = JSON.parse(tdSampleLemonbeatBurlingame); + jsonActual = JSON.parse(newJsonLemonbeatBurlingame); + + expect(jsonActual).to.deep.equal(jsonExpected); + } + + @test "should parse and serialize metadata fields"() { + // parse TD + const thing: Thing = parseTD(tdSampleMetadata1); + + expect(thing).to.have.property("@context").that.has.lengthOf(2); + expect(thing).to.have.property("@context").contains(DEFAULT_CONTEXT_V1); + expect(thing["@context"][1]).to.have.property("@language").that.equals(DEFAULT_CONTEXT_LANGUAGE); + expect(thing).to.have.property("title").that.equals("MyTemperatureThing3"); + expect(thing).to.have.property("base").that.equals("coap://mytemp.example.com:5683/interactions/"); + + // thing metadata "reference": "myTempThing" in metadata + expect(thing).to.have.property("reference").that.equals("myTempThing"); + + const thingProperties = thing.properties!; + expect(thingProperties).to.have.property("myTemp"); + expect(thingProperties.myTemp).to.have.property("readOnly").that.equals(true); + expect(thingProperties.myTemp).to.have.property("observable").that.equals(false); + expect(thingProperties.myTemp).to.have.property("forms").to.have.lengthOf(1); + expect(thingProperties.myTemp.forms[0]).to.have.property("contentType").that.equals("application/json"); + + // metadata + // metadata "unit": "celsius" + expect(thingProperties.myTemp).to.have.property("unit").that.equals("celsius"); + // metadata "reference": "threshold" + expect(thingProperties.myTemp).to.have.property("reference").that.equals("threshold"); + + // serialize + // const newJson = TDParser.serializeTD(thing); + // TODO JSON.parse() and expect + } + + @test "should normalize forms with base"() { + const thing: Thing = parseTD(tdSample3); + + expect(thing).to.have.property("base").that.equals("coap://mytemp.example.com:5683/interactions/"); + + const thingProperties = thing.properties!; + expect(thingProperties.temperature.forms[0]) + .to.have.property("href") + .that.equals("coap://mytemp.example.com:5683/interactions/temp"); + expect(thingProperties.temperature2.forms[0]) + .to.have.property("href") + .that.equals("coap://mytemp.example.com:5683/interactions/temp"); + expect(thingProperties.humidity.forms[0]) + .to.have.property("href") + .that.equals("coap://mytemp.example.com:5683/humid"); + + expect(thing.actions!.reset.forms[0]) + .to.have.property("href") + .that.equals("coap://mytemp.example.com:5683/actions/reset"); + + expect(thing.events!.update.forms[0]) + .to.have.property("href") + .that.equals("coap://mytemp.example.com:5683/interactions/events/update"); + } + + @test "simplified TD 1"() { + const thing: Thing = parseTD(tdSimple1); + + // simple elements + expect(thing).to.have.property("@context").that.equals(DEFAULT_CONTEXT_V1); + expect(thing.id).equals("urn:dev:wot:com:example:servient:lamp"); + expect(thing.title).equals("MyLampThing"); + + // interaction arrays + expect(thing).to.have.property("properties"); + expect(thing).to.have.property("actions"); + expect(thing).to.have.property("events"); + + debug(`${thing["@context"]}`); + + const thingProperties = thing.properties!; + expect(thingProperties).to.have.property("status"); + expect(thingProperties.status.readOnly).equals(true); + expect(thingProperties.status.observable).equals(false); + } + + @test "simplified TD 1.1"() { + const thing: Thing = parseTD(tdSimple11); + + // simple elements + expect(thing).to.have.property("@context").that.equals(DEFAULT_CONTEXT_V11); + expect(thing.id).equals("urn:dev:wot:com:example:servient:lamp"); + expect(thing.title).equals("MyLampThing"); + + // interaction arrays + expect(thing).to.have.property("properties"); + expect(thing).to.have.property("actions"); + expect(thing).to.have.property("events"); + + debug(`${thing["@context"]}`); + + const thingProperties = thing.properties!; + expect(thingProperties).to.have.property("status"); + expect(thingProperties.status.readOnly).equals(true); + expect(thingProperties.status.observable).equals(false); + } + + @test "should detect broken TDs"() { + assert.throws( + () => { + parseTD(tdBroken1); + }, + Error, + "Property 'status' has no forms field" + ); + assert.throws( + () => { + parseTD(tdBroken2); + }, + Error, + "Form of Action 'toggle' has no href field" + ); + assert.throws( + () => { + parseTD(tdBroken3); + }, + Error, + "Form of Event 'overheating' has relative URI while TD has no base field" + ); + } + + @test "uriVarables in combination with and without http base"() { + // see https://github.com/eclipse-thingweb/node-wot/issues/97 + + const tdTest = `{ + "@context": "https://www.w3.org/2019/wot/td/v1", + "id": "urn:dev:wot:com:example:servient:urivarables", + "base": "coap://localhost:8080/uv/", + "title": "UriVarables", + "properties": { + "without": { + "readOnly": true, + "observable": false, + "type": "string", + "forms": [{ + "href": "coap://localhost:8080/uv/without{?step}", + "contentType": "application/json" + }] + }, + "with1": { + "readOnly": true, + "observable": false, + "type": "string", + "forms": [{ + "href": "with1{?step}", + "contentType": "application/json" + }] + }, + "with2": { + "readOnly": true, + "observable": false, + "type": "string", + "forms": [{ + "href": "with2{?step,a}", + "contentType": "application/json" + }] + } + } + }`; + + const thing: Thing = parseTD(tdTest); + + // simple elements + expect(thing).to.have.property("@context").that.equals(DEFAULT_CONTEXT_V1); + expect(thing.id).equals("urn:dev:wot:com:example:servient:urivarables"); + expect(thing.title).equals("UriVarables"); + + // interaction arrays + expect(thing).to.have.property("properties"); + + const thingProperties = thing.properties!; + + expect(thingProperties).to.have.property("without"); + expect(thingProperties.without.forms[0].href).equals("coap://localhost:8080/uv/" + "without{?step}"); + + expect(thingProperties).to.have.property("with1"); + expect(thingProperties.with1.forms[0].href).equals("coap://localhost:8080/uv/" + "with1{?step}"); + + expect(thingProperties).to.have.property("with2"); + expect(thingProperties.with2.forms[0].href).equals("coap://localhost:8080/uv/" + "with2{?step,a}"); + } + + @test "uriVarables in combination with and without coap base"() { + const tdTest = `{ + "@context": "https://www.w3.org/2019/wot/td/v1", + "id": "urn:dev:wot:com:example:servient:urivarables", + "base": "http://localhost:8080/uv/", + "title": "UriVarables", + "properties": { + "without": { + "readOnly": true, + "observable": false, + "type": "string", + "forms": [{ + "href": "http://localhost:8080/uv/without{?step}", + "contentType": "application/json" + }] + }, + "with1": { + "readOnly": true, + "observable": false, + "type": "string", + "forms": [{ + "href": "with1{?step}", + "contentType": "application/json" + }] + }, + "with2": { + "readOnly": true, + "observable": false, + "type": "string", + "forms": [{ + "href": "with2{?step,a}", + "contentType": "application/json" + }] + } + } + }`; + + const thing: Thing = parseTD(tdTest); + + // simple elements + expect(thing).to.have.property("@context").that.equals(DEFAULT_CONTEXT_V1); + expect(thing.id).equals("urn:dev:wot:com:example:servient:urivarables"); + expect(thing.title).equals("UriVarables"); + + // interaction arrays + expect(thing).to.have.property("properties"); + + const thingProperties = thing.properties!; + + expect(thingProperties).to.have.property("without"); + expect(thingProperties.without.forms[0].href).equals("http://localhost:8080/uv/" + "without{?step}"); + + expect(thingProperties).to.have.property("with1"); + expect(thingProperties.with1.forms[0].href).equals("http://localhost:8080/uv/" + "with1{?step}"); + + expect(thingProperties).to.have.property("with2"); + expect(thingProperties.with2.forms[0].href).equals("http://localhost:8080/uv/" + "with2{?step,a}"); + } +} diff --git a/packages/examples/package.json b/packages/examples/package.json index 8df22de40..47e90ba56 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -13,7 +13,6 @@ "@node-wot/binding-mqtt": "0.8.15", "@node-wot/binding-opcua": "0.8.15", "@node-wot/core": "0.8.15", - "@node-wot/td-tools": "0.8.15", "rxjs": "5.5.11" }, "scripts": { diff --git a/packages/td-tools/README.md b/packages/td-tools/README.md index 10f38758c..36aca23e0 100644 --- a/packages/td-tools/README.md +++ b/packages/td-tools/README.md @@ -1,3 +1,5 @@ +> [!WARNING] > `@node-wot/td-tools` package will be removed in the future. Please use `@node-wot/core` / `@thingweb/thing-model` / `@thingweb/td-utils` package instead. + # TD (Thing Description) tools of node-wot Current Maintainer(s): [@danielpeintner](https://github.com/danielpeintner) [@relu91](https://github.com/relu91) diff --git a/packages/td-tools/src/td-tools.ts b/packages/td-tools/src/td-tools.ts index 43a367c6d..9ffefa49e 100644 --- a/packages/td-tools/src/td-tools.ts +++ b/packages/td-tools/src/td-tools.ts @@ -25,4 +25,8 @@ type DeepPartial = T extends Record [P in keyof T]?: T[P] extends Array ? Array> : DeepPartial; } : T; + +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export type ThingModel = DeepPartial; diff --git a/packages/td-tools/src/thing-description.ts b/packages/td-tools/src/thing-description.ts index a21dcb757..efb22df3b 100644 --- a/packages/td-tools/src/thing-description.ts +++ b/packages/td-tools/src/thing-description.ts @@ -17,12 +17,27 @@ import * as WoT from "wot-typescript-definitions"; import * as TDT from "wot-thing-description-types"; +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export const DEFAULT_CONTEXT_V1 = "https://www.w3.org/2019/wot/td/v1"; +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export const DEFAULT_CONTEXT_V11 = "https://www.w3.org/2022/wot/td/v1.1"; +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export const DEFAULT_CONTEXT_LANGUAGE = "en"; +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export const DEFAULT_THING_TYPE = "Thing"; -/** Implements the Thing Description as software object */ +/** Implements the Thing Description as software object + * + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export default class Thing implements TDT.ThingDescription { title: TDT.Title; securityDefinitions: { @@ -55,10 +70,16 @@ export default class Thing implements TDT.ThingDescription { } } -/** Basis from implementing the Thing Interaction descriptions for Property, Action, and Event */ +/** Basis from implementing the Thing Interaction descriptions for Property, Action, and Event + * + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export type ThingInteraction = TDT.PropertyElement | TDT.ActionElement | TDT.EventElement; -/** Implements the Interaction Form description */ +/** Implements the Interaction Form description + * + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export class Form implements TDT.FormElementBase { op?: string | string[]; href: TDT.AnyUri; @@ -76,13 +97,22 @@ export class Form implements TDT.FormElementBase { if (contentType != null) this.contentType = contentType; } } +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export interface ExpectedResponse { contentType?: string; } +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export type DataSchema = WoT.DataSchema & (BooleanSchema | IntegerSchema | NumberSchema | StringSchema | ObjectSchema | ArraySchema | NullSchema); +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export class BaseSchema { type?: string; title?: TDT.Title; @@ -97,32 +127,50 @@ export class BaseSchema { enum?: Array; } +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export interface BooleanSchema extends BaseSchema { type: "boolean"; } +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export interface IntegerSchema extends BaseSchema { type: "integer"; minimum?: number; maximum?: number; } +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export interface NumberSchema extends BaseSchema { type: "number"; minimum?: number; maximum?: number; } +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export interface StringSchema extends BaseSchema { type: "string"; } +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export interface ObjectSchema extends BaseSchema { type: "object"; properties: { [key: string]: DataSchema }; required?: Array; } +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export interface ArraySchema extends BaseSchema { type: "array"; items: DataSchema; @@ -130,12 +178,19 @@ export interface ArraySchema extends BaseSchema { maxItems?: number; } +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export interface NullSchema extends BaseSchema { type: "null"; } // TODO AutoSecurityScheme // TODO ComboSecurityScheme + +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export type SecurityType = | NoSecurityScheme | BasicSecurityScheme @@ -145,6 +200,9 @@ export type SecurityType = | OAuth2SecurityScheme | PSKSecurityScheme; +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export interface SecurityScheme { scheme: string; description?: TDT.Description; @@ -153,35 +211,59 @@ export interface SecurityScheme { [k: string]: unknown; } +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export interface NoSecurityScheme extends SecurityScheme, TDT.NoSecurityScheme { scheme: "nosec"; } +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export interface BasicSecurityScheme extends SecurityScheme, TDT.BasicSecurityScheme { scheme: "basic"; } +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export interface DigestSecurityScheme extends SecurityScheme, TDT.DigestSecurityScheme { scheme: "digest"; } +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export interface APIKeySecurityScheme extends SecurityScheme, TDT.ApiKeySecurityScheme { scheme: "apikey"; } +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export interface BearerSecurityScheme extends SecurityScheme, TDT.BearerSecurityScheme { scheme: "bearer"; } +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export interface PSKSecurityScheme extends SecurityScheme, TDT.PskSecurityScheme { scheme: "psk"; } +/** + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export interface OAuth2SecurityScheme extends SecurityScheme, TDT.OAuth2SecurityScheme { scheme: "oauth2"; } -/** Implements the Thing Property description */ +/** Implements the Thing Property description + * + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export abstract class ThingProperty extends BaseSchema { // writable: boolean; observable?: boolean; @@ -204,7 +286,10 @@ export abstract class ThingProperty extends BaseSchema { [key: string]: any; } -/** Implements the Thing Action description */ +/** Implements the Thing Action description + * + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export abstract class ThingAction { input?: DataSchema; output?: DataSchema; @@ -227,7 +312,10 @@ export abstract class ThingAction { // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; } -/** Implements the Thing Event description */ +/** Implements the Thing Event description + * + * @deprecated Will be removed in the future. Please use '@node-wot/core' package instead. + */ export abstract class ThingEvent { subscription?: DataSchema; data?: DataSchema;