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

This specification describes a programming interface representing the WoT Interface that allows scripts run on a Thing to discover and consume (retrieve) other Thing Descriptions and to expose Things characterized by WoT Interactions specified by a script.

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 like Thing Directory.

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 modeled: as being "consumed" and "exposed".

By consuming a TD, a client Thing creates a runtime resource model that allows accessing the Properties, Actions and Events exposed by the server Thing exposed on a remote device.

Exposing a Thing requires defining a Thing Description (TD) and instantiating a software stack to serve requests for accessing the exposed Properties, Actions and Events. 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:

Discovery

Consuming a Thing

Exposing a Thing

The WoT object

The WoT object is the API entry point and it is exposed by an implementation of the WoT Runtime. The WoT object does not expose properties, only methods for discovering, consuming and exposing a Thing.

Browser implementations SHOULD use a namespace object such as navigator.wot. Node.js-like runtimes MAY provide the API object through the require() or import mechanism.

      // [SecureContext]
      // [NamespaceObject]
      interface WoT {
        Observable discover(optional ThingFilter filter);
        Promise<ThingDescription> fetch(USVString url);
        ConsumedThing consume(ThingDescription td);
        ExposedThing produce(ThingModel model);
        Promise<void> register(USVString directory, ExposedThing thing);
        Promise<void> unregister(USVString directory, ExposedThing thing);
      };
      typedef object ThingFragment;
      typedef object PropertyFragment;
      typedef object ActionFragment;
      typedef object EventFragment;
      typedef object DataSchema;
      typedef object SecurityScheme;
      typedef object Link;
      typedef object Form;
      typedef USVString ThingDescription;
      typedef (ThingFragment or ThingDescription) ThingModel;
    

The algorithms for the WoT methods will be specified later, including error handling and security considerations.

The ThingModel type represents either a ThingFragment, or a ThingDescription.

The discover() method

Starts the discovery process that will provide ThingDescriptions that match the optional argument filter of type ThingFilter. Returns an [Observable](https://github.com/tc39/proposal-observable) object that can be subscribed to and unsubscribed from. The handler function provided to the Observable during subscription will receive an argument of type USVString representing a ThingDescription.

The DiscoveryMethod enumeration

          typedef DOMString DiscoveryMethod;
        

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

The ThingFilter dictionary that represents the constraints for discovering Things as key-value pairs.

          dictionary ThingFilter {
            (DiscoveryMethod or DOMString) method = "any";
            USVString? url;
            USVString? query;
            ThingFragment? 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 a Thing Directory (if method is "directory") or 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 ThingFragment dictionary used for matching property by property against discovered Things.

The discover(filter) method MUST run the following steps:

  1. If invoking discover() is not allowed for the current scripting context for security reasons, throw SecurityError and terminate these steps.
  2. Return an Observable obs and execute the next steps in parallel.
  3. If obs.subscribe(handler, errorHandler, complete) is called, execute the following sub-steps:
    1. If the first argument handler is not defined or it is not a function, throw TypeError and terminate the algorithm. Otherwise configure handler to be invoked when a discovery hit happens.
    2. If the second argument errorHandler is defined, but it is not a function, throw TypeError and terminate these steps. Otherwise if defined, save it to be invoked in error conditions.
    3. If the third argument onComplete is defined, but it is not a function, throw TypeError and terminate these steps. Otherwise if defined, save it to be invoked when the discovery process finished for other reasons than having been canceled.
    4. 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, implementations SHOULD throw a NotSupported error and terminate these steps.
    5. If filter.fragment is defined, and if it contains other properties than the ones defined in ThingFragment, throw TypeError and terminate these steps. Otherwise save the object for matching the discovered items against it.
    6. 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.
  4. Whenever a new item td is discovered by the underlying platform, run the following sub-steps:
    1. If filter.query is defined, check if td is a match for the query. The matching algorithm is encapsulated by implementations. If that returns false, discard td and continue the discovery process.
    2. If filter.fragment is defined, for each property defined in it, check if that property exists in td and has the same value. If this is false in any checks, discard td and continue the discovery process.
    3. Otherwise if td has not been discarded in the previous steps, invoke the handler function with td as parameter.
  5. Whenever an error occurs during the discovery process, and if errorHandler is defined, invoke it with an argument of type Error whose message property is set to UnknownError unless there was an error code provided by the Protocol Bindings, in which case set it to that value.
  6. When the discovery process is finished, and if onComplete is defined, invoke it run the cancel discovery steps.
  7. When the obs.unsubscribe() method is called, run the following cancel discovery 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 obs.closed to false.

The fetch() method

Accepts an url argument of type USVString that represents a URL (e.g. "file://..." or "https://...") and returns a Promise that resolves with a ThingDescription (a serialized JSON-LD document of type USVString).

The fetch(url) method MUST run the following steps:

  1. Return a Promise promise and execute the next steps in parallel.
  2. If invoking fetch() is not allowed for the current scripting context for security reasons, reject promise with SecurityError and terminate these steps.
  3. If the argument url is not a URL, reject promise with TypeError and terminate these steps.
  4. Make a request to fetch the content of url as described by the Protocol Bindings and wait for the reply. Implementations encapsulate the fetching process and the accepted media types (such as application/td+json), as far as a valid Thing Description can be obtained as defined in [[!WOT-TD]]. Let td be the Thing Description string-serialized from the returned content, as specified in the Thing Description serialization.
  5. 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.
  6. Otherwise resolve promise with td and terminate these steps.

The consume() method

Accepts an td argument of type ThingDescription and returns a ConsumedThing object instantiated based on parsing that description.

The consume(td) method must run the following steps:

  1. If the argument td is not a string, throw a TypeError and terminate these steps.
  2. Let stub be the result of running the TD parsing algorithm with td as argument. If that throws an error, re-throw the error and terminate these steps.
  3. If stub does not have an own property that is defined in ThingFragment with a default value, add that property and value to stub.
  4. Create a ConsumedThing object thing initialized from stub that implements Observable.
  5. Add the read() and write() methods to the ThingProperty elements so that they make requests to access the remote Things and wait for the reply, as defined by the Protocol Bindings. Also, all ThingProperty elements SHOULD implement Observable, i.e. define a subscribe() method that should make request to observe the given Properties as defined by the Protocol Bindings.
  6. Add the invoke() methods to the ThingAction elements so that they make requests to the remote Thing to invoke its actions, as defined by the Protocol Bindings.
  7. Add the subscribe() method to all ThingEvent elements so that they make requests to subscribe to the events defined by the remote Thing, as defined by the Protocol Bindings.
  8. Return thing.

The produce() method

Accepts a model argument of type ThingModel and returns an ExposedThing object.

The produce(model) method MUST run the following steps:

  1. If invoking produce() is not allowed for the current scripting context for security reasons, throw SecurityError and terminate these steps.
  2. If the argument model is a string, then run the TD parsing algorithm with model passed as parameter. If it throws an error, re-throw that error and terminate this algorithm. Otherwise let model be the returned value.
  3. If model is not an object, throw TypeError and terminate these steps.
  4. If model does not have an own property that is defined in ThingFragment with a default value, add that property and value to model.
  5. Create an ExposedThing object thing initialized from model.
  6. For each property of ExposedThing defined in ThingFragment, initialize the property based on the provided initial or default values provided to the local WoT Runtime implementation, for instance initialize:
    1. the id property to be the final unique identifier of the Thing,
    2. the security object of type SecurityScheme to represent the actual security scheme and its properties as set up by the implementation,
    3. the properties property to be an object with all properties being ThingProperty objects in which the read() and write() methods are provided to define local methods to get and set the Property values,
    4. the actions property to be an object with all properties being ThingAction objects in which the invoke() method is provided to define a local method to run the defined Actions,
    5. the events property to be an object with all properties being ExposedEvent objects in which the emit() method is provided to define a local way to trigger sending notifications to all subscribed clients,
    6. and initialize the other properties as initialized from model.
    7. Return thing.

    The TD parsing algorithm takes a string td as argument and runs the following steps:

    1. Parse td according to the WoT Thing Description in order to produce a JSON object json. Update thing with the properties and values defined in json.
    2. If there was an error during the parsing, throw that error and terminate these steps.
    3. Otherwise return json.

The register() method

Takes two mandatory arguments:

Generate the Thing Description as td, given the Properties, Actions and Events defined for this ExposedThing object. Then make a request to register td to the given WoT Thing Directory.

The unregister() method

Takes two mandatory arguments:

Makes a request to unregister the thing from the given WoT Thing Directory.

Examples

        let discoveryFilter = {
          method: "directory",
          url: "http://directory.wotservice.org"
        };
        let subscription = wot.discover(discoveryFilter).subscribe(
          td => {
            console.log("Found Thing " + td.name);
            // fetch the TD and create a ConsumedThing
            let thing = wot.consume(td);
          },
          error => { console.log("Discovery finished because an error: " + error.message); },
          () => { console.log("Discovery finished successfully");}
        );
        setTimeout( () => {
            subscription.unsubscribe();
            console.log("Discovery timeout");
          },
          5000);
      
        let subscription = wot.discover({ method: "local" }).subscribe(
          td => { console.log("Found local Thing " + td.name); },
          error => { console.log("Discovery error: " + error.message); },
          () => { console.log("Discovery finished successfully");}
        );
      
        let subscription = wot.discover({ method: "local" }).subscribe({
          td => { console.log("Found local Thing " + td.name); },
          error: err => { console.log("Discovery error: " + err.message); },
          complete: () => { console.log("Discovery finished successfully");}
        });
      

The ConsumedThing interface

Represents an object that extends a ThingFragment with methods for client interactions (send request for reading and writing Properties), invoke Actions, subscribe and unsubscribe for Property changes and Events.

      interface ConsumedThing: ThingFragment {
        readonly attribute DOMString id;
        readonly attribute DOMString name;
        readonly attribute DOMString? base;
        readonly attribute PropertyMap properties;
        readonly attribute ActionMap actions;
        readonly attribute EventMap events;
        // getter for ThingFragment properties
        getter any(DOMString name);
      };
      [NoInterfaceObject]
      interface PropertyMap {
        readonly maplike<DOMString, ThingProperty>;
      };
      [NoInterfaceObject]
      interface ActionMap {
        readonly maplike<DOMString, ThingAction>;
      };
      [NoInterfaceObject]
      interface EventMap {
        readonly maplike<DOMString, ThingEvent>;
      };
      ConsumedThing includes Observable;  // for TD changes
    

The id attribute represents the unique identifier of the Thing instance, typically a URI, IRI, or URN as USVString.

The name attribute represents the name of the Thing as DOMString.

The base attribute represents the base URI that is valid for all defined local interaction resources.

The properties attribute represents a dictionary of ThingProperty items. The PropertyMap interface represents a maplike dictionary where all values are ThingProperty objects. The read() and write() methods make a request to access the Properties on the remote Thing represented by this ConsumedThing proxy object.

The actions attribute represents a dictionary of ThingAction items. The ActionMap interface represents a maplike dictionary where all values are ThingAction objects. The invoke() method represents a request to invoke the Action on the remote Thing.

The events attribute represents a dictionary of ThingEvent items. The EventMap interface represents a maplike dictionary where all values are ThingEvent objects. Subscribing to the events involves setting up an observation (subscription) mechanism on the remote object.

Examples

Below a ConsumedThing interface example is given.

        try {
          let subscription = wot.discover({ method: "local" }).subscribe(
            td => {
              let thing = wot.consume(td);
              console.log("Thing " + thing.name + " has been consumed.");
              let subscription = thing["temperature"].subscribe(function(value) {
                  console.log("Temperature: " + value);
                });
              thing.actions["startMeasurement"].invoke({ units: "Celsius" })
                .then(() => { console.log("Temperature measurement started."); })
                .catch(e => {
                   console.log("Error starting measurement.");
                   subscription.unsubscribe();
                 })
            },
            error => { console.log("Discovery error: " + error.message); },
            () => { console.log("Discovery finished successfully");}
          );
        } catch(error) {
          console.log("Error: " + error.message);
        };
      

The ExposedThing interface

The ExposedThing interface is the server API that allows defining request handlers, properties, Actions, and Events to a Thing. It also implements the Observable interface. An ExposedThing is created by the produce() method.

      interface ExposedThing: ThingFragment {
        readonly attribute PropertyMap properties;
        readonly attribute ActionMap actions;
        readonly attribute ExposedEvents events;
        // getter for ThingFragment properties
        getter any(DOMString name);
        // setter for ThingFragment properties
        setter void(DOMString name, any value);
        // methods to expose and destroy the Thing
        Promise<void> expose();
        Promise<void> destroy();
        // define Properties
        ExposedThing addProperty(DOMString name, PropertyFragment property, optional any initValue);
        ExposedThing setPropertyReadHandler(DOMString name, PropertyReadHandler readHandler);
        ExposedThing setPropertyWriteHandler(DOMString name, PropertyWriteHandler writeHandler);
        ExposedThing removeProperty(DOMString name);
        // define Actions
        ExposedThing addAction(DOMString name, ActionFragment init, ActionHandler action);
        ExposedThing removeAction(DOMString name);
        ExposedThing setActionHandler(DOMString name, ActionHandler action);
        // define Events
        ExposedThing addEvent(DOMString name, EventFragment event);
        ExposedThing removeEvent(DOMString name);
      };
      [NoInterfaceObject]
      interface ExposedEvents {
        maplike<DOMString, ExposedEvent>;
      };
      callback PropertyReadHandler = Promise<any>();
      callback PropertyWriteHandler = Promise<void>(any value);
      callback ActionHandler = Promise<any>(any parameters);
    

The properties attribute represents a dictionary of ThingProperty items in which the read() and write() methods define local methods that access the physical representations of the Properties.

The actions attribute represents a dictionary of ThingAction items in which the invoke() method represents a local method to invoke the Action.

The events attribute represents a dictionary of ExposedEvent items that add the emit() method to the ThingEvent definition. The ExposedEvents interface represents a maplike dictionary where all values are ExposedEvent objects.

The expose() method

Start serving external requests for the Thing, so that WoT Interactions using Properties, Actions and Events will be possible.

The expose() method MUST run the following steps:

  1. Return a Promise promise and execute the next steps in parallel.
  2. If invoking expose() 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 attach protocol handlers and 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 destroy() method MUST run the following steps:

  1. Return a Promise promise and execute the next steps in parallel.
  2. If invoking destroy() 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.

The addProperty() method

Adds a Property with name defined by the name argument, the data schema provided by the property argument of type PropertyFragment, and optionally an initial value provided in the argument initValue whose type should match the one defined in the type property according to the value-matching algorithm. If initValue is not provided, it SHOULD be initialized as undefined. Implementations SHOULD update the Thing Description. Throws on error. Returns a reference to the same object for supporting chaining.

The removeProperty() method

Removes the Property specified by the name argument and updates the Thing Description. Throws on error. Returns a reference to the same object for supporting chaining.

The addAction() method

Adds to the actions property of a Thing object an Action with name defined by the name argument, defines input and output data format by the init argument of type ActionFragment, and adds the function provided in the action argument as a handler, then updates the Thing Description. Throws on error. Returns a reference to the same object for supporting chaining.

The provided action callback function will implement invoking an Action and SHOULD be called by implementations when a request for invoking the Action is received from the underlying platform. The callback will receive a parameters dictionary argument according to the definition in the init.input argument and will return a value of type defined by the init.output argument according to the value-matching algorithm.

There SHOULD be exactly one handler for any given Action. If no handler is initialized for any given Action, implementations SHOULD throw a TypeError.

The removeAction() method

Removes the Action specified by the name argument and updates the Thing Description. Throws on error. Returns a reference to the same object for supporting chaining.

The addEvent() method

Adds an event with name defined by the name argument and qualifiers and initialization value provided by the event argument of type EventFragmentto the Thing object and updates the Thing Description. Throws on error. Returns a reference to the same object for supporting chaining.

The removeEvent() method

Removes the event specified by the name argument and updates the Thing Description. Returns a reference to the same object for supporting chaining.

The PropertyReadHandler callback

A function that is called when an external request for reading a Property is received. It should return a Promise and resolves it with the value of the Property matching the name argument to the setPropertyReadHandler function, or rejects with an error if the property is not found or the value cannot be retrieved.

The PropertyWriteHandler callback

A function that is called when an external request for writing a Property is received. It is given the requested new value as argument and should return 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 this function is invoked by implementations before the property is updated and it actually defines what to do when a write request is received. The code in this callback function can invoke the read() method to find out the old value of the property, if needed. Therefore the old value is not provided to this function.

The ActionHandler callback

A function called with a parameters dictionary argument assembled by the WoT runtime 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 or ongoing (may also resolve with a control object such as an Observable for actions that need progress notifications or that can be canceled).

The setPropertyReadHandler() method

Takes name as string argument and readHandler as argument of type PropertyReadHandler. Sets the handler function for reading the specified Property matched by name. Throws on error. Returns a reference to the same object for supporting chaining.

The readHandler callback function will implement reading a Property and SHOULD be called by implementations when a request for reading a Property is received from the underlying platform.

There SHOULD be at most one handler for any given Property and newly added handlers replace the old handlers. If no handler is initialized for any given Property, implementations SHOULD implement a default property read handler.

When an external request for reading Property propertyName is received, the runtime SHOULD execute the following steps:

  1. Return a Promise promise and execute the next steps in parallel.
  2. If a Property with propertyName does not exist, reject promise with a ReferenceError and terminate these steps.
  3. Otherwise, if no read handler has been defined for propertyName, resolve promise with the value of the Property named propertyName provided by the runtime implementation and terminate these steps.
  4. Otherwise, invoke the read handler associated with propertyName. If it rejects, then reject promise with the same error, and resolve promise with the same value.

The setPropertyWriteHandler() method

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

There SHOULD be at most one write handler for any given Property and newly added handlers replace the old handlers. If no write handler is initialized for any given Property, implementations SHOULD implement default property update and notifying observers on change.

When an external request for writing a Property propertyName with a new value value is received, the runtime SHOULD execute the following steps:

  1. Return a Promise promise and execute the next steps in parallel.
  2. If a Property with propertyName does not exist, reject promise with a ReferenceError and terminate these steps.
  3. Otherwise, if no write handler has been defined for propertyName, the runtime implementation SHOULD update the Property value with value, resolve promise and terminate these steps.
  4. Otherwise, invoke the write handler associated with propertyName providing value as argument. If it rejects, then reject promise with the same error, and resolve promise with the same value.

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 the same 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 SHOULD be at most one handler for any given Action and newly added handlers replace the old handlers.

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

  1. Return a Promise promise and execute the next steps in parallel.
  2. If an Action identified by name does not exist, reject promise with a ReferenceError and terminate these steps.
  3. Otherwise, if no action handler has been defined for name, reject promise with a ReferenceError and terminate these steps.
  4. Otherwise, invoke the Action handler associated with name. If it rejects with error, then reject promise with the same error, otherwise if it resolves with value, then resolve promise with the same value.

Examples

Below some ExposedThing interface examples are given.

        try {
          var temperatureValueDefinition = {
            type: "number",
            minimum: -50,
            maximum: 10000
          };
          var temperaturePropertyDefinition = temperatureValueDefinition;
          // add the 'forms' property
          temperaturePropertyDefinition.forms = [ ... ];
          var thing = WoT.produce({
            name: "tempSensor",
            properties: {
              temperature: temperaturePropertyDefinition
            },
            actions: {
              reset: {
                description: "Reset the temperature sensor",
                input: {
                  temperature: temperatureValueDefinition
                },
                output: null,
                forms: []
              },
            },
            events: {
              onchange: temperatureValueDefinition
            },
            links: []
          });
          await thing.expose();
          await wot.register("https://mydirectory.org", thing);
          // define Thing business logic
          setInterval( async () => {
            let mock = Math.random()*100;
            let old = await thing["temperature"].read();
            if (old < mock) {
              await thing["temperature"].write(mock);
              thing.emitEvent("onchange", mock);
            }
          }, 1000);
        } catch (err) {
           console.log("Error creating ExposedThing: " + err);
        }
      
        try {
          var 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
                }
              }
          };
          var statusPropertyDefinition = statusValueDefinition;
          // add the 'forms' property
          statusPropertyDefinition["forms"] = [];
          var thing = WoT.produce({
            name: "mySensor",
            properties: {
              brightness: {
                type: "number",
                minimum: 0.0,
                maximum: 100.0,
                required: true,
              },
              status: statusPropertyDefinition
            },
            actions: {
              status: {
                description: "Get status object",
                input: null,
                output: {
                  status : statusValueDefinition;
                },
                forms: []
              },
            },
            events: {
              onstatuschange: statusValueDefinition;
            },
            links: []
          });
          thing.expose().then(() => {
              thing.register();
          });
        } catch (err) {
           console.log("Error creating ExposedThing: " + err);
        }
      
        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 thingDescription contains error
          let thing = WoT.produce(thingDescription);
          // Interactions were added from TD
          // WoT adds generic handler for reading any property
          // define a specific handler for one property
          let name = "examplePropertyName";
          thing.setPropertyReadHandler(name, () => {
            console.log("Handling read request for " + name);
            return new Promise((resolve, reject) => {
                let examplePropertyValue = 5;
                resolve(examplePropertyValue);
              },
              e => {
                console.log("Error");
              });
          });
          thing.expose();
        } catch(err) {
           console.log("Error creating ExposedThing: " + err);
        }
      
        // fetch an external TD, e.g., to set up a proxy for that Thing
        WoT.fetch("http://myservice.org/mySensor/description").then(td => {
          // WoT.produce() ignores instance-specific metadata (security, form)
          let thing = WoT.produce(td);
          // Interactions were added from TD
          // add server functionality
          // ...
        });
      

Data types and structures

The [[!WOT-TD]] specification defines the WoT information model, i.e. the data types and data structures used in WoT Interactions. In this API these definitions translate to dictionary objects that are extended with methods by the interfaces defined in this specification.

In order to avoid duplication of definitions, references to these data types and structures is defined in this section, but for their full description please refer to the Thing Description specification.

The DataSchema dictionary and its subclasses

Value types basically represent types that may be used in JSON object definitions and are used in ThingFragment to define Properties, Events and Action parameters. Value types are represented as dictionary objects whose properties and possible sub-classes are defined in the DataSchema section of [[!WOT-TD]].

One property of all DataSchema dictionary is the type property whose value is from a set of enumerated strings defined in the DataSchema section of [[!WOT-TD]] and is referred as DataType in this specification.

Based on type, the following sub-classes of DataSchema are defined in [[!WOT-TD]]: BooleanSchema, NumberSchema, IntegerSchema, StringSchema, ObjectSchema, ArraySchema.

The SecurityScheme dictionary and its subclasses

Security metadata is represented as dictionary objects whose properties and sub-classes are defined in the SecurityScheme section of [[!WOT-TD]].

One property of the SecurityScheme dictionary is the scheme property whose value is from a set of enumerated strings defined in the SecurityScheme section of [[!WOT-TD]]. Based on type, multiple subclasses of SecurityScheme are defined.

The Link dictionary

Represents a Web Link with properties defined in the Link section of [[!WOT-TD]].

The Form dictionary

Represents metadata describing service details, with properties defined in the Form section of [[!WOT-TD]].

The InteractionFragment dictionary

Represents the common properties of WoT Interactions, one of Property, Action or Event, as defined in the InteractionPattern section of [[!WOT-TD]]. Its subclasses are referred as PropertyFragment, ActionFragment and EventFragment.

The PropertyFragment dictionary

Represents the Property interaction data that initializes a ThingProperty object. Its properties are defined in the Property and InteractionPattern sections of [[!WOT-TD]].

The ActionFragment dictionary

Represents the Action interaction data that initializes a ThingAction object. Its properties are defined in the Action and InteractionPattern sections of [[!WOT-TD]].

The EventFragment dictionary

Represents the Event interaction data that initializes a ThingEvent object. Its properties are defined in the Event section of [[!WOT-TD]].

The ThingFragment dictionary

The ThingFragment dictionary is defined as Thing in [[!WOT-TD]]. It is a dictionary that contains properties representing semantic metadata and interactions (Properties, Actions and Events). It is used for initializing an internal representation of a Thing Description and its properties may be used in ThingFilter.

The ThingDescription type

Serialized representation of the Thing Description (a JSON-LD document).

In this version of the API, Thing Descriptions are represented as an opaque USVString that can be transmitted between devices.

Interfaces for WoT Interactions

The data types and structures imported from [[!WOT-TD]] are extended by this specification in order to provide the interfaces for WoT Interactions.

Every Thing describes its metadata as defined in ThingFragment, and basic interactions defined as Properties, Actions and Events. The following interfaces are used for representing these interactions.

The Interaction interface

The Interaction interface is an abstract class to represent Thing interactions: Properties, Actions and Events.

The InteractionFragment dictionary holds the common properties of PropertyFragment, ActionFragment and EventFragment dictionaries used for initializing ThingProperty, ThingAction and ThingEvent objects in a ThingFragment dictionary used for creating an ExposedThing object.

        interface Interaction {
          readonly attribute (Form or FrozenArray<Form>) forms;
        };
        Interaction includes InteractionFragment;
      

The forms read-only property represents the protocol bindings initialization data and is initialized by the WoT Runtime.

The ThingProperty interface

The ThingProperty interface is used in ConsumedThing and ExposedThing objects to represent Thing Property interactions.

The PropertyFragment dictionary is used for initializing Property objects in a ThingFragment dictionary used for creating an ExposedThing object. It MUST implement one of the DataSchema dictionaries.

        interface ThingProperty: Interaction {
          // getter for PropertyFragment properties
          getter any(DOMString name);
          // get and set interface for the Property
          Promise<any> read();
          Promise<void> write(any value);
        };
        ThingProperty includes PropertyFragment;
        ThingProperty includes Observable;
      

The ThingProperty interface contains all the properties defined on PropertyFragment as read-only properties.

The type read-only property represents the type definition for the Property as a DataSchema dictionary object.

The writable read-only property tells whether the Property value can be updated. If it is false, then the set(value) method SHOULD always reject.

The observable read-only property tells whether the Property supports subscribing to value change notifications. If it is false, then the subscribe() method SHOULD always fail.

The constant read-only property - defined in DataSchema - tells whether the Property value is a constant. If true, the set() and subscribe() methods SHOULD always fail.

The required read-only property - defined in DataSchema - tells whether the Property should be always present on the ExposedThing object.

The read() method will fetch the value of the Property. Returns a Promise that resolves with the value, or rejects with an error.

The write() method will attempt to set the value of the Propertyspecified in the value argument whose type SHOULD match the one specified by the type property. Returns a Promise that resolves on success, or rejects on an error.

The ThingAction interface

        interface ThingAction: Interaction {
          Promise<any> invoke(optional any inputValue);
        };
        ThingAction includes ActionFragment;
      

The invoke() method when invoked, starts the Action interaction with the input value provided by the inputValue argument. If inputValue is null, the action does not take any arguments and rejects if any arguments are provided. If the value is undefined, the action will ignore any arguments provided. Otherwise the type of inputValue SHOULD match the DataSchema definition in the input property. Returns a Promise that will reject with an error or will resolve with a value of type defined by the output property.

The ThingEvent interface

        interface ThingEvent: Interaction {
        };
        ThingEvent includes EventFragment;
        ThingEvent includes ThingProperty;
      

Since ThingEvent implements Observable through the ThingProperty interface, event subscription is done by invoking the subscribe() method on the event object that returns a cancelable Subscription.

The ExposedEvent interface

        interface ExposedEvent: ThingEvent {
          void emit(any payload);
        };
      

The emit() method

Emits an event that carries data specified by the payload argument.

The value-matching algorithm

The value-matching algorithm is applied to a value input in relation to a valueType property of type DataSchema, for instance the value and type properties of a PropertyFragment object, or the inputValue parameter to the invoke() method of a ThingAction object in relation to the same object. It executes the following steps:

  1. If valueType.type is not defined, or does not fully match a string enumerated in DataType, return false.
  2. Otherwise, if valueType.type is "null": if value is null, return true, otherwise return false.
  3. Otherwise, if valueType.type is "boolean": if value is either true or false, then return true, otherwise return false.
  4. Otherwise, if valueType.type is "integer": if value is not an integer type defined by the underlying platform (such as long or long long), then return false, otherwise execute the following sub-steps:
    1. If valueType.minimum is defined and value is not greater or equal than that value, return false.
    2. If valueType.maximum is defined and value is not less or equal than that value, return false.
    3. Return true.
  5. Otherwise, if valueType.type is "number", if value is not an integer or floating point type defined by the underlying platform (such as long or long long or double), then return false, otherwise otherwise execute the following sub-steps:
    1. If valueType.minimum is defined and value is not greater or equal than that value, return false.
    2. If valueType.maximum is defined and value is not less or equal than that value, return false.
    3. Return true.
  6. Otherwise, if valueType.type is "string": if value is not a string type defined by the underlying platform, then return false, otherwise return true. In this case the algorithm expects a third parameter valueType.enum and runs the following sub-steps:
    • If valueType.enum is an array of strings, then if value fully matches one of the strings defined in the array, return true.
    • Otherwise, return false.
  7. Otherwise, if valueType.type is "array", execute the following sub-steps:
    1. If value is not an array, return false.
    2. If valueType.minItems is defined, and value does not contain at least valueType.minItems elements, return false.
    3. If valueType.maxItems is defined, and value contains more than valueType.maxItems elements, return false.
    4. Otherwise, if valueType.items is undefined, return false.
    5. Otherwise, if valueType.items is null, return true (i.e. any type is accepted as array element, including heterogenous arrays).
    6. Otherwise, for each element of the array value run the value-matching algorithm against the valueType.items object. If any of these runs returns false, then return false.
    7. Otherwise, return true.
  8. Otherwise, if type is "object", execute the following sub-steps:
    1. If value is not an Object, return false.
    2. If valueType.properties is not defined, return false.
    3. If valueType.properties is null, return true (i.e. accept any object value).
    4. For each string in the valueType.required array, if it does not match a property name in the value.properties object or in the value object, then return false.
    5. For each property with name propName and value propDataSchema found in valueType.properties, run the following sub-steps:
      1. If the result of applying the value-matching algorithm on the value value[propName] and propDataSchema is false, then return false.
    6. Return true.

Observables

Observables are proposed to be included in ECMAScript and are used for handling pushed data associated with various possible sources, for instance events, timers, streams, etc. A minimal required implementation is described here.

This section is informal and contains rather laconic information for implementations on what to support for interoperability.

      interface Observable {
        Subscription subscribe(EventHandler handler,
                               optional ErrorHandler errorHandler,
                               optional OnComplete onComplete);
      };
      interface Subscription {
        void unsubscribe();
        readonly attribute boolean closed;
      };
      callback EventHandler = void (any value);
      callback ErrorHandler = void (Error error);
      callback OnComplete = void ();
    

The following callbacks can be provided when subscribing to an Observable:

The Subscription interface

Contains the closed property of type boolean that tells if the subscription is closed or active.

Also, contains the unsubscribe() method that cancels the subscription, i.e. makes a request to the underlying platform to stop receiving data from the source, and sets the closed property to false.

The Observable interface

The Observable interface enabled subscribing to pushed data notifications by the subscribe() method:

Security and Privacy

In general the security measures taken to protect a WoT system will depend on the threats and attackers that system may face and the value of the assets needs to protect. 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.

When designing new devices and services for use with the WoT, we have documented a suggested set of best practices in [[!WOT-SECURITY-BEST-PRACTICES]] to improve security. This best-practices document may be updated as security measures evolve. Following these practices does not guarantee security, but it at least will help to avoid common known vulnerabilities and pitfalls.

Scripts Security and Privacy Risks

This section contains specific risks relevant for script developers.

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:
Developers 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]].
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, id, which may not be an immutable one and updated periodically.

Mitigation:
Scripts should use the provided script 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.

WoT Runtime Security and Privacy Risks

This section contains specific risks relevant for the WoT Runtime itself. While the following risks are strictly speaking out of the scope for the WoT Scripting API, a script needs a secure runtime to execute and therefore we present these risks and recommended mitigations here.

Cross-Script Security and Privacy Risk

In basic WoT setups, all scripts running inside the WoT runtime are considered trusted, and therefore there is no strong need to perform strict isolation between each running script instance. However, depending on device capabilities and deployment use case scenario risk level it might be desirable to do so. For example, if one script handles sensitive privacy-related data and well-audited, it might be desirable to separate it from the rest of the script instances to minimize the risk of data exposure in case some other script inside WoT gets compromised during runtime. Another example is mutual co-existence of different tenants on a single WoT device. In this case each WoT runtime instance will be hosting a different tenant, and isolation between them is required.

Mitigation:
The WoT runtime should perform isolation of script instances and their data in cases when scripts handle privacy-related or other critical security data. Similarly, the WoT runtime should perform isolation of WoT runtime instances and their data if a WoT device has more than one tenant. Such isolation can be performed within the WoT Runtime using platform security mechanisms available on the device. For more information see Sections "WoT Servient Single-Tenant" and "WoT Servient Multi-Tenant" of [[!WOT-SECURITY-CONSIDERATIONS]].
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 Runtime should avoid directly exposing the native device interfaces to the script developers. Instead a WoT 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 runtime supports post-manufacturing provisioning or updates of scripts, WoT 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 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 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 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 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 runtime should not expose any API for scripts to query the provisioned security credentials.

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 and URL path are defined in [[!URL]].

The following terms are defined in [[!HTML5]] and are used in the context of browser implementations: browsing context, top-level browsing context, global object, incumbent settings object, Document, document base URL, Window, WindowProxy, origin, ASCII serialized origin, executing algorithms in parallel, queue a task, task source, iframe, valid MIME type.

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 associated with the browsing context identifies the entity which invokes this API, which can be a web app, a web page, or an iframe.

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

Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError , script execution context, Promise, JSON, JSON.stringify and JSON.parse are defined in [[!ECMASCRIPT]].

DOMString, USVString, ArrayBuffer, BufferSource and any are defined in [[!WEBIDL]].

The algorithms utf-8 encode, and utf-8 decode are defined in [[!ENCODING]].

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

Acknowledgements

Special thanks to former editor Johannes Hund (until August 2017, when at Siemens AG) for developing this specification. Also, the editors would like to thank Dave Raggett, Matthias Kovatsch, Michael Koster and Michael McCool for their comments and guidance.