Copyright © 2017-2020 W3C® (MIT, ERCIM, Keio, Beihang). W3C liability, trademark and permissive document license rules apply.
The main Web of Things (WoT) concepts are described in the WoT Architecture document. The Web of Things is made of entities (Things) that can describe their capabilities in a machine-interpretable Thing Description (TD) and expose these capabilities through the WoT Interface, that is, network interactions modeled as Properties (for reading and writing values), Actions (to execute remote procedures with or without return values) and Events (for signaling notifications).
Scripting is an optional "convenience" building block in WoT and it is typically used in gateways that are able to run a WoT Runtime and script management, providing a convenient way to extend WoT support to new types of endpoints and implement WoT applications such as Thing Directory.
This specification describes a programming interface representing the WoT Interface that allows scripts to discover, operate Things and to expose locally defined Things characterized by WoT Interactions specified by a script.
The specification deliberately follows the WoT Thing Description specification closely. It is possible to implement simpler APIs on top of this API, or implementing directly the WoT network facing interface (i.e. the WoT Interface).
This specification is implemented at least by the Thingweb project also known as node-wot, which is considered the reference open source implementation at the moment. Check its source code, including examples. Other, closed source implementations have been made by WG member companies and tested against node-wot in plug-fests.
This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at https://www.w3.org/TR/.
Implementers need to be aware that this specification is considered unstable. Vendors interested in implementing this specification before it eventually reaches the Candidate Recommendation phase should subscribe to the repository and take part in the discussions.
Please contribute to this draft using the GitHub Issue feature of the WoT Scripting API repository. For feedback on security and privacy considerations, please use the WoT Security and Privacy Issues.
This document was published by the Web of Things Working Group as an Editor's Draft.
Comments regarding this document are welcome. Please send them to public-wot-wg@w3.org (archives).
Publication as an Editor's Draft does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.
This document was produced by a group operating under the W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.
This document is governed by the 1 March 2019 W3C Process Document.
WoT provides layered interoperability based on how Things are used: "consumed" and "exposed", as defined in [WOT-ARCHITECTURE].
By consuming a TD, a client Thing creates a local runtime resource model that allows accessing the Properties, Actions and Events exposed by the server Thing on a remote device.
Typically scripts are meant to be used on bridges or gateways that expose and control simpler devices as WoT Things and have means to handle (e.g. install, uninstall, update etc.) and run scripts.
This specification does not make assumptions on how the WoT Runtime handles and runs scripts, including single or multiple tenancy, script deployment and lifecycle management. The API already supports the generic mechanisms that make it possible to implement script management, for instance by exposing a manager Thing whose Actions (action handlers) implement script lifecycle management operations.
This section is non-normative.
The following scripting use cases are supported in this specification:
As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.
The key words MAY, MUST, and SHOULD in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.
This specification describes the conformance criteria for the following classes of user agent (UA).
Due to requirements of small embedded implementations, splitting WoT client and server interfaces was needed. Then, discovery is a distributed application, but typical scenarios have been covered by a generic discovery API in this specification. This resulted in using 3 conformance classes for a UA that implements this API, one for client, one for server, and one for discovery. An application that uses this API can introspect for the presence of the consume()
, produce()
and discover()
methods on the WoT API object in order to determine which conformance class the UA implements.
Implementations of this conformance class MUST implement the
interface and the ConsumedThing
consume()
method on the WoT API object.
Implementations of this conformance class MUST implement
interface and the ExposedThing
produce()
method on the WoT API object.
Implementations of this conformance class MUST implement the
interface and the ThingDiscovery
discover()
method on the WoT API object.
These conformance classes MAY be implemented in a single UA.
This specification can be used for implementing the WoT Scripting API in multiple programming languages. The interface definitions are specified in [WEBIDL].
The UA may be implemented in the browser, or in a separate runtime environment, such as Node.js or in small embedded runtimes.
Implementations that use ECMAScript executed in a browser to implement the APIs defined in this document MUST implement them in a manner consistent with the ECMAScript Bindings defined in the Web IDL specification [WEBIDL].
Implementations that use TypeScript or ECMAScript in a runtime to implement the APIs defined in this document MUST implement them in a manner consistent with the TypeScript Bindings defined in the TypeScript specification [TYPESCRIPT].
ThingDescription
typeWebIDLtypedef object ThingDescription
;
Represents a Thing Description (TD) as defined in [WOT-TD]. It is expected to be a parsed JSON object that is validated using JSON schema validation.
Fetching a TD given a URL should be done with an external method, such as the Fetch API or a HTTP client library, which offer already standardized options on specifying fetch details.
try {
let res = await fetch('https://tds.mythings.biz/sensor11');
// ... additional checks possible on res.headers
let td = await res.json();
let thing = new ConsumedThing(td);
console.log("Thing name: " + thing.getThingDescription().title);
} catch (err) {
console.log("Fetching TD failed", err.message);
}
Note that [WOT-TD] allows using a shortened Thing Description by the means of defaults and requiring clients to expand them with default values specified in [WOT-TD] for the properties that are not explicitly defined in a given TD.
The [WOT-TD] specification defines how a TD should be validated.
Therefore, this API expects the
objects be validated before used as parameters. This specification defines a basic TD validation as follows.
ThingDescription
TypeError
" and abort these steps.
TypeError
" and abort these steps.
TypeError
" and abort these steps.
Defines the API entry point exposed as a singleton and contains the API methods.
WOT
interfaceWebIDL[SecureContext, Exposed=(Window,Worker)]
interface WOT
{
// methods defined in UA conformance classes
};
consume()
methodWebIDLpartial interfaceWOT
{ Promise<ConsumedThing
>consume
(ThingDescription
td); };
Promise
that resolves with a ConsumedThing
object that represents a client interface to operate with the Thing. The method MUST run the following steps:
Promise
promise and execute the next steps in parallel.
SecurityError
and abort these steps.
SyntaxError
and abort these steps.
ConsumedThing
object constructed from td.
Implementations encapsulate the complexity of how to use the Protocol Bindings for implementing WoT interactions. In the future elements of that could be standardized.
produce()
methodWebIDLpartial interfaceWOT
{ Promise<ExposedThing
>produce
(ThingDescription
td); };
Promise
that resolves with an ExposedThing
object that extends ConsumedThing
with a server interface, i.e. the ability to define request handlers. The method MUST run the following steps:
Promise
promise and execute the next steps in parallel.
SecurityError
and abort these steps.
ExposedThing
object constructed with td.
discover()
methodWebIDLpartial interfaceWOT
{ThingDiscovery
discover
(optionalThingFilter
filter = null); };
ThingDescription
objects for Thing Descriptions that match an optional filter argument of type ThingFilter
. The method MUST run the following steps:
SecurityError
" and abort these steps.
ThingDiscovery
object discovery with filter.
ConsumedThing
interfaceRepresents a client API to operate a Thing. Belongs to the WoT Consumer conformance class.
WebIDL[SecureContext, Exposed=(Window,Worker)] interfaceConsumedThing
{constructor
(ThingDescription
td); Promise<InteractionData
>readProperty
(DOMString propertyName, optionalInteractionOptions
options = null); Promise<PropertyMap
>readAllProperties
(optionalInteractionOptions
options = null); Promise<PropertyMap
>readMultipleProperties
( sequence<DOMString> propertyNames, optionalInteractionOptions
options = null); Promise<void>writeProperty
(DOMString propertyName, any value, optionalInteractionOptions
options = null); Promise<void>writeMultipleProperties
(PropertyMap
valueMap, optionalInteractionOptions
options = null); Promise<any>invokeAction
(DOMString actionName, optional any params = null, optionalInteractionOptions
options = null); Promise<void>observeProperty
(DOMString name,WotListener
listener, optionalInteractionOptions
options = null); Promise<void>unobserveProperty
(DOMString name, optionalInteractionOptions
options = null); Promise<void>subscribeEvent
(DOMString name,WotListener
listener, optionalInteractionOptions
options = null); Promise<void>unsubscribeEvent
(DOMString name, optionalInteractionOptions
options = null);ThingDescription
getThingDescription
(); }; dictionaryInteractionOptions
{ unsigned longformIndex
; objecturiVariables
; }; typedef objectPropertyMap
; callbackWotListener
= void(any data);
ConsumedThing
After fetching a
Thing Description as a JSON object, one can create a
object.
ConsumedThing
ConsumedThing
with the ThingDescription
td, run the following steps:
ConsumedThing
object.
getThingDescription()
method
Returns the internal slot |td| of the
object that represents the Thing Description of the ConsumedThing
.
Applications may consult the Thing metadata stored in |td| in order to introspect its capabilities before interacting with it.
ConsumedThing
InteractionOptions
dictionary
Holds the interaction options that need to be exposed for application scripts according to the Thing Description.
The formIndex property, if defined, represents an application
hint for which Form
definition, identified by this index,
of the TD to use for the given WoT interaction.
Implementations SHOULD use the Form
with this index for
making the interaction, but MAY override this value if the index is not
found or not valid.
If not defined, implementations SHOULD attempt to use the
Form
definitions in order of appearance as listed in the
TD for the given Wot Interaction.
The uriVariables property if defined, represents the URI template variables to be used with the WoT Interaction that are represented as parsed JSON objects defined in [WOT-TD].
The support for URI variables comes from the need exposed by [WOT-TD] to be able to describe existing TDs that use them, but it should be possible to write a Thing Description that would use Actions for representing the interactions that need URI variables and represent the URI variables as parameters to the Action and in that case that could be encapsulated by the implementations and the options parameter could be dismissed from the methods exposed by this API.
PropertyMap
typeRepresents a map of Property names as strings to a value that the Property can take. It is used as a property bag for interactions that involve multiple Properties at once.
It could be defined in Web IDL (as well as
) as a maplike interface from string to any.
ThingDescription
readProperty()
methodInteractionOptions
options
argument. It returns a Property value represented as any
type.
The method MUST run the following steps:
Promise
promise and execute the next steps
in parallel.
SecurityError
and abort these steps.
formIndex
is defined, let form be the
Form associated with formIndex
in interaction's forms
array, otherwise let form be the first Form in
interaction's forms whose op is readproperty
.
SyntaxError
and
abort these steps.
uriVariables
.
SyntaxError
and abort these steps.
readMultipleProperties()
methodInteractionOptions
options argument. It returns an object that maps keys from propertyNames to values returned by this algorithm. The method MUST run the following steps:
Promise
promise and execute the next steps in parallel.
SecurityError
and abort these steps.
formIndex
is defined, let form be the
Form associated with formIndex
in |td|'s forms array, otherwise let form be the first Form in |td|'s forms array whose op is readmultipleproperties
.
SyntaxError
and abort these steps.
null
.
uriVariables
.
NotSupportedError
and abort these steps.
readAllProperties()
methodInteractionOptions
options argument. It returns an object that maps keys from Property names to values returned by this algorithm. The method MUST run the following steps:
Promise
promise and execute the next steps in parallel.
SecurityError
and abort these steps.
formIndex
is defined, let form be the
Form associated with formIndex
in |td|'s forms array, otherwise let form be the first Form in |td|'s forms array whose op is readallproperties
.
SyntaxError
and abort these steps.
uriVariables
.
NotSupportedError
and abort these steps.
writeProperty()
methodInteractionOptions
options argument. It returns success or failure. The method MUST run the following steps:
Promise
promise and execute the next steps in parallel.
SecurityError
and abort these steps.
formIndex
is defined, let form be the
Form associated with formIndex
in interaction's forms
array, otherwise let form be the first Form in
interaction's forms whose op is writeproperty
.
SyntaxError
and abort these steps.
uriVariables
.
As discussed in Issue #193, the design decision is that write interactions only return success or error, not the written value (optionally). TDs should capture the schema of the Property values, including precision and alternative formats. When a return value is expected from the interaction, an Action should be used instead of a Property.
writeMultipleProperties()
methodInteractionOptions
options argument. It returns success or failure. The method MUST run the following steps:
Promise
promise and execute the next steps
in parallel.
SecurityError
and abort these steps.
formIndex
is defined, let form be the
Form associated with formIndex
in |td|'s forms array,
otherwise let form be the first Form in |td|'s forms
array whose op is writemultipleproperties
.
SyntaxError
and
abort these steps.
null
.
uriVariables
.
NotSupportedError
and abort these steps.
WotListener
callback
User provided callback that takes any
argument and is used
for observing Property changes and handling Event notifications. Since subscribing to these are WoT interactions, they are not modelled with software events.
observeProperty()
methodWotListener
callback function listener and an optional InteractionOptions
options argument. It returns success or failure.
The method MUST run the following steps:
Promise
promise and execute the next steps in parallel.
SecurityError
and abort these steps.
Function
, reject promise
with a TypeError
and abort these steps.
formIndex
is defined, let form be the
Form associated with formIndex
in interaction's forms
array, otherwise let form be the first Form in
interaction's forms array whose op is observeproperty
.
SyntaxError
and abort these steps.
uriVariables
.
unobserveProperty()
methodInteractionOptions
options argument.
It returns success or failure. The method MUST run the following steps:
Promise
promise and execute the next steps in parallel.
SecurityError
and abort these steps.
formIndex
is defined, let form be the
Form associated with formIndex
in interaction's forms
array, otherwise let form be the first Form in
interaction's forms array whose op is unobserveproperty
.
SyntaxError
and abort these steps.
uriVariables
.
invokeAction()
methodInteractionOptions
options argument. It returns the result of the Action or an error. The method MUST run the following steps:
Promise
promise and execute the next steps in parallel.
SecurityError
and abort these steps.
formIndex
is defined, let form be the
Form associated with formIndex
in interaction's forms
array, otherwise let form be the first Form in
interaction's forms array whose op is invokeaction
.
SyntaxError
and abort these steps.
uriVariables
.
subscribeEvent()
methodWotListener
callback function listener and an optional InteractionOptions
options argument. It returns success or failure.
The method MUST run the following steps:
Promise
promise and execute the next steps in parallel.
SecurityError
and abort these steps.
Function
, reject promise
with a TypeError
and abort these steps.
formIndex
is defined, let form be the
Form associated with formIndex
in interaction's forms
array, otherwise let form be the first Form in
interaction's forms array whose op is subscribeevent
.
SyntaxError
and abort these steps.
uriVariables
.
unsubscribeEvent()
methodInteractionOptions
options argument. It returns success or failure. The method MUST run the following steps:
Promise
promise and execute the next steps in parallel.
SecurityError
and abort these steps.
formIndex
is defined, let form be the
Form associated with formIndex
in interaction's forms
array, otherwise let form be the first Form in
interaction's forms array whose op is unsubscribeevent
.
SyntaxError
and abort these steps.
uriVariables
.
The next example illustrates how to fetch a TD by URL, create a
, read metadata (title), read property value, subscribe to property change, subscribe to a WoT event, unsubscribe.
ConsumedThing
try {
let res = await fetch("https://tds.mythings.org/sensor11");
let td = res.json();
let thing = new ConsumedThing(td);
console.log("Thing " + thing.getThingDescription().title + " consumed.");
} catch(e) {
console.log("TD fetch error: " + e.message); },
};
try {
// subscribe to property change for “temperature”
await thing.observeProperty("temperature", value => {
console.log("Temperature changed to: " + parseData(value));
});
// subscribe to the “ready” event defined in the TD
await thing.subscribeEvent("ready", eventData => {
console.log("Ready; index: " + parseData(eventData));
// run the “startMeasurement” action defined by TD
await thing.invokeAction("startMeasurement", { units: "Celsius" });
console.log("Measurement started.");
});
} catch(e) {
console.log("Error starting measurement.");
}
setTimeout( () => {
console.log(“Temperature: “ +
parseData(await thing.readProperty(“temperature”)));
await thing.unsubscribe(“ready”);
console.log("Unsubscribed from the ‘ready’ event.");
},
10000);
async function parseData(response) {
let value = undefined;
try {
value = await response.value();
catch(err) {
// if response.value() fails, try low-level stream read
if (response.dataUsed)
return undefined; // or make a second request
const reader = value.data.getReader();
value = null;
reader.read().then(function process({ done, chunk }) {
if (done) {
value += chunk;
return value;
}
value += chunk;
return reader.read().then(process);
});
}
return value;
};
InteractionData
interfaceBelongs to the WoT Consumer conformance class. Represents the data used by WoT Interactions.
As specified in [WOT-TD], WoT interactions extend DataSchema
and include a number of possible Forms, out of which one is selected
for the interaction. The
Form contains a contentType
to describe the data.
For certain content types, a DataSchema is defined, based on
JSON schema, making possible to represent these contents as
JavaScript types and eventually set range constraints on the data.
This interface exposes the data returned from the WoT Interaction.
WebIDL[SecureContext, Exposed=(Window,Worker)] interfaceInteractionData
{ readonly attribute ReadableStream?data
; readonly attribute booleandataUsed
; readonly attribute Form?form
; readonly attribute DataSchema?schema
; Promise<ArrayBuffer>arrayBuffer
(); Promise<any>value
(); };
The data
property represents the raw payload in
WoT Interactions as a ReadableStream
, initialy null
.
The dataUsed
property tells whether the data stream has
been
disturbed. Initially false
.
The form
attribute represents the Form selected from
the Thing Description for this WoT Interaction,
initially null
.
The schema
attribute represents the DataSchema
of the payload as a JSON
object, initially null
.
The [[value]] internal slot represents the parsed value of
the WoT Interaction, initially undefined
(note that null
is a
valid value).
arrayBuffer()
functionPromise
promise and execute the next steps
in parallel.
ReadableStream
or if dataUsed is true
,
reject|promise| with NotReadableError
and abort these steps.
true
.
ArrayBuffer
whose contents are bytes.
If that throws, reject promise with that exception and abort these steps.
value()
functioncontentType
of the Form used for the
interaction. The method MUST run the following steps:
Promise
promise and execute the next steps
in parallel.
undefined
, resolve promise with that value and abort these steps.
ReadableStream
or if
dataUsed is true
, or if form is null
or if schema or its
type are null
or undefined
,
reject promise with NotReadableError
and abort these steps.
application/json
and if a mapping is
not available in the Protocol Bindings from form's contentType
to [JSON-SCHEMA], reject promise with NotSupportedError
and
abort these steps.
true
.
application/json
and if a mapping is
available in the Protocol Bindings from form's contentType
to [JSON-SCHEMA], transform bytes with that mapping.
"null"
and if payload is not null
,
throw TypeError
and abort these steps, otherwise return null
.
"boolean"
and payload is a falsey value or its byte
length is 0, return false
, otherwise return true
.
"integer"
or "number"
,
TypeError
and abort these steps.
RangeError
and abort these steps.
"string"
, return payload.
"array"
, run these sub-steps:
TypeError
and abort these steps.
RangeError
and abort these steps.
"object"
, run these sub-steps:
TypeError
and abort these steps.
SyntaxError
and abort these steps.
ConsumedThing
object thing, in order to
create interaction data given source, form and
schema, run these steps:
InteractionData
object whose data is a
ReadableStream
, set its form to form,
return source and abort these steps.
InteractionData
object whose [[value]]
internal slot is undefined
, whose form is form,
whose schema is set to schema and whose data is null
.
"null"
and source is not,
throw TypeError
and abort these steps.
"boolean"
and source is a falsy value, set
idata's [[value]] value to false
, otherwise to true
.
"integer"
or "number"
and source is not a number,
or if form's minimum is defined and source is smaller,
or if form's maximum is defined and source is bigger,
throw RangeError
and abort these steps.
"string"
and source is not a string, let idata's
[[value]] be the result of running serialize JSON to bytes
on source.
If that is failure, throw SyntaxError
and abort these steps.
"array"
, run these sub-steps:
TypeError
and abort these steps.
RangeError
and abort these steps.
"object"
, run these sub-steps:
TypeError
and abort these steps.
TypeError
and abort these steps.
SyntaxError
and abort these steps.
ReadableStream
created from idata's
[[value]] internal slot as its
underlying source.
ConsumedThing
object thing, in order to
parse interaction response given response, form and
schema, run these steps:
InteractionData
object.
ReadableStream
with the payload data
of response as its
underlying source.
false
.
ExposedThing
interface
The
interface is the server API to operate the Thing that allows defining request handlers, Property, Action, and Event interactions.
ExposedThing
WebIDL[SecureContext, Exposed=(Window,Worker)] interfaceExposedThing
:ConsumedThing
{ExposedThing
setPropertyReadHandler
(DOMString name,PropertyReadHandler
readHandler);ExposedThing
setPropertyWriteHandler
(DOMString name,PropertyWriteHandler
writeHandler);ExposedThing
setActionHandler
(DOMString name,ActionHandler
action); voidemitEvent
(DOMString name, any data); Promise<void>expose
(); Promise<void>destroy
(); }; callbackPropertyReadHandler
= Promise<any>( optionalInteractionOptions
options = null); callbackPropertyWriteHandler
= Promise<void>(any value, optionalInteractionOptions
options = null); callbackActionHandler
= Promise<any>(any params, optionalInteractionOptions
options = null);
ExposedThing
The
interface extends ExposedThing
. It
is constructed from a full or partial ConsumedThing
object.
ThingDescription
Note that an existing
object can be optionally modified (for instance by adding or removing elements on its properties, actions and events internal properties) and the resulting object can used for constructing an
ThingDescription
object. This is the current way of adding and
removing Property, Action and Event definitions, as illustrated in the examples.
ExposedThing
Before invoking expose(), the
object does not serve any requests. This allows first constructing ExposedThing
and then initialize its Properties and service handlers before starting serving requests.
ExposedThing
ExposedThing
with the ThingDescription
td, run the following steps:
SecurityError
and abort these steps.
ExposedThing
object.
ConsumedThing
The readProperty()
, readMultipleProperties()
, readAllProperties()
, writeProperty()
, writeMultipleProperties()
, writeAllProperties()
methods have the same algorithmic steps as described in ConsumedThing
, with the difference that making a request to the underlying platform MAY be implemented with local methods or libraries and don't necessarily need to involve network operations.
The implementation of
interface in an ConsumedThing
provide the default methods to interact with the ExposedThing
.
ExposedThing
After constructing an
, a script can initialize its Properties and can set up the optional read, write and action request handlers (the default ones are provided by the implementation). The script provided handlers MAY use the default handlers, thereby extending the default behavior, but they can also bypass them, overriding the default behavior. Finally, the script would call ExposedThing
expose()
on the
in order to start serving external requests.
ExposedThing
PropertyReadHandler
callback
A function that is called when an external request for reading a Property is received and defines what to do with such requests. It returns a Promise
and resolves it when the value of the Property matching the name argument is obtained, or rejects with an error if the property is not found or the value cannot be retrieved.
setPropertyReadHandler()
method
Takes name as string argument and readHandler as argument of type PropertyReadHandler
. Sets the service handler for reading the specified Property matched by name. Throws on error. Returns a reference to this object for supporting chaining.
The readHandler callback function should implement reading a Property and SHOULD be called by implementations when a request for reading a Property is received from the underlying platform.
There MUST be at most one handler for any given Property, so newly added handlers MUST replace the previous handlers. If no handler is initialized for any given Property, implementations SHOULD implement a default property read handler based on the Thing Description.
ReferenceError
in the reply and abort these steps.
setPropertyReadHandler()
, invoke that given propertyName, return the obtained value in the reply and abort these steps.
NotSupportedError
with the reply and abort these steps.
PropertyWriteHandler
callback
A function that is called when an external request for writing a Property is received and defines what to do with such requests. It expects the requested new value as argument and returns a Promise
which is resolved when the value of the Property that matches the name argument has been updated with value, or rejects with an error if the property is not found or the value cannot be updated.
Note that the code in this callback function can read the property before updating it in order to find out the old value, if needed. Therefore the old value is not provided to this function.
setPropertyWriteHandler()
method
Takes name as string argument and writeHandler
as argument of type PropertyWriteHandler
. Sets the service handler
that will be used for serving write requests for writing the specified
Property matched by name. Throws on error.
Returns a reference to this object for supporting chaining.
Note that even for readonly Properties it is possible to specify a write handler, as explained in Issue 199. In this case, the write handler may define in an application-specific way to fail the request.
There MUST be at most one write handler for any given Property, so newly added handlers MUST replace the previous handlers. If no write handler is initialized for any given Property, implementations SHOULD implement default property update if the Property is writeable and notifying observers on change if the Property is observeable, based on the Thing Description.
"single"
:
ReferenceError
in the reply and abort these steps.
setPropertyWriteHandler()
, or if there is a default write handler,
"single"
, reply to the request with the new value, following to the Protocol Bindings.
NotSupportedError
in the reply and abort these steps.
"multiple"
.
ActionHandler
callback
A function that is called when an external request for invoking an
Action is received and defines what to do with such requests.
It is invoked with a params dictionary argument.
It returns a Promise
that rejects with an error or resolves if
the action is successful.
setActionHandler()
method
Takes name as string argument and action as argument of type ActionHandler
. Sets the handler function for the specified Action matched by name. May throw an error. Returns a reference to this object for supporting chaining.
The action callback function will implement an Action and SHOULD be called by implementations when a request for invoking the Action is received from the underlying platform.
There MUST be at most one handler for any given Action, so newly added handlers MUST replace the previous handlers.
ReferenceError
in the reply and abort these steps.
setActionHandler()
, invoke that wih name, return the resulting value with the reply and abort these steps.
NotSupportedError
with the reply and abort these steps.
emitEvent()
methodany
type. The method MUST run the following steps:
SecurityError
and abort these steps.
NotFoundError
and abort these steps.
expose()
methodPromise
promise and execute the next steps in parallel.
SecurityError
and abort these steps.
TypeError
and abort these steps.
Error
object error with error's message set to the error code seen by the Protocol Bindings and abort these steps.
destroy()
methodPromise
promise and execute the next steps in parallel.
SecurityError
and abort these steps.
Error
object error with its message set to the error code seen by the Protocol Bindings and abort these steps.
The next example illustrates how to create an
based on a partial TD object constructed beforehands.
ExposedThing
try {
let temperaturePropertyDefinition = {
type: "number",
minimum: -50,
maximum: 10000
};
let tdFragment = {
properties: {
temperature: temperaturePropertyDefinition
},
actions: {
reset: {
description: "Reset the temperature sensor",
input: {
temperature: temperatureValueDefinition
},
output: null,
forms: []
},
},
events: {
onchange: temperatureValueDefinition
}
};
let thing1 = await WOT.produce(tdFragment);
// initialize Properties
await thing1.writeProperty("temperature", 0);
// add service handlers
thing1.setPropertyReadHandler("temperature", () => {
return readLocalTemperatureSensor(); // Promise
});
// start serving requests
await thing1.expose();
} catch (err) {
console.log("Error creating ExposedThing: " + err);
}
The next example illustrates how to add or modify a Property definition on an existing
: take its td property, add or modify it, then create another ExposedThing
with that.
ExposedThing
try {
// create a deep copy of thing1's TD
let instance = JSON.parse(JSON.stringify(thing1.td));
const statusValueDefinition = {
type: "object",
properties: {
brightness: {
type: "number",
minimum: 0.0,
maximum: 100.0,
required: true
},
rgb: {
type: "array",
"minItems": 3,
"maxItems": 3,
items : {
"type" : "number",
"minimum": 0,
"maximum": 255
}
}
};
instance["name"] = "mySensor";
instance.properties["brightness"] = {
type: "number",
minimum: 0.0,
maximum: 100.0,
required: true,
};
instance.properties["status"] = statusValueDefinition;
instance.actions["getStatus"] = {
description: "Get status object",
input: null,
output: {
status : statusValueDefinition;
},
forms: [...]
};
instance.events["onstatuschange"] = statusValueDefinition;
instance.forms = [...]; // update
var thing2 = new ExposedThing(instance);
// TODO: add service handlers
await thing2.expose();
});
} catch (err) {
console.log("Error creating ExposedThing: " + err);
}
ThingDiscovery
interfaceDiscovery is a distributed application that requires provisioning and support from participating network nodes (clients, servers, directory services). This API models the client side of typical discovery schemes supported by various IoT deployments.
The
object is constructed given a filter and provides the properties and methods controlling the discovery process.
ThingDiscovery
WebIDL[SecureContext, Exposed=(Window,Worker)] interfaceThingDiscovery
{constructor
(optionalThingFilter
filter = null); readonly attributeThingFilter
?filter
; readonly attribute booleanactive
; readonly attribute booleandone
; readonly attribute Error?error
; voidstart
(); Promise<ThingDescription
>next
(); voidstop
(); };
The
interface has a ThingDiscovery
next()
method and a done
property, but it is not an Iterable. Look into Issue 177 for rationale.
The discovery results internal slot is an internal queue for temporarily storing the found
objects until they are consumed by the application using the next() method. Implementations MAY optimize the size of this queue based on e.g. the available resources and the frequency of invoking the next() method.
ThingDescription
The filter
property represents the discovery filter of type ThingFilter
specified for the discovery.
The active
property is true
when the discovery is actively ongoing on protocol level (i.e. new TDs may still arrive) and false
otherwise.
The done
property is true
if the discovery has been completed with no more results to report and discovery results is also empty.
The error
property represents the last error that occured during the discovery process. Typically used for critical errors that stop discovery.
ThingDiscovery
ThingDiscovery
with a filter or type ThingFilter
, run the following steps:
The start() method sets active to true
. The stop() method sets the active property to false, but done may be still false
if there are
objects in the discovery results not yet consumed with next().
ThingDescription
During successive calls of next(), the active property may be true
or false
, but the done property is set to false
by next() only when both the active property is false
and discovery results is empty.
DiscoveryMethod
enumerationWebIDLtypedef DOMString DiscoveryMethod
;
Represents the discovery type to be used:
ThingFilter
dictionaryRepresents an object containing the constraints for discovering Things as key-value pairs.
WebIDLdictionaryThingFilter
{ (DiscoveryMethod
or DOMString)method
= "any"; USVString?url
; USVString?query
; object?fragment
; };
The method
property represents the discovery type that should be used in the discovery process. The possible values are defined by the DiscoveryMethod enumeration that MAY be extended by string values defined by solutions (with no guarantee of interoperability).
The url
property represents additional information for the discovery method, such as the URL of the target entity serving the discovery request, for instance the URL of a Thing Directory (if method is "directory"
), or otherwise the URL of a directly targeted Thing.
The query
property represents a query string accepted by the implementation, for instance a SPARQL or JSON query. Support may be implemented locally in the WoT Runtime or remotely as a service in a Thing Directory.
The fragment
property represents a template object used for matching property by property against discovered Things.
start()
methodSecurityError
and abort these steps.
NotSupportedError
and abort these steps.
NotSupportedError
and abort these steps.
ThingDescription
objects.
"any"
, use the widest discovery method supported by the underlying platform.
"local"
, use the local Thing Directory for discovery. Usually that defines Things deployed in the same device, or connected to the device in slave mode (e.g. sensors connected via Bluetooth or a serial connection).
"directory"
, use the remote Thing Directory specified in |filter.url|.
"multicast"
, use all the multicast discovery protocols supported by the underlying platform.
true
.
SyntaxError
, discard td and continue the discovery process.
false
, discard td and continue the discovery process.
false
in any checks, discard td and continue the discovery process.
Error
object. Set error's name to "DiscoveryError"
.
false
.
false
.
next()
methodThingDescription
object. The method MUST run the following steps:
Promise
promise and execute the next steps in parallel.
true
, wait until the discovery results internal slot is not empty.
false
, set the done property to true
and reject promise.
ThingDescription
object td from discovery results.
stop()
methodfalse
.
The following example finds
objects of Things that are exposed by local hardware, regardless how many instances of WoT Runtime it is running. Note that the discovery can end (become inactive) before the internal discovery results queue is emptied, so we need to continue reading ThingDescription
objects until done. This is typical with local and directory type discoveries.
ThingDescription
let discovery = new ThingDiscovery({ method: "local" });
do {
let td = await discovery.next();
console.log("Found Thing Description for " + td.title);
let thing = new ConsumedThing(td);
console.log("Thing name: " + thing.getThingDescription().title);
} while (!discovery.done);
The next example finds
objects of Things listed in a Thing Directory service. We set a timeout for safety.
ThingDescription
let discoveryFilter = {
method: "directory",
url: "http://directory.wotservice.org"
};
let discovery = new ThingDiscovery(discoveryFilter);
setTimeout( () => {
discovery.stop();
console.log("Discovery stopped after timeout.");
},
3000);
do {
let td = await discovery.next();
console.log("Found Thing Description for " + td.title);
let thing = new ConsumedThing(td);
console.log("Thing name: " + thing.getThingDescription().title);
} while (!discovery.done);
if (discovery.error) {
console.log("Discovery stopped because of an error: " + error.message);
}
The next example is for an open-ended multicast discovery, which likely won't complete soon (depending on the underlying protocol), so stopping it with a timeout is a good idea. It will likely deliver results one by one.
let discovery = new ThingDiscovery({ method: "multicast" });
setTimeout( () => {
discovery.stop();
console.log("Stopped open-ended discovery");
},
10000);
do {
let td = await discovery.next();
let thing = new ConsumedThing(td);
console.log("Thing name: " + thing.getThingDescription().title);
} while (!discovery.done);
A detailed discussion of security and privacy considerations for the Web of Things, including a threat model that can be adapted to various circumstances, is presented in the informative document [WOT-SECURITY-GUIDELINES]. This section discusses only security and privacy risks and possible mitigations directly relevant to the scripts and WoT Scripting API.
A suggested set of best practices to improve security for WoT devices and services has been documented in [WOT-SECURITY-BEST-PRACTICES]. That document may be updated as security measures evolve. Following these practices does not guarantee security, but it might help avoid common known vulnerabilities.
This section is normative and contains specific risks relevant for the WoT Scripting Runtime.
A typical way to compromise any process is to send it a corrupted input via one of the exposed interfaces. This can be done to a script instance using WoT interface it exposes.
In case a script is compromised or misbehaving, the underlying physical device (and potentially surrounded environment) can be damaged if a script can use directly exposed native device interfaces. If such interfaces lack safety checks on their inputs, they might bring the underlying physical device (or environment) to an unsafe state (i.e. device overheats and explodes).
If the WoT Scripting Runtime supports post-manufacturing provisioning or updates of scripts, WoT Scripting Runtime or any related data (including security credentials), it can be a major attack vector. An attacker can try to modify any above described element during the update or provisioning process or simply provision attacker's code and data directly.
Typically the WoT Scripting Runtime needs to store the security credentials that are provisioned to a WoT device to operate in WoT network. If an attacker can compromise the confidentiality or integrity of these credentials, then it can obtain access to the WoT assets, impersonate WoT things or devices or create Denial-Of-Service (DoS) attacks.
This section is non-normative.
This section describes specific risks relevant for script developers.
A script instance may receive data formats defined by the TD, or data formats defined by the applications. While the WoT Scripting Runtime SHOULD perform validation on all input fields defined by the TD, scripts may be still exploited by input data.
If a script performs a heavy functional processing on received requests before the request is authenticated, it presents a great risk for Denial-Of-Service (DOS) attacks.
During the lifetime of a WoT network, a content of a TD can change. This includes its identifier, which might not be an immutable one and might be updated periodically.
While stale TDs can present a potential problem for WoT network operation, it might not be a security risk.
The generic WoT terminology is defined in [WOT-ARCHITECTURE]: Thing, Thing Description (in short TD), Web of Things (in short WoT), WoT Interface (same as WoT network interface), Protocol Bindings, WoT Runtime, Consuming a Thing Description, Thing Directory, WoT Interactions, Property, Action, Event, DataSchema, Form etc.
JSON-LD is defined in [JSON-LD] as a JSON document that is augmented with support for Linked Data.
JSON schema is defined in these specifications.
The terms URL, URL scheme, URL host, URL path, URL record, parse a URL, absolute-URL string, path-absolute-URL string, basic URL parser are defined in [URL].
The terms MIME type, Parsing a MIME type, Serializing a MIME type, valid MIME type string, JSON MIME type are defined in [MIMESNIFF].
The terms UTF-8 encoding, UTF-8 decode, encode, decode are defined in [ENCODING].
string, parse JSON from bytes and serialize JSON to bytes, are defined in [INFRA].
Promise
,
Error,
JSON,
JSON.stringify,
JSON.parse,
internal method and
internal slot are defined in [ECMASCRIPT].
The terms browsing context, top-level browsing context, global object, current settings object, executing algorithms in parallel are defined in [HTML5] and are used in the context of browser implementations.
The term secure context is defined in [WEBAPPSEC].
IANA media types (formerly known as MIME types) are defined in RFC2046.
The terms hyperlink reference and relation type are defined in [HTML5] and RFC8288.
API rationale usually belongs to a separate document, but in the WoT case the complexity of the context justifies including basic rationale here.
The WoT Interest Group and Working Group have explored different approaches to application development for WoT that have been all implemented and tested.
It is possible to develop WoT applications that only use the WoT network interface, typically exposed by a WoT gateway that presents a REST-ful API towards clients and implements IoT protocol plugins that communicate with supported IoT deployments. One such implementation is the Mozilla WebThings platform.
WoT Things show good synergy with software objects, so a Thing can be represented as a software object, with Properties represented as object properties, Actions as methods, and Events as events. In addition, metadata is stored in special properties. Consuming and exposing is done with factory methods that produce a software object that directly represents a remote Thing and its interactions. One such implementation is the Arena Web Hub project.
In the next example, a Thing that represents interactions with
a lock would look like the following: the status property
and the open()
method are directly exposed on the object.
let lock = await WoT.consume(‘https://td.my.com/lock-00123’);
console.log(lock.status);
lock.open('withThisKey');
Since the direct mapping of Things to software objects have had some challenges, this specification takes another approach that exposes software objects to represent the Thing metadata as data property and the WoT interactions as methods. One implementation is node-wot in the the Eclipse ThingWeb project, which is the current reference implementation of the API specified in this document.
The same example now would look like the following: the
status property and the open()
method are
represented indirectly.
let res = await fetch(‘https://td.my.com/lock-00123’);
let td = await res.json();
let lock = new ConsumedThing(td);
console.log(lock.readProperty(‘status’));
lock.invokeAction(‘open’, 'withThisKey');
In conclusion, the WoT WG decided to explore the third option that closely follows the [WOT-TD] specification. Based on this, a simple API can also be implemented. Since Scripting is an optional module in WoT, this leaves room for applications that only use the WoT network interface. Therefore all three approaches above are supported by [WOT-TD].
Moreover, the WoT network interface can be implemented in many languages and runtimes. Consider this API an example for what needs to be taken into consideration when designing a Scripting API for WoT.
The fetch(url)
method has been part of this API in earlier versions. However, now fetching a TD given a URL should be done with an external method, such as the Fetch API or a HTTP client library, which offer already standardized options on specifying fetch details. The reason is that while simple fetch operations (covering most use cases) could be done in this API, when various fetch options were needed, there was no point in duplicating existing work to re-expose those options in this API.
Since fetching a TD has been scoped out, and TD validation is defined externally in [WOT-TD], that is scoped out, too. This specification expects a TD as parsed JSON object that has been validated according to the [WOT-TD] specification.
The factory methods for consuming and exposing Things are asynchronous and fully validate the input TD. In addition, one can also construct
and ConsumedThing
by providing a parsed and validated TD. Platform initialization is then done when needed during the WoT interactions. So applications that prefer validating a TD themselves, may use the constructors, whereas applications that leave validation to implementations and prefer interactions initialized up front SHOULD use the factory methods on the WoT API object.
ExposedThing
Earlier drafts used the Observer construct, but since it has not become standard, a new design was needed that was light enough for embedded implementations. Therefore observing Property changes and handling WoT Events is done with callback registrations.
The reason to use function names like readProperty()
, readMultipleProperties()
etc. instead of a generic polymorphic read()
function is that the current names map exactly to the "op"
vocabulary from the Form definition in [WOT-TD].
fetch()
for fetching a TD (delegated to external API).
Observer
and use W3C TAG recommended design patterns.
ThingDescription
instead.
ConsumedThing
and ExposedThing
.
For a complete list of changes, see the github change log. You can also view the recently closed issues.
ExposedThing
(it was present in earlier versions, but removed for complexity and a simpler way to do it.
WebIDLtypedef objectThingDescription
; [SecureContext, Exposed=(Window,Worker)] interfaceWOT
{ // methods defined in UA conformance classes }; partial interfaceWOT
{ Promise<ConsumedThing
>consume
(ThingDescription
td); }; partial interfaceWOT
{ Promise<ExposedThing
>produce
(ThingDescription
td); }; partial interfaceWOT
{ThingDiscovery
discover
(optionalThingFilter
filter = null); }; [SecureContext, Exposed=(Window,Worker)] interfaceConsumedThing
{constructor
(ThingDescription
td); Promise<InteractionData
>readProperty
(DOMString propertyName, optionalInteractionOptions
options = null); Promise<PropertyMap
>readAllProperties
(optionalInteractionOptions
options = null); Promise<PropertyMap
>readMultipleProperties
( sequence<DOMString> propertyNames, optionalInteractionOptions
options = null); Promise<void>writeProperty
(DOMString propertyName, any value, optionalInteractionOptions
options = null); Promise<void>writeMultipleProperties
(PropertyMap
valueMap, optionalInteractionOptions
options = null); Promise<any>invokeAction
(DOMString actionName, optional any params = null, optionalInteractionOptions
options = null); Promise<void>observeProperty
(DOMString name,WotListener
listener, optionalInteractionOptions
options = null); Promise<void>unobserveProperty
(DOMString name, optionalInteractionOptions
options = null); Promise<void>subscribeEvent
(DOMString name,WotListener
listener, optionalInteractionOptions
options = null); Promise<void>unsubscribeEvent
(DOMString name, optionalInteractionOptions
options = null);ThingDescription
getThingDescription
(); }; dictionaryInteractionOptions
{ unsigned longformIndex
; objecturiVariables
; }; typedef objectPropertyMap
; callbackWotListener
= void(any data); [SecureContext, Exposed=(Window,Worker)] interfaceInteractionData
{ readonly attribute ReadableStream?data
; readonly attribute booleandataUsed
; readonly attribute Form?form
; readonly attribute DataSchema?schema
; Promise<ArrayBuffer>arrayBuffer
(); Promise<any>value
(); }; [SecureContext, Exposed=(Window,Worker)] interfaceExposedThing
:ConsumedThing
{ExposedThing
setPropertyReadHandler
(DOMString name,PropertyReadHandler
readHandler);ExposedThing
setPropertyWriteHandler
(DOMString name,PropertyWriteHandler
writeHandler);ExposedThing
setActionHandler
(DOMString name,ActionHandler
action); voidemitEvent
(DOMString name, any data); Promise<void>expose
(); Promise<void>destroy
(); }; callbackPropertyReadHandler
= Promise<any>( optionalInteractionOptions
options = null); callbackPropertyWriteHandler
= Promise<void>(any value, optionalInteractionOptions
options = null); callbackActionHandler
= Promise<any>(any params, optionalInteractionOptions
options = null); [SecureContext, Exposed=(Window,Worker)] interfaceThingDiscovery
{constructor
(optionalThingFilter
filter = null); readonly attributeThingFilter
?filter
; readonly attribute booleanactive
; readonly attribute booleandone
; readonly attribute Error?error
; voidstart
(); Promise<ThingDescription
>next
(); voidstop
(); }; typedef DOMStringDiscoveryMethod
; dictionaryThingFilter
{ (DiscoveryMethod
or DOMString)method
= "any"; USVString?url
; USVString?query
; object?fragment
; };
Special thanks to former editor Johannes Hund (until August 2017, when at Siemens AG) and Kazuaki Nimura (until December 2018) for developing this specification. Also, the editors would like to thank Dave Raggett, Matthias Kovatsch, Michael Koster, Elena Reshetova, Michael McCool as well as the other WoT WG members for their comments, contributions and guidance.