"use strict";
import "@node-wot/browser-bundle";
import axios from "axios";
const newNotification = new Event("newNotification");
/**
* ConsumedThing according to WoT Scripting API
* @typedef {object} ConsumedThing
*/
/**
* Class to create a an object that manages all consumed things.
* @type {object}
* @constructor
* @public
* @property {object.<ConsumedThing>} things - The property is the ConsumedThing id and the value is the ConsumedThing.
* @property {?object.<ConsumedThing>} currentThing - The current consumed thing which is the thing that will be used by the methods.
* @property {object.<string[]>} observedProperties - Contains all the observed properties where the key is the thing id and the value is all the observed properties.
* @property {object.<string[]>} subscribedEvents - Contains all the subscribed event where the key is the thing id and the value is all the subscribed event.
*/
class ThingsController {
/**
*Contains all the consumed things with their id as a key.
* @type {object}
* @property {ConsumedThing} - The property is the ConsumedThing id and the value is the ConsumedThing.
* @default
*/
#things = {};
/**
* The current consumed thing which is the thing that will be used by the methods.
* @type {ConsumedThing|null}
* @default
*/
#currentThing = null;
/**
* Contains all the observed properties where the key is the thing id and the value is all the observed properties.
* @type {object}
* @property {string[]}
* @default
*/
#observedProperties = {};
/**
* Contains all the subscribed event where the key is the thing id and the value is all the subscribed event.
* @type {object}
* @property {string[]}
* @default
*/
#subscribedEvents = {};
/**
* An instance of the class Logger.
* @type {Logger}
*/
#logger;
set logger(logger) {
this.#logger = logger;
}
/**
* Get the current thing id.
* @type {string}
*/
get currentThingID() {
return this.#currentThing.id;
}
/**Get the current thing title.
* @type {string}
*/
get currentThingTitle() {
return this.#currentThing.title;
}
/**
* Returns false if the this.#currentThing is null and true otherwise.
* @type {Function}
* @returns {boolean}
*/
hasCurrentThing() {
if (this.#currentThing === null) {
return false;
}
return true;
}
/**
* Returns false if the this.#currentThing has nosec_sc
* @type {Function}
* @returns {boolean}
*/
hasSecurity() {
let security = this.#currentThing.security;
if (typeof security === "object" && security.length > 0) {
return security[0] === "nosec_sc" ? false : true;
} else if (typeof security === "string") {
return security === "nosec_sc" ? false : true;
} else {
return false;
}
}
/**
* Returns true if the this.#currentThing has uriVariables.
* @type {Function}
* @param {"properties"|"actions"|"events"} affordance
* @param {string} interactionAffordance
* @returns {boolean}
*/
hasUriVariables(affordance, interactionAffordance) {
const td = this.#getTD();
return td[affordance][interactionAffordance].hasOwnProperty("uriVariables");
}
/**
* Get the uriVariables object from the TD for the specified affordance and interactionAffordance for the currentThing.
* @type {Function}
* @param {"properties"|"actions"|"events"} affordance
* @param {string} interactionAffordance
* @returns {object}
*/
getUriVariables(affordance, interactionAffordance) {
const td = this.#getTD();
return td[affordance][interactionAffordance].uriVariables;
}
/**
* Add security credential to the currentThing.
* @type {Function}
* @param {string} username
* @param {string} password
* @returns {void}
*/
addCredential(username, password) {
this.#currentThing.getServient().addCredentials({
[this.#currentThing.id]: {
username,
password,
},
});
}
/**
* Get the security type for the currentThing.
* @type {Function}
* @returns {string | undefined}
*/
securityType() {
let security = this.#currentThing.security;
if (typeof security === "object" && security.length > 0) {
return security[0];
} else if (typeof security === "string") {
return security;
} else {
return undefined;
}
}
/**
* Consume a thing description.
* @type {Function}
* @param {object} td - Thing description object.
* @returns void
*/
async consume(td) {
if (!td.hasOwnProperty("id") || td.id.length === 0) {
td.id = Date.now().toString();
}
if (!td.hasOwnProperty("title") || td.title.length === 0) {
td.title = Date.now().toString();
}
let servient = new Wot.Core.Servient();
servient.addClientFactory(new Wot.Http.HttpsClientFactory(null));
servient.addClientFactory(new Wot.Http.HttpClientFactory(null));
await servient.start().then((thingFactory) => {
thingFactory.consume(td).then((thing) => {
this.#things[thing.id] = thing;
this.#currentThing = thing;
this.#observedProperties[thing.id] = [];
this.#subscribedEvents[thing.id] = [];
this.#logger.initiate(thing.id, td);
console.clear();
});
});
}
/**
* Get the action interaction affordance object from the currentThing.
* @type {Function}
* @param {object} td - Thing description object.
* @returns {object}
*/
getActionForm(action) {
let td = this.#getTD();
return td.actions[action];
}
/**
* Get all actions interaction affordance from the currentThing.
* @type {Function}
* @returns {string[]}
*/
getActions() {
let td = this.#getTD();
return Object.keys(td.actions);
}
/**
* Get all events interaction affordance from the currentThing.
* @type {Function}
* @returns {object}
*/
getEvents() {
let td = this.#getTD();
return Object.keys(td.events);
}
/**
* Get the object for all properties from the currentThing.
* @type {Function}
* @returns {object}
*/
getPropertiesTD() {
let td = this.#getTD();
return td.properties;
}
/**
* Get the object for all events from the currentThing.
* @type {Function}
* @returns {object}
*/
getEventsTD() {
let td = this.#getTD();
return td.events;
}
/**
* @type {Function}
* @param {string} property - The property interaction affordance.
* @returns {boolean}
*/
isPropertyObservable(property) {
return this.getPropertyTD(property).observable === true;
}
/**
* @type {Function}
* @param {string} property - The property interaction affordance.
* @returns {boolean}
*/
isPropertyObserved(property) {
let thingID = this.#currentThing.id;
try {
return this.#observedProperties[thingID].includes(property)
? true
: false;
} catch (error) {
return false;
}
}
/**
* @type {Function}
* @param {string} event - The event interaction affordance.
* @returns {boolean}
*/
isEventSubscribed(event) {
const thingID = this.#currentThing.id;
try {
return this.#subscribedEvents[thingID].includes(event);
} catch (error) {
return false;
}
}
/**
* @type {Function}
* @param {string} property - The property interaction affordance.
* @returns {boolean}
*/
isPropertyReadable(property) {
let isReadable;
if (["writeallproperties", "writemultipleproperties"].includes(property)) {
isReadable = false;
} else if (
["readallproperties", "readmultipleproperties"].includes(property)
) {
isReadable = true;
} else {
let propertyTD = this.getPropertyTD(property);
isReadable =
!propertyTD.hasOwnProperty("writeOnly") || !propertyTD.writeOnly;
}
return isReadable;
}
/**
* @type {Function}
* @param {string} property - The property interaction affordance.
* @returns {boolean}
*/
isPropertyWritable(property) {
let isWritable;
if (["writeallproperties", "writemultipleproperties"].includes(property)) {
isWritable = true;
} else if (
["readallproperties", "readmultipleproperties"].includes(property)
) {
isWritable = false;
} else {
let propertyTD = this.getPropertyTD(property);
isWritable =
!propertyTD.hasOwnProperty("readOnly") || !propertyTD.readOnly;
}
return isWritable;
}
/**
* get the property interaction affordance object from the currentThing.
* @type {Function}
* @param {string} property - The property interaction affordance.
* @returns {object}
*/
getPropertyTD(property) {
return this.getPropertiesTD()[property];
}
/**
* get the top level form object for the currentThing.
* @type {Function}
* @param {"readallproperties"|"readmultipleproperties"|"writeallproperties"|"writemultipleproperties"} property - The top level form type
* @returns {object}
*/
getTopLevelFormTD(property) {
const forms = this.#getTD()["forms"];
if (forms.length) {
return { ...forms };
} else {
for (const iterator of forms) {
if (iterator.op === property) {
return { ...property };
}
}
}
}
/**
* get op(s) for the top level form for the currentThing.
* @type {Function}
* @returns {object}
*/
getToplevelForms() {
let td = this.#getTD();
return td.hasOwnProperty("forms")
? td["forms"].reduce((acc, curr) => {
return { [curr.op]: {}, ...acc };
}, {})
: {};
}
/**
* get the event ob for the currentThing.
* @type {Function}
* @param {string} event - The event interaction affordance.
* @returns {object}
*/
getEventTD(event) {
return this.getEventsTD()[event];
}
/**
* @type {Function}
* @param {string} property - The property interaction affordance.
* @returns {object.<status:boolean,data:string>}
*/
observeProperty(property) {
const thing = this.#currentThing;
const requestDate = Date.now().toString();
const thingId = this.currentThingID;
this.#logger.saveRequest(
"observeProperty",
property,
undefined,
undefined,
thingId,
requestDate
);
!this.#observedProperties[thing.id].includes(property) &&
this.#observedProperties[thing.id].push(property);
return thing
.observeProperty(
property,
(response) => {
const responseDate = Date.now().toString();
this.#logger.saveResponse(
"observeProperty",
property,
response,
undefined,
thingId,
requestDate,
responseDate,
true
);
document.dispatchEvent(newNotification);
},
(error) => {
const index = this.#observedProperties[thing.id].indexOf(property);
if (index > -1) {
this.#observedProperties[thing.id].splice(index, 1);
const responseDate = Date.now().toString();
this.#logger.saveResponse(
"observeProperty",
property,
error.message,
undefined,
thingId,
requestDate,
responseDate,
false
);
return { status: false, data: error.message };
}
}
)
.catch((error) => {
const index = this.#observedProperties[thing.id].indexOf(property);
if (index > -1) {
this.#observedProperties[thing.id].splice(index, 1);
const responseDate = Date.now().toString();
this.#logger.saveResponse(
"observeProperty",
property,
error.message,
undefined,
thingId,
requestDate,
responseDate,
false
);
return { status: false, data: error.message };
}
});
}
/**
* @type {Function}
* @param {string} property - The property interaction affordance.
*/
async unobserveProperty(property) {
const requestDate = Date.now().toString();
const thingId = this.currentThingID;
const thing = this.#currentThing;
if (this.isPropertyObserved(property)) {
this.#logger.saveRequest(
"unobserveProperty",
property,
undefined,
undefined,
thingId,
requestDate
);
await thing.unobserveProperty(property);
const index = this.#observedProperties[thing.id].indexOf(property);
if (index > -1) this.#observedProperties[thing.id].splice(index, 1);
}
}
/**
* @type {Function}
* @param {string[]} properties - All properties.
* @returns {object.<status:boolean,data:string>}
*/
async writeAllProperties(properties) {
const requestDate = Date.now().toString();
const thingId = this.currentThingID;
const form = this.#getTopLevelForm("writeallproperties");
this.#logger.saveRequest(
"writeAllProperties",
undefined,
properties,
undefined,
thingId,
requestDate
);
let config = {
method: form.httpMethod.toLowerCase(),
url: form.url,
headers: {
"Content-Type": form.contentType,
},
data: properties,
};
return await axios(config)
.then((res) => {
const responseDate = Date.now().toString();
this.#logger.saveResponse(
"writeAllProperties",
undefined,
res.data,
undefined,
thingId,
requestDate,
responseDate,
true
);
})
.catch((e) => {
const responseDate = Date.now().toString();
this.#logger.saveResponse(
"writeAllProperties",
[e.toString()],
thingId,
requestDate,
responseDate,
false
);
return { status: false, data: e.toString() };
});
}
/**
* @type {Function}
* @param {string[]} properties
* @returns {object.<status:boolean,data:string>}
*/
async writeMultipleProperties(properties) {
const requestDate = Date.now().toString();
const thingId = this.currentThingID;
const form = this.#getTopLevelForm("writemultipleproperties");
this.#logger.saveRequest(
"writeMultipleProperties",
undefined,
properties,
undefined,
thingId,
requestDate
);
let config = {
method: form.httpMethod.toLowerCase(),
url: form.url,
headers: {
"Content-Type": form.contentType,
},
data: properties,
};
return await axios(config)
.then((res) => {
const responseDate = Date.now().toString();
this.#logger.saveResponse(
"writeMultipleProperties",
undefined,
"Promise Resolved",
undefined,
thingId,
requestDate,
responseDate,
true
);
return { status: true, data: "Promise Resolved" };
})
.catch((e) => {
const responseDate = Date.now().toString();
this.#logger.saveResponse(
"writeMultipleProperties",
undefined,
message,
undefined,
thingId,
requestDate,
responseDate,
false
);
return { status: false, data: e.toString() };
});
}
/**
* @type {Function}
* @param {string} property
* @param {*} value
* @param {object} uriVariables
* @returns {object.<status:boolean,data:string>}
*/
async writeProperty(property, value, uriVariables) {
const requestDate = Date.now().toString();
const thingId = this.currentThingID;
this.#logger.saveRequest(
"writeProperty",
property,
value,
uriVariables,
thingId,
requestDate
);
return this.#currentThing
.writeProperty(property, value, { uriVariables })
.then(() => {
const responseDate = Date.now().toString();
this.#logger.saveResponse(
"writeProperty",
property,
undefined,
uriVariables,
thingId,
requestDate,
responseDate,
true
);
return { status: true, data: "The Property Was Successfully Updated." };
})
.catch((error) => {
const responseDate = Date.now().toString();
this.#logger.saveResponse(
"writeProperty",
property,
error.message,
uriVariables,
thingId,
requestDate,
responseDate,
false
);
return { status: false, data: error.message };
});
}
/**
* Get the description for a property.
* @type {Function}
* @param {string} property
* @returns {string|undefined}
*/
getPropertyDescription(property) {
return this.getPropertyTD(property).description;
}
/**
* Get the description for an event.
* @type {Function}
* @param {string} event
* @returns {string|undefined}
*/
getEventDescription(event) {
return this.getEventTD(event).description;
}
/**
* Get all the readable properties.
* @type {Function}
* @returns {string[]}
*/
getReadableProperties() {
return Object.keys(this.getPropertiesTD()).filter((property) =>
this.isPropertyReadable(property)
);
}
/**
* Get all the writable properties.
* @type {Function}
* @returns {string[]}
*/
getWritableProperties() {
return Object.keys(this.getPropertiesTD()).filter((property) =>
this.isPropertyWritable(property)
);
}
/**
* Get the url,httpMethod and contentType for a top level Form.
* @type {Function}
* @param {"readallproperties"|"readmultipleproperties"|"writeallproperties"|"writemultipleproperties"} method - The top level form type
* @returns {object.<url:string,httpMethod:url,contentType:string>}
* @private
*/
#getTopLevelForm(method) {
let thing = this.#currentThing;
let base = thing.base || "";
let forms = thing.forms;
for (let i in forms) {
if (forms[i].op === method) {
return {
url: base + forms[i].href,
httpMethod: forms[i]["htv:methodName"],
contentType: forms[i].contentType,
};
}
}
throw new Error("notSupported");
}
/**
* @type {Function}
* @returns {*}
*/
async readAllProperties() {
let requestDate = Date.now().toString();
let responseDate;
let thingId = this.currentThingID;
let form = this.#getTopLevelForm("readallproperties");
this.#logger.saveRequest(
"readAllProperties",
undefined,
undefined,
undefined,
thingId,
requestDate
);
let config = {
method: form.httpMethod.toLowerCase(),
url: form.url,
headers: {
"Content-Type": form.contentType,
},
};
return await axios(config)
.then((res) => {
responseDate = Date.now().toString();
this.#logger.saveResponse(
"readAllProperties",
undefined,
res.data,
undefined,
thingId,
requestDate,
responseDate,
true
);
return res.data;
})
.catch((e) => {
responseDate = Date.now().toString();
this.#logger.saveResponse(
"readAllProperties",
undefined,
e.message,
undefined,
thingId,
requestDate,
responseDate,
false
);
return e.toString();
});
}
/**
* @type {Function}
* @returns {object}
*/
async readMultipleProperties(properties) {
let requestDate = Date.now().toString();
let responseDate;
let thingId = this.currentThingID;
this.#logger.saveRequest(
"readMultipleProperties",
undefined,
properties,
undefined,
thingId,
requestDate
);
let form = this.#getTopLevelForm("readmultipleproperties");
let config = {
method: form.httpMethod.toLowerCase(),
url: form.url,
headers: {
"Content-Type": form.contentType,
},
data: properties,
};
return await axios(config)
.then((res) => {
responseDate = Date.now().toString();
this.#logger.saveResponse(
"readMultipleProperties",
undefined,
res.data,
undefined,
thingId,
requestDate,
responseDate,
true
);
return res.data;
})
.catch((e) => {
responseDate = Date.now().toString();
this.#logger.saveResponse(
"readMultipleProperties",
undefined,
e.toString(),
undefined,
thingId,
requestDate,
responseDate,
false
);
return e.toString();
});
}
/**
* @type {Function}
* @param {string} property
* @param {object} uriVariables
* @returns {*}
*/
async readProperty(property, uriVariables) {
let payload = [property];
let requestDate = Date.now().toString();
let responseDate;
let thingId = this.currentThingID;
this.#logger.saveRequest(
"readProperty",
property,
undefined,
uriVariables,
thingId,
requestDate
);
return this.#currentThing
.readProperty(property, { uriVariables })
.then((res) => {
responseDate = Date.now().toString();
this.#logger.saveResponse(
"readProperty",
property,
res,
uriVariables,
thingId,
requestDate,
responseDate,
true
);
return res;
})
.catch((error) => {
responseDate = Date.now().toString();
this.#logger.saveResponse(
"readProperty",
property,
error.message,
uriVariables,
thingId,
requestDate,
responseDate,
false
);
return error.message;
});
}
/**
* @type {Function}
* @param {string} action
* @param {*} payload
* @param {object} uriVariables
* @returns {*}
*/
async invokeAction(action, payload, uriVariables) {
const thingId = this.currentThingID;
const requestDate = Date.now().toString();
this.#logger.saveRequest(
"invokeAction",
action,
payload,
uriVariables,
thingId,
requestDate
);
return await this.#currentThing
.invokeAction(action, payload, { uriVariables })
.then((res) => {
const responseDate = Date.now().toString();
this.#logger.saveResponse(
"invokeAction",
action,
res,
uriVariables,
thingId,
requestDate,
responseDate,
true
);
return res;
})
.catch((error) => {
const responseDate = Date.now().toString();
this.#logger.saveResponse(
"invokeAction",
action,
error.message,
uriVariables,
thingId,
requestDate,
responseDate,
false
);
return error.message;
});
}
/**
* @type {Function}
* @param {string} event
*/
subscribeEvent(event) {
const thingId = this.currentThingID;
this.#subscribedEvents[thingId].push(event);
const requestDate = Date.now().toString();
this.#logger.saveRequest(
"subscribeEvent",
event,
undefined,
undefined,
thingId,
requestDate
);
return this.#currentThing
.subscribeEvent(
event,
(response) => {
const responseDate = Date.now().toString();
this.#logger.saveResponse(
"subscribeEvent",
event,
response,
undefined,
thingId,
requestDate,
responseDate,
true
);
document.dispatchEvent(newNotification);
},
(error) => {
const index = this.#subscribedEvents[thingId].indexOf(event);
if (index > -1) {
this.#subscribedEvents[thingId].splice(index, 1);
const responseDate = Date.now().toString();
this.#logger.saveResponse(
"subscribeEvent",
event,
error.message,
undefined,
thingId,
requestDate,
responseDate,
false
);
}
}
)
.catch((error) => {
const index = this.#subscribedEvents[thingId].indexOf(event);
if (index > -1) {
this.#subscribedEvents[thingId].splice(index, 1);
const responseDate = Date.now().toString();
this.#logger.saveResponse(
"subscribeEvent",
event,
error.message,
undefined,
thingId,
requestDate,
responseDate,
false
);
return { status: false, data: error.message };
}
});
}
/**
* @type {Function}
* @param {string} event
*/
unsubscribeEvent(event) {
const thingId = this.currentThingID;
const requestDate = Date.now().toString();
const index = this.#subscribedEvents[thingId].indexOf(event);
if (index > -1) this.#subscribedEvents[thingId].splice(index, 1);
this.#logger.saveRequest(
"unsubscribeEvent",
event,
undefined,
undefined,
thingId,
requestDate
);
this.#currentThing.unsubscribeEvent(
event,
() => {
const responseDate = Date.now().toString();
this.#logger.saveResponse(
"unsubscribeEvent",
event,
undefined,
undefined,
thingId,
requestDate,
responseDate,
true
);
},
(error) => {
console.log(error);
}
);
}
/** return the metadata for the currentThing.
* @type {Function}
* @returns {string[][]}
*/
getMetadata() {
let enumString = [
"@context",
"@type",
"id",
"title",
"titles",
"description",
"descriptions",
"support",
"version",
"created",
"modified",
"securityDefinitions",
"security",
"base",
"links",
"forms",
];
let td = this.#getTD();
let metadata = enumString.reduce((accumulator, currentValue) => {
if (td[currentValue] !== undefined && enumString.includes(currentValue)) {
accumulator.push([currentValue, td[currentValue]]);
}
return accumulator;
}, []);
return metadata;
}
/**
* Return the Thing Description for the currentThing.
* @type {Function}
* @returns {object}
*/
#getTD() {
return this.#currentThing.getThingDescription();
}
/**Change the current Thing.
* @type {Function}
* @param {string} id
*/
setCurrentThingByID(id) {
if (id in this.#things) {
this.#currentThing = this.#things[id];
}
}
}
export default ThingsController;