The key 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 format, the 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 and 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.

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.

Introduction

WoT provides layered interoperability based on how Things are used: "consumed" and "exposed".

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.

Exposing a Thing requires

This specification describes how to expose and consume Things by a script.

Typically scripts are meant to be used on devices able to provide resources (with a WoT Interface) for managing (installing, updating, running) scripts, such as bridges or gateways that expose and control simpler devices as WoT Things.

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.

For an introduction on how scripts could be used in Web of Things, check the Primer document. For some background on API design decisions check the Rationale document.

Use Cases

The following scripting use cases are supported in this specification:

Consuming a Thing

Exposing a Thing

Discovery

The WOT object

Defines the API entry point exposed as a singleton and contains the API methods for consuming and producing a Thing based on Thing Descriptions, together with a generic discovery API.

Browser implementations should use a namespace object such as navigator.wot. Standalone runtimes may expose the API object through mechanisms like require() or import.

      [SecureContext, Exposed=(Window,Worker)]
      interface WOT {
        ConsumedThing consume(ThingDescription td);
        ExposedThing produce(ThingDescription td);
      };
      typedef (USVString or object) ThingDescription;
      typedef object ThingInstance;
    

The ThingDescription type

Represents a Thing Description (TD). It is expected to be either a ThingDescription string (a JSON-serialized object as described in the TD serialization), or a ThingDescription object (a parsed JSON object validated using JSON schema validation). A ThingDescription object is obtained by parsing a ThingDescription string.

To parse a ThingDescription string td, run the following steps:

  1. If td is not a string, throw TypeError and terminate these steps.
  2. Let json be the result of invoking parse JSON from bytes on td. If that fails, throw SyntaxError and terminate these steps.
  3. Return json.

Fetching a Thing Description

This API works with Thing Descriptions that can be obtained either by direct fetch or by discovery.

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.

        try {
          let res = await fetch('https://tds.mythings.biz/sensor11');
          // ... additional checks possible on res.headers
          let td = await res.json();  // could also be res.text()
          let thing = WOT.consume(td);
          console.log("Thing name: " + thing.instance.name);
        } catch (err) {
          console.log("Fetching TD failed", err.message);
        }
      

The ThingInstance type

Denotes an object that is obtained from a ThingDescription object and represents an instantiated TD Thing, i.e. a Thing Description that has been initialized in the local WoT Runtime.

To instantiate a TD, given td, run the following steps:

  1. If the td argument is not a string or an object, throw TypeError and terminate these steps.
  2. If td is a string, let json be the result of running the parse a ThingDescription string steps on td. If that throws an error, re-throw the error and terminate these steps.
  3. If td is an object, let json be td.
  4. Let instance be an object with properties and default values defined in TD Thing.

    As the TD specification owns the structure, validation and serialization of these objects, please refer to that specification on what properties need to be on the instance object. In this specification, instance is considered implementation-internal metadata, exposed for introspection.

  5. Update the properties of instance also defined in json with the values defined in json.

    When more efficient, implementations MAY reverse the last two steps, i.e. let instance be the result of adding json the missing properties with default values as defined in TD specification.

  6. Update instance with the WoT Runtime local settings.
    1. Initialize instance.id to be the final unique identifier of the Thing instance.
    2. Initialize instance.security to the actual security scheme used by the WoT Runtime.
    3. Update instance.forms with local settings specific to the WoT Runtime. Also, update the runtime-specific parts (if any) for the forms array of all elements in instance.properties, instance.actions and instance.events.
  7. Validate instance according to the TD instance validation. If that fails, throw SyntaxError and terminate these steps.
  8. Return instance.

The consume() method

Accepts an td argument and returns a ConsumedThing object that represents a client interface to operate with the Thing. The method MUST run the following steps:

  1. Let instance be the result of running the instantiate a TD steps on td. If that throws, re-throw the error and terminate these steps.
  2. Let thing be a new ConsumedThing object constructed with instance.
  3. Return thing.

The produce() method

Accepts a td argument and returns 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:

  1. If invoking this method is not allowed for the current scripting context for security reasons, throw SecurityError and terminate these steps.
  2. Let instance be the result of running the instantiate a TD steps on td. If that throws, re-throw the error and terminate these steps.
  3. Let thing be a new ExposedThing object constructed with instance. The internal properties of thing.instance SHOULD be used for setting up the WoT Interactions based on the Thing Description as explained in [[!WOT-TD]]. This part is private to the implementations.
  4. For each Property definition in thing.instance.properties initialize an internal observer list in order to store observe request data needed to notify the observers on value changes.
  5. Return thing.

The ConsumedThing interface

Represents a client API to operate a Thing.

      [Constructor(ThingInstance instance), SecureContext, Exposed=(Window,Worker)]
      interface ConsumedThing {
        Promise<any> readProperty(DOMString propertyName);
        Promise<object> readAllProperties();
        Promise<object> readMultipleProperties(sequence<DOMString> propertyNames);
        Promise<void> writeProperty(DOMString propertyName, any value);
        Promise<void> writeMultipleProperties(object valueMap);
        Promise<any> invokeAction(DOMString actionName, optional any params);
        Promise<void> observeProperty(DOMString name, WotListener listener);
        Promise<void> unobserveProperty(DOMString name);
        Promise<void> subscribeEvent(DOMString name, WotListener listener);
        Promise<void> unsubscribeEvent(DOMString name);
        readonly attribute ThingInstance instance;
      };
      callback WotListener = void(any data);
    

Constructing ConsumedThing

A ConsumedThing object is constructed by providing a ThingInstance object that initializes the instance attribute of ConsumedThing, without running the instantiate a TD steps.

The ConsumedThing objects SHOULD only be created by the WOT.consume() method which internally uses this constructor by providing a ThingInstance object with a ThingInstance object obtained either directly or by parsing a ThingDescription and also running the instantiate a TD steps on it.

Note that a valid ThingInstance object can be provided either by the instantiate a TD steps, or also by external libraries that implement [[!WOT-TD]].

The WotListener callback

User provided callback that takes any argument and is used for Property change and Event subscriptions. As subscriptions are WoT interactions, they are not modelled with software events.

The readProperty() method

Reads a Property value. Takes a string argument propertyName and returns a Property value represented as any type. The method MUST run the following steps:

  1. Return a Promise promise and execute the next steps in parallel.
  2. If invoking this method is not allowed for the current scripting context for security reasons, reject promise with SecurityError and terminate these steps.
  3. Make a request to the underlying platform (via the Protocol Bindings) to retrieve the value of the Property given by propertyName.
  4. If the request fails, reject promise with the error received from the Protocol Bindings and terminate these steps.
  5. Let value be the result of the request.
  6. Run the validate Property value sub-steps on value:
    1. Based on the DataSchema definition, value MUST be a JSON value and comply to the data schema defined for the Property that is found in this.instance.properties[propertyName].
    2. If this fails, throw SyntaxError, otherwise return value.
  7. If these above steps failed, reject promise with SyntaxError and terminate these steps.
  8. Otherwise resolve promise with value.

The readMultipleProperties() method

Reads multiple Property values with one or multiple requests. Takes one argument, a sequence of strings propertyNames and returns an object with keys from propertyNames and values returned by this algorithm. The method MUST run the following steps:

  1. Return a Promise promise and execute the next steps in parallel.
  2. If invoking this method is not allowed for the current scripting context for security reasons, reject promise with SecurityError and terminate these steps.
  3. Let result be an object and for each string name in propertyNames add a property with key name and the value null.
  4. Make a request to the underlying platform (via the Protocol Bindings) to retrieve the Property values given by propertyNames. If the Protocol Bindings support this using one request, use that, otherwise run the readProperty() steps on each property name in propertyNames and options and store the resulting values in result for each name in propertyNames.
  5. If the above step fails at any point, reject promise with SyntaxError and terminate these steps.
  6. Resolve promise with result.

The readAllProperties() method

Reads all properties of the Thing with one or multiple requests. Takes no arguments. It returns an object with keys from Property names and values returned by this algorithm. The method MUST run the following steps:

  1. Let propertyNames be a sequence created from all the Property names of this Thing as found in this.instance.properties.
  2. Let result be the result of running the readMultipleProperties() steps on propertyNames and options. If that fails, reject promise with that error and terminate these steps.
  3. Resolve promise with result.

The writeProperty() method

Writes a single Property. Takes a string argument propertyName and a value argument value. It returns success or failure. The method MUST run the following steps:

  1. Return a Promise promise and execute the next steps in parallel.
  2. If invoking this method is not allowed for the current scripting context for security reasons, reject promise with SecurityError and terminate these steps.
  3. Run the validate Property value steps on value. If that fails, reject promise with SyntaxError and terminate these steps.
  4. Make a request to the underlying platform (via the Protocol Bindings) to write value to the Property given by propertyName.
  5. If the request fails, reject promise with the error received from the Protocol Bindings and terminate these steps.
  6. Otherwise resolve promise.

The writeMultipleProperties() method

Writes a multiple Property values with one request. Takes one argument, an object properties with keys as Property names and values as Property values. It returns success or failure. The method MUST run the following steps:

  1. Return a Promise promise and execute the next steps in parallel.
  2. If invoking this method is not allowed for the current scripting context for security reasons, reject promise with SecurityError and terminate these steps.
  3. For each key name on properties, take its value as value and run the validate Property value steps on value. If that fails in for any name, reject promise with SyntaxError and terminate these steps.
  4. Make a single request to the underlying platform (via the Protocol Bindings) to write the each Property provided in properties. If this cannot be done with a single request with the Protocol Bindings of the Thing, then reject promise with NotSupportedError and terminate these steps.
  5. If the request fails, return the error received from the Protocol Bindings and terminate these steps.
  6. Otherwise resolve promise.

The observeProperty() method

Makes a request for Property value change notifications. Takes a string argument propertyName and returns success or failure. The method MUST run the following steps:

  1. Return a Promise promise and execute the next steps in parallel.
  2. If invoking this method is not allowed for the current scripting context for security reasons, reject promise with SecurityError and terminate these steps.
  3. Make a request to the underlying platform (via the Protocol Bindings) to observe Property identified by propertyName.
  4. If the request fails, reject promise with the error received from the Protocol Bindings and terminate these steps.
  5. Otherwise resolve promise.

The unobserveProperty() method

Makes a request for unsubscribing from Property value change notifications. Takes a string argument propertyName and returns success or failure. The method MUST run the following steps:

  1. Return a Promise promise and execute the next steps in parallel.
  2. If invoking this method is not allowed for the current scripting context for security reasons, reject promise with SecurityError and terminate these steps.
  3. Make a request to the underlying platform (via the Protocol Bindings) to stop observing the Property identified by propertyName.
  4. If the request fails, reject promise with the error received from the Protocol Bindings and terminate these steps.
  5. Otherwise resolve promise.

The invokeAction() method

Makes a request for invoking an Action and return the result. Takes a string argument actionName, and an optional argument parameters of type any. It returns the result of the Action or an error. The method MUST run the following steps:

  1. Return a Promise promise and execute the next steps in parallel.
  2. If invoking this method is not allowed for the current scripting context for security reasons, reject promise with SecurityError and terminate these steps.
  3. Make a request to the underlying platform (via the Protocol Bindings) to invoke the Action identified by actionName with parameters provided in params.
  4. If the request fails locally or returns an error over the network, reject promise with the error received from the Protocol Bindings and terminate these steps.
  5. Otherwise let value be the result returned in the reply and run the validate Property value steps on it. If that fails, reject promise with SyntaxError and terminate these steps.
  6. Reject promise with value.

The subscribeEvent() method

Makes a request for subscribing to Event notifications. Takes a string argument eventName and returns success or failure. The method MUST run the following steps:

  1. Return a Promise promise and execute the next steps in parallel.
  2. If invoking this method is not allowed for the current scripting context for security reasons, reject promise with SecurityError and terminate these steps.
  3. Make a request to the underlying platform (via the Protocol Bindings) to subscribe to an Event identified by eventName.
  4. If the request fails, reject promise with the error received from the Protocol Bindings and terminate these steps.
  5. Otherwise resolve promise.

The unsubscribeEvent() method

Makes a request for unsubscribing from Event notifications. Takes a string argument eventName and returns success or failure. The method MUST run the following steps:

  1. Return a Promise promise and execute the next steps in parallel.
  2. If invoking this method is not allowed for the current scripting context for security reasons, reject promise with SecurityError and terminate these steps.
  3. Make a request to the underlying platform (via the Protocol Bindings) to unsubscribe from the Event identified by eventName.
  4. If the request fails, reject promise with the error received from the Protocol Bindings and terminate these steps.
  5. Otherwise resolve promise.

ConsumedThing Examples

This example illustrates how to fetch a TD by URL, create a ConsumedThing, read metadata (name), read property value, subscribe to property change, subscribe to a WoT event, unsubscribe.

        try {
          let res = await fetch("https://tds.mythings.org/sensor11");
          let td = res.json();
          let thing = new ConsumedThing(td);
          console.log("Thing " + thing.instance.name + " 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: " + value);
          });
          // subscribe to the “ready” event defined in the TD
          await thing.subscribeEvent("ready", eventData => {
            console.log("Ready; index: " + 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: “ + await thing.readProperty(“temperature”));
          await thing.unsubscribe(“ready”);
          console.log("Unsubscribed from the ‘ready’ event.");
        },
        10000);
      

The ExposedThing interface

The ExposedThing interface is the server API to operate the Thing that allows defining request handlers, Property, Action, and Event interactions.

      [Constructor(ThingInstance instance), SecureContext, Exposed=(Window,Worker)]
      interface ExposedThing: ConsumedThing {
        ExposedThing setPropertyReadHandler(DOMString name, PropertyReadHandler readHandler);
        ExposedThing setPropertyWriteHandler(DOMString name, PropertyWriteHandler writeHandler);
        ExposedThing setActionHandler(DOMString name, ActionHandler action);
        void emitEvent(DOMString name, any data);
        Promise<void> expose();
        Promise<void> destroy();
      };
      callback PropertyReadHandler = Promise<any>();
      callback PropertyWriteHandler = Promise<void>(any value);
      callback ActionHandler = Promise<any>(any parameters);
    

Constructing ExposedThing

The ExposedThing interface extends ConsumedThing and is constructed by providing a ThingInstance object that initializes the instance attribute of ExposedThing, without running the instantiate a TD steps.

The ExposedThing objects SHOULD only be created by the produce() method which internally uses the ExposedThing constructor with a ThingInstance object obtained either directly or by parsing a ThingDescription and also running the instantiate a TD steps on it.

Note that a valid ThingInstance object can be provided either by the instantiate a TD steps, or also by external libraries that implement [[!WOT-TD]].

Note that an existing ThingInstance 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 another ExposedThing object. This is the current way of adding and removing Property, Action and Event definitions, and later adding the service handler functions. This is illustrated in the examples.

The 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.

The 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 MAY implement a default property read handler based on the Thing Description.

Handling Property read requests

When a network request for reading Property propertyName is received by the implementation, run the following steps:

  1. If a Property with propertyName does not exist, return ReferenceError in the reply and terminate these steps.
  2. If there is a user provided read handler registered with setPropertyReadHandler(), invoke that wih propertyName, return the value with the reply and terminate these steps.
  3. Otherwise, if there is a default read handler provided by the implementation, invoke it with propertyName, return the value with the reply and terminate these steps.
  4. if there is no default handler defined by the implementation, return NotSupportedError with the reply and terminate these steps.

Handling Property observe requests

When a network request for observing a Property propertyName is received by the implementation, run the following steps:

  1. If a Property with propertyName does not exist, return ReferenceError in the reply and terminate these steps.
  2. Save the request sender information to the Property's internal observer list in order to be able to notify about Property value changes.

The 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.

The setPropertyWriteHandler() method

Takes name as string argument and writeHandler as argument of type PropertyWriteHandler. Sets the service handler for writing the specified Property matched by name. Throws on error. Returns a reference to this object for supporting chaining.

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 MAY implement default property update and notifying observers on change, based on the Thing Description.

Handling Property write requests

When a network request for writing a Property propertyName with a new value value is received, implementations SHOULD run the following update property steps, given propertyName, value and mode set to "single":

  1. If a Property with propertyName does not exist, return ReferenceError in the reply and terminate these steps.
  2. If there is a user provided write handler registered with setPropertyWriteHandler(), or if there is a default write handler,
    1. Invoke the handler with propertyName. If it fails, return the error in the reply and terminate these steps.
    2. Otherwise, if mode is "single", reply to the request with the new value, following to the Protocol Bindings.
    3. For each item stored in the internal observer list of the Property with propertyName, send an observe reply with the new value attached.
    4. If there is no handler to handle the request, return NotSupportedError in the reply and terminate these steps.

    When a network request for writing multiple Properties given in an object propertyNames is received, run the following steps:

    1. For each property with key name and value value defined in propertyNames, run the update property steps with name, value and mode set to "multiple".
    2. Reply to the request (by sending a single or multiple replies) according to the Protocol Bindings defined for the Property.

The 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 expects a parameters dictionary argument compiled by the implementation (based on the Thing Description and the external client request). It returns a Promise that rejects with an error or resolves if the action is successful.

The 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. Throws on 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.

Handling Action requests

When a network request for invoking the Action identified by name is received, the runtime SHOULD execute the following steps:

  1. If an Action identified by name does not exist, return ReferenceError in the reply and terminate these steps.
  2. If there is a user provided action handler registered with setActionHandler(), invoke that wih name, return the resulting value with the reply and terminate these steps.
  3. Otherwise return NotSupportedError with the reply and terminate these steps.

The emitEvent() method

Takes name as string argument denoting an Event name, and a data argument of any type. The method MUST run the following steps:

  1. If invoking this method is not allowed for the current scripting context for security reasons, throw SecurityError and terminate these steps.
  2. If an Event with the name name is not found in this.instance.events, throw NotFoundError and terminate these steps.
  3. Make a request to the underlying platform to send an Event with data attached as property, using the Protocol Bindings, then terminate these steps.

The expose() method

Start serving external requests for the Thing, so that WoT Interactions using Properties, Actions and Events will be possible. The method MUST run the following steps:

  1. Return a Promise promise and execute the next steps in parallel.
  2. If invoking this method is not allowed for the current scripting context for security reasons, reject promise with SecurityError and terminate these steps.
  3. Make a request to the underlying platform to initialize the Protocol Bindings and then start serving external requests for WoT Interactions (read, write and observe Properties, invoke Actions and manage Event subscriptions), based on the Protocol Bindings.
  4. If there was an error during the request, reject promise with an Error object error with error.message set to the error code seen by the Protocol Bindings and terminate these steps.
  5. Otherwise resolve promise with td and terminate these steps.

The destroy() method

Stop serving external requests for the Thing and destroy the object. Note that eventual unregistering should be done before invoking this method. The method MUST run the following steps:

  1. Return a Promise promise and execute the next steps in parallel.
  2. If invoking this method is not allowed for the current scripting context for security reasons, reject promise with SecurityError and terminate these steps.
  3. Make a request to the underlying platform to stop serving external requests for WoT Interactions, based on the Protocol Bindings.
  4. If there was an error during the request, reject promise with an Error object error with error.message set to the error code seen by the Protocol Bindings and terminate these steps.
  5. Otherwise resolve promise with td and terminate these steps.

ExposedThing Examples

Below some ExposedThing interface examples are given.

        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 = WOT.produce(tdFragment);
          // TODO: add service handlers
          await thing1.expose();
          // define Thing business logic
          setInterval( async () => {
            let mock = Math.random()*100;
            let old = await thing1.readProperty("temperature");
            if (old < mock) {
              await thing1.writeProperty("temperature", mock);
            }
          }, 1000);
        } catch (err) {
           console.log("Error creating ExposedThing: " + err);
        }
      
        try {
          // create a deep copy of thing1 instance
          let instance = JSON.parse(JSON.stringify(thing1.instance));
          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 = WOT.produce(instance);
          // TODO: add service handlers
          await thing2.expose();
          });
        } catch (err) {
           console.log("Error creating ExposedThing: " + err);
        }
      
        // Typically a TD is obtained from somewhere, but let's write it now.
        let thingDescription = '{ \
          "name": "mySensor", \
          "@context": [ "http://www.w3.org/ns/td",\
             "https://w3c.github.io/wot/w3c-wot-common-context.jsonld" ],\
          "@type": [ "Thing", "Sensor" ], \
          "geo:location": "testspace", \
          "properties": { \
            "prop1": { \
              "type": "number",\
              "@type": [ "Property", "Temperature" ], \
              "saref:TemperatureUnit": "degree_Celsius" \
          } } }';
        try {
          // note that produce() fails if the TD contains an error
          let thing = WOT.produce(thingDescription);
          // Interactions were added from TD
          // WoT adds generic handler for reading any property
          // Define a specific handler for a Property
          thing.setPropertyReadHandler("prop1", () => {
            return new Promise((resolve, reject) => {
                let examplePropertyValue = 5;
                resolve(examplePropertyValue);
              },
              e => {
                console.log("Error");
              });
          });
          await thing.expose();
        } catch(err) {
           console.log("Error creating ExposedThing: " + err);
        }
      

Discovery

Discovery is basically 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.

      partial interface WOT {
        ThingDiscovery discover(optional ThingFilter filter);
      };
    

The discover() method

Starts the discovery process that will provide ThingDescription objects of Thing Descriptions that match an optional filter argument which can specify the discovery method and match a specific source URL, a query and a template object. Check the examples. The method MUST run the following steps:

  1. If invoking this method is not allowed for the current scripting context for security reasons, throw SecurityError and terminate these steps.
  2. If this method is not supported by the implementation, throw NotSupportedError and terminate these steps.
  3. Construct a ThingDiscovery object discovery using filter.
  4. Invoke the discovery.start() method.
  5. Return discovery.

The ThingDiscovery interface

Constructed given a filter and provides the events and methods controlling the discovery process.

        [Constructor(optional ThingFilter filter), SecureContext, Exposed=(Window,Worker)]
        interface ThingDiscovery {
          readonly attribute ThingFilter? filter;
          readonly attribute boolean active;
          readonly attribute boolean done;
          readonly attribute Error? error;
          void start();
          Promise<ThingDescription> next();
          void stop();
        };
      

The discovery results internal slot is an internal queue for temporarily storing the found ThingDescription objects until they are consumed by the application using the next() method.

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.

When ThingDiscovery is created, active and done are false, error is null. The start() sets active to true. The stop() method sets active to false, but done may be still false if there are ThingDescription objects in the discovery results not yet consumed with next(). During successive calls of next(), active may be true or false, but done is set to false by next() only when both active is false and discovery results is empty.

The start() method

Starts the discovery process. The method MUST run the following steps:

  1. If invoking this method is not allowed for the current scripting context for security reasons, set this.error to SecurityError and terminate these steps.
  2. If discovery is not supported by the implementation, set this.error to NotSupportedError and terminate these steps.
  3. If this.filter is defined,
    • Let filter denote this.filter.
    • If filter.query is defined, pass it as an opaque string to the underlying implementation to be matched against discovered items. The underlying implementation is responsible to parse it e.g. as a SPARQL or JSON query and match it against the Thing Descriptions found during the discovery process. If queries are not supported, set this.error to NotSupportedError and terminate these steps.
  4. Create the discovery results internal slot for storing discovered ThingDescription objects.
  5. Request the underlying platform to start the discovery process, with the following parameters:
    • If filter.method is not defined or the value is "any", use the widest discovery method supported by the underlying platform.
    • Otherwise if filter.method is "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).
    • Otherwise if filter.method is "directory", use the remote Thing Directory specified in filter.url.
    • Otherwise if filter.method is "multicast", use all the multicast discovery protocols supported by the underlying platform.
  6. When the underlying platform has started the discovery process, set the active property to true.
  7. Whenever a new Thing Description td is discovered by the underlying platform, run the following sub-steps:
    1. Fetch td.
    2. Let json be the result of running the parse a ThingDescription string steps on td. If that fails, set this.error to SyntaxError, discard td and continue the discovery process.
    3. If filter.query is defined, check if json is a match for the query. The matching algorithm is encapsulated by implementations. If that returns false, discard td and continue the discovery process.
    4. If filter.fragment is defined, for each property defined in it, check if that property exists in json.properties and has the same value. If this is false in any checks, discard td and continue the discovery process.
    5. Otherwise add td to the discovery results.
    6. At this point implementations MAY control the flow of the discovery process (depending on memory constraints, for instance temporarily stop discovery if the queue is getting too large, or resume discovery when the queue is emptied sufficiently).
  8. Whenever an error occurs during the discovery process,
    1. Set this.error to a new Error object error. Set error.name to 'DiscoveryError'.
    2. If there was an error code or message provided by the Protocol Bindings, set error.message to that value as string.
    3. If the error is irrecoverable and discovery has been stopped by the underlying platform, set this.active to false.
  9. When the underlying platform reports the discovery process has completed, set this.active to false.

The next() method

Provides the next discovered ThingDescription object. The method MUST run the following steps:

  1. Return a Promise promise and execute the next steps in parallel.
  2. If this.active is true, wait until the discovery results internal slot is not empty.
  3. If discovery results is empty and this.active is false, set this.done to true and reject promise.
  4. Remove the first ThingDescription object td from discovery results.
  5. Resolve promise with td and terminate these steps.

The stop() method

Stops or suppresses the discovery process. It might not be supported by all discovery methods and endpoints, however, any further discovery results or errors will be discarded and the discovery is marked inactive. The method MUST run the following steps:

  1. Request the underlying platform to stop the discovery process. If this returns an error, or if it is not possible, for instance when discovery is based on open ended multicast requests, the implementation SHOULD discard subsequent discovered items.
  2. Set this.active to false.

The DiscoveryMethod enumeration

        typedef DOMString DiscoveryMethod;
      

Represents the discovery type to be used:

  • "any" does not provide any restriction
  • "local" for discovering Things defined in the same device or connected to the device by wired or wireless means.
  • "directory" for discovery based on a service provided by a Thing Directory.
  • "multicast" for discovering Things in the device's network by using a supported multicast protocol.

The ThingFilter dictionary

Represents an object containing the constraints for discovering Things as key-value pairs.

        dictionary ThingFilter {
          (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 that of a Thing (otherwise).

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.

Discovery Examples

The following example finds ThingDescription 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.

        let discovery = WOT.discover({ method: "local" });
        do {
          let td = await discovery.next();
          console.log("Found Thing Description for " + td.name);
          let thing = WOT.consume(td);
          console.log("Thing name: " + thing.instance.name);
        } while (!discovery.done);
      

The next example finds ThingDescription objects of Things listed in a Thing Directory service. We set a timeout for safety.

        let discoveryFilter = {
          method: "directory",
          url: "http://directory.wotservice.org"
        };
        let discovery = WOT.discover(discoveryFilter);
        setTimeout( () => {
            discovery.stop();
            console.log("Discovery stopped after timeout.");
          },
          3000);
        do {
          let td = await discovery.next();
          console.log("Found Thing Description for " + td.name);
          let thing = WOT.consume(td);
          console.log("Thing name: " + thing.instance.name);
        } 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 = WOT.discover({ method: "multicast" });
        setTimeout( () => {
            discovery.stop();
            console.log("Stopped open-ended discovery");
          },
          10000);
        do {
          let td = await discovery.next();
          let thing = WOT.consume(td);
          console.log("Thing name: " + thing.instance.name);
        } while (!discovery.done);
      

Security and Privacy

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-CONSIDERATIONS]]. 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.

The WoT security risks and possible mitigations are concerning the following groups:

Scripting Runtime Security and Privacy Risks

This section is normative and contains specific risks relevant for the WoT Scripting Runtime.

Corrupted Input Security and Privacy Risk

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.

Mitigation:
Implementors of this API SHOULD perform validation on all script inputs. In addition to input validation, fuzzing should be used to verify that the input processing is done correctly. There are many tools and techniques in existence to do such validation. More details can be found in [[!WOT-SECURITY-TESTING]].

Physical Device Direct Access Security and Privacy Risk

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).

Mitigation:
The WoT Scripting Runtime SHOULD avoid directly exposing the native device interfaces to the script developers. Instead, a WoT Scripting Runtime should provide a hardware abstraction layer for accessing the native device interfaces. Such hardware abstraction layer should refuse to execute commands that might put the device (or environment) to an unsafe state. Additionally, in order to reduce the damage to a physical WoT device in cases a script gets compromised, it is important to minimize the number of interfaces that are exposed or accessible to a particular script based on its functionality.

Provisioning and Update Security Risk

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.

Mitigation:
Post-manufacturing provisioning or update of scripts, WoT Scripting Runtime or any related data should be done in a secure fashion. A set of recommendations for secure update and post-manufacturing provisioning can be found in [[!WOT-SECURITY-CONSIDERATIONS]].

Security Credentials Storage Security and Privacy Risk

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.

Mitigation:
The WoT Scripting Runtime should securely store the provisioned security credentials, guaranteeing their integrity and confidentiality. In case there are more than one tenant on a single WoT-enabled device, a WoT Scripting Runtime should guarantee isolation of each tenant provisioned security credentials. Additionally, in order to minimize a risk that provisioned security credentials get compromised, the WoT Scripting Runtime should not expose any API for scripts to query the provisioned security credentials.

Script Security and Privacy Risks

This section describes specific risks relevant for script developers.

Corrupted Script Input Security and Privacy Risk

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.

Mitigation:
Script developers should perform validation on all application defined script inputs. In addition to input validation, fuzzing could be used to verify that the input processing is done correctly. There are many tools and techniques in existence to do such validation. More details can be found in [[!WOT-SECURITY-TESTING]].

Denial Of Service Security Risk

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.

Mitigation:
Scripts should avoid heavy functional processing without prior successful authentication of requestor. The set of recommended authentication mechanisms can be found in [[!WOT-SECURITY-BEST-PRACTICES]].

Stale TD Security Risk

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.

Mitigation:
Scripts should use this API to subscribe for notifications on TD changes and do not rely on TD values to remain persistent.

While stale TDs can present a potential problem for WoT network operation, it might not be a security risk.

Terminology and conventions

The generic WoT terminology is defined in [[!WOT-ARCHITECTURE]]: Thing, Thing Description (in short TD), Web of Things (in short WoT), WoT Interface, Protocol Bindings, WoT Runtime, Consuming a Thing Description, Thing Directory, WoT Interactions, Property, Action, Event etc.

JSON-LD is defined in [[!JSON-LD]] as a JSON document that is augmented with support for Linked Data.

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]].

ASCII decode, ASCII lowercase, string, byte, byte sequence, set, exists, list, for each, continue, is empty, is not empty, append, contains, parse JSON from bytes and serialize JSON to bytes, are defined in [[!INFRA]].

The terms throw, creating, DOMString, Dictionary, ArrayBuffer, BufferSource, any, not present, DOMException, AbortError, SyntaxError, NotSupportedError, NetworkError, TypeError, NotReadableError, TimeoutError, NoModificationAllowedError, SecurityError, are defined in [[!WEBIDL]].

Promise, JSON, JSON.stringify, JSON.parse and internal slots are defined in [[!ECMASCRIPT]].

The terms browsing context, top-level browsing context, global object, current settings object, Document, document base URL, Window, WindowProxy, origin, serialized origin, executing algorithms in parallel, queue a task, task source, iframe, relevant settings object, active document, environment settings object, EventHandler, are defined in [[!HTML5]] and are used in the context of browser implementations.

A browsing context refers to the environment in which Document objects are presented to the user. A given browsing context has a single WindowProxy object, but it can have many Document objects, with their associated Window objects. The script execution context which invokes this API is associated with the browsing context, which can be a web app, a web page, or an iframe.

The term secure context is defined in [[!WEBAPPSEC]].

fire an event, AbortSignal, aborted flag, and add the following abort steps are defined in [[!DOM]].

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.

This document defines conformance criteria that apply to a single product: the UA (user agent) that implements the interfaces it contains.

This specification can be used for implementing the WoT Scripting API in multiple programming languages. The interface definitions are specified in [[!WEBIDL]].

The user agent (UA) may be implemented in the browser, or in a separate runtime environment, such as Node.js or 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]].

This document serves a general description of the WoT Scripting API. Language and runtime specific issues are discussed in separate extensions of this document.

Changes

The following is a list of major changes to the document. Major versions of this specification are the following:

For a complete list of changes, see the github change log. You can also view the recently closed issues.

Open issues

The following problems are being discussed and need most attention:

Full Web IDL

Acknowledgements

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.