COTONIC
Cotonic is a Javascript
library which makes it possible to split the javascript code of your
page into truly isolated components. By doing this a crash in one component
can never affect another component.
Cotonic provides tools to make it possible for these components to cooperate
by providing an MQTT publish/subscribe bus. This makes it possible for
components to communicate via topics.
The project is hosted on Github.
You can report bugs and discuss features on the
issues page.
Cotonic is an open-source component of Zotonic.
Installation
Place the cotonic.js,
cotonic-worker.js and
cotonic-service-worker.js scripts on a web-server.
(Right-click and use "Safe as"), or use one of the download links.
Add the following tag to the page:
<script src="//path/to/cotonic.js"
data-base-worker-src="//path/to/cotonic-worker.js"></script>
Workers
Cotonic uses Web Workers which all run in separate calling context. This
means that they will not block the main user interface thread. They are
also truly isolated from each other. This means that a crash or another
kind of problem in worker A can't crash worker B.
You can run whatever code you like in workers, with some exceptions. You
can't access the DOM, and a lot of things from the window object. This also
makes them more secure because you don't have to worry that worker code
from an external resource can steal the a credit-card number entered
somewhere in the DOM tree.
Cotonic adds a MQTT like publish subscribe mechanism to the standard javascript
web worker api. This makes it easy for web workers to communicate with each other.
// worker-a
"use strict";
self.subscribe("some/topic", function(message) {
self.publish("model/ui/update", "<p>Worker A got message</p>")
});
self.publish("model/ui/insert", "<p>Worker A started</p>");
// worker-b
"use strict";
self.subscribe("some/topic", function(message) {
self.publish("model/ui/update", "<p>Worker B got message</p>")
});
self.publish("model/ui/insert", "<p>Worker B started</p>");
let worker_a = cotonic.spawn("worker-a.js", [1, 2]);
let worker_a = cotonic.spawn("worker-b.js");
cotonic.broker.publish("some/topic", "hello workers!");
Page Functions
Cotonics main functions are available in the cotonic namespace on the main page.
The functions are mostly used to control and allow messaging between workers.
cotonic.spawn(url, [args])
Spawn a new worker. The code of the worker should be available at url. The optional
args parameter list will be passed to the worker and can be used to pass information from
the page to the worker. It can be picked up with the worker_init
callback. The structured clone algorithm will be used to send the args to the worker. See
worker functions for more information on implementing a worker.
Returns the worker-id of the newly created worker process.
cotonic.spawn("/js/example-worker.js");
=> 1
cotonic.spawn("/js/another-worker.js", ["Arg1", 1]);
=> 2
cotonic.spawn_named(name, src_url, [base], [args])
Spawn a new named worker. Named workers are unique. Use "" or undefined to create a
nameless worker. Return the worker_id of the newly spawned
worker. If the worker was already running, the existing worker_id is returned.
cotonic.spawn_named("example", "example-worker.js");
=> "worker"
cotonic.exit(nameOrwid)
Exit terminates a worker which was previously spawned and has nameOrWid as
worker-id.
const wid = cotonic.spawn("example.worker.js");
cotonic.exit(wid);
=> Worker wid is no longer running.
cotonic.send(nameOrWid, message)
Send message message to worker nameOrWid. The parameter nameOrWid
can either be the name of worker, or a worker id. The structured clone algorithm will
be used to copy the message before sending it to the worker.
cotonic.send(worker_id1, "hello");
cotonic.receive(handler)
Receive messages from workers. The handler should be a function which takes
two parameters, the message, and the worker_id.
cotonic.receive(function(message, worker_id) {
console.log("Received", message, "from worker", worker_id);
});
The page function has one configuration option.
- base_worker_src
- The url for the base worker runtime for cotonic. This runtime provides
the required communication primitives.
<script>
window.cotonic = window.cotonic || {};
window.cotonic.config = {
base_worker_src: "/base/worker-bundle.js"
};
</script>
<script src="cotonic.js"></script>
Broker
The broker module handles all local publish subscribe connections. The subscriptions
are stored in a trie datastructure allowing quick action. They are available in the
cotonic.broker namespace.
cotonic.broker.find_subscriptions_below(topic)
Find all subscribers below a certain topic. Used by the bridge to collect all subscriptions
after a session restart. Returns a list with subscriptions.
cotonic.broker.find_subscriptions_below("truck");
=> [
{type: "page", wid: 0, callback: function, sub: Object, topic: "truck/+/speed"}
{type: "page", wid: 0, callback: function, sub: Object, topic: "#"}]
cotonic.broker.match(topic)
Collect all subscribers which match the topic. Returns a list with subscriptions.
cotonic.broker.subscribe("truck/+/speed", function(msg) {
console.log("Some trucks speed", msg);
});
cotonic.broker.subscribe("truck/#", function(msg) {
console.log("Some info of a truck", msg);
});
cotonic.broker.subscribe("truck/02/speed", function(msg) {
console.log("Speed of truck 2", msg);
})
cotonic.broker.match("truck/01/speed");
=> [Object, Object] // Returns two subscriptions, truck/+/speed, and truck/#
cotonic.broker.match("truck/01/speed")[0]
=> {type: "page", wid: 0, callback: function, sub: Object, topic: "truck/+/speed"}
cotonic.broker.match("truck/02/speed");
=> [Object, Object, Object] // Returns all subscriptions
cotonic.broker.match("boat/02/speed");
=> [] // Has no subscribers
cotonic.broker.publish(topic, payload, [options])
Publish the message payload on a topic. The possible options are:
- qos
- Quality of service. Can be 0, 1, or 2. 0 means at most once, 1 at least once, and 2 exactly once.
- retain
- When retain is true, the last message sent will be stored, and delivered immediately when
a new client subscribes to the topic.
- properties
- Extra properties which can be attached to the message
cotonic.broker.publish("truck/001/temperature", 88);
=> All subscribers receive the message 88 on the topic.
cotonic.broker.publish("truck/001/speed", 74, {retain: true});
=> All subscribers receive the message 74. New subscribers will immediately receive 74.
cotonic.broker.subscribe(topics, callback, [options])
Subscribe to topics. Argument topics can either be a single topic, or a list of topics.
The function callback will be called when a message which matches
one of the topics is published. It is a function is called with two arguments, message
and info. Argument message is the received mqtt message, and info
the returned information object returned by extract.
This makes it possible to easily extract information from the topic.
Parameter options can be
- wid
- The worker if used for making the scription. Can be used to differentiate
subscriptions from different components on the page. Defaults to: 0
- qos
- The quality-of-service of the subscription. Defaults to: 0
- retain_handling
- [todo]
- retain_as_published
- [todo]
- no_local
- [todo]
- properties
- [todo]
cotonic.broker.subscribe("truck/+truck_id/speed",
function(msg, info) {
console.log("Truck", info.truckid, "speed:", msg.payload);
},
{wid: "example"});
=> The function will now be called when a truck publishes its speed.
cotonic.broker.unsubscribe(topics, [options])
Unsubscribe from the topics. The parameter topics can be a single topic as a string,
or a list of topics. The optional parameter options is an object which has the following
properties.
- wid
- The worker id from which to unsubscribe from. Defaults to: 0
cotonic.broker.unsubscribe("truck/+truck_id/speed",
{wid: "example"});
=> The subscriptions for topic "truck/+truck_id/speed" for worker "example" will not be called anymore.
cotonic.broker.call(topic, payload, [options])
Call is a special kind of publish where the publisher expects an answer back. The payload
will be published on topic using the options as described in
publish.
The caller will be temporarily subscribed to a reply topic. When an answer is received on this
reply topic, the returned promise will be resolved. When no answer is received, the promise will
be rejected with a reason.
The option parameter can have the following extra options:
- timeout
The timeout in milliseconds to use before rejecting
the returned promise. Default: 15000, or
15 seconds.
cotonic.broker.call("model/localStorage/get/username", {}, {timeout: 1000})
.then(function(username) {
console.log("The username is:", username");
})
.catch(function(e) {
console.log("Could not get username within 1 second.", e);
});
MQTT
The mqtt module provides functions to work with mqtt topics. The broker
uses this module as the basis to provide its functionality. This module also has some utility functions
to easily extract information from topics.
cotonic.mqtt.matches(pattern, topic)
Returns true when the pattern matches the topic, false otherwise.
cotonic.mqtt.matches("truck/+/speed", "truck/01/speed");
=> true
cotonic.mqtt.matches("boat/+/speed", "truck/01/speed");
=> false
cotonic.mqtt.matches("+/+/speed", "plane/01/speed");
=> true
cotonic.mqtt.matches("+/+/speed", "plane/01/height");
=> false
cotonic.mqtt.fill(pattern, params)
Fill can use a pattern topic, and use the param object to create
an mqtt topic. Returns a string with the created topic.
cotonic.mqtt.fill("truck/+truck_id/speed", {truck_id: 100});
=> "truck/100/speed"
cotonic.mqtt.extract("truck/+truck_id/speed", "truck/01/speed");
=> {truck_id: "01"}
cotonic.mqtt.extract("truck/+truck_id/#params", "truck/01/speed");
=> {truck_id: "01", params: ["speed"]}
cotonic.mqtt.exec(pattern, topic)
When the pattern matches the topic,
extract the values from the topic. Returns the
extracted values when the pattern matches, null otherwise.
cotonic.mqtt.exec("truck/+truck_id/speed", "truck/01/speed");
=> {truck_id: "01"}
cotonic.mqtt.exec("boat/+truck_id/speed", "truck/01/speed");
=> null
cotonic.mqtt.remove_named_wildcards(pattern)
Remove the special, and non mqtt compliant, wildcards from the pattern and return a compliant
topic.
cotonic.mqtt.remove_named_wildcards("truck/+truck_id/speed");
=> "truck/+/speed"
cotonic.mqtt.remove_named_wildcards("truck/#truck_info");
=> "truck/#"
mqtt_bridge
This module makes it possible to bridge the broker on the local page to
an external MQTT broker.
cotonic.mqtt_bridge.newBridge([remote], [options])
Create a new bridge. This is the hostname of the mqtt broker to connect to. When set to
"origin" the bridge uses the hostname of the document.
When the bridge is connected messages published on the topic matching bridge/+remote/# will
be re-published on the remote broker. Subscriptions on the topic bridge/+remote/#
will be published locally. This makes it possible to connect and communicate with
all clients connected to remote mqtt brokers.
Note: It is possible to connect to multiple brokers.
- protocol
- The protocol to use to connect to the mqtt broker. Defaults to "ws" when the page is
loaded via "http", "wss" otherwise.
- controller_path
- The pathname to use when connecting the web socket to the broker. Default: "mqtt-transport"
- connect_delay
- Default: 20
- periodic_delay
- Default: 1000
- username
- Default: undefined
- password
- Default: undefined
- mqtt_session
- The mqtt_session module which should be used. Default: cotonic.mqtt_session.
cotonic.mqtt_bridge.newBridge("test.mosquitto.org:8081",
{protocol: "wss"});
=> Connect the local broker to test.mosquitto.org via a websocket.
const decoder = new TextDecoder("utf-8");
cotonic.broker.subscribe("bridge/test.mosquitto.org:8081/bbc/subtitles/bbc_news24/raw",
function(m, t) {
console.log(decoder.decode(m.payload));
});
=> Subscribe to a local topic, it will be bridged from the server
to the page. This gets the raw subtitles of bbc news24.
cotonic.mqtt_bridge.findBridge([remote])
Find bridge remote, when remote is not specified, "origin" is used.
Returns the bridge, or undefined when the bridge is not found.
const b = cotonic.mqtt_bridge.findBridge("test.mosquitto.org:8081");
=> returns the bridge, or undefined
cotonic.mqtt_bridge.deleteBridge([remote])
Delete bridge remote, when remote is not specified, "origin" is used.
cotonic.mqtt_bridge.deleteBridge("test.mosquitto.org:8081");
ui
The user interface composer manages html snippets which can be placed in the DOM tree. When an
updated html snippet is delivered to the composer it will render it by using Google's incremental-dom library.
The updates will be applied incrementally directly to the DOM tree. The updates can be
delivered as html text snippets to the interface composer.
cotonic.ui.insert(targetId, mode, initialHTML, [priority])
Insert a new html snippet into the user interface composer. The snippet will be stored under the given
targetId. The element will not be placed in the dom-tree immediately. This will happen when one of the
render functions is called. Parameter mode supports the following options:
- inner or true
- The html-snippets update the innerHTML of the element.
- outer or false
- The html-snippets update the outerHTML of the element.
- shadow-open or shadow
- The html-snippets update the shadow DOM of the element. The element will be the shadow host.
The shadow dom will be put in "open" mode. When the shadow dow was not yet initialised, it will
be initialised when the element is rendered the first time. Using the shadow-dom makes it possible
to isolate the component from the css rules which are present on the page.
- shadow-closed
- Like "shadow-open", with the difference that the shadow dom will be initialised in "closed" mode.
The optional priority parameter indicates the render order of the elements.
Elements with a high priority are rendedered before lower priorities. This makes it possible to nest
elements.
cotonic.ui.insert("root", "outer", "<p>Hello World!</p>");
cotonic.ui.insert("shadow-root", "shadow", "<p>Hello World!</p>");
cotonic.ui.get(id)
Returns the current html snippet registered at id.
let currentHTML = cotonic.ui.get("root");
=> "Hello World!"
cotonic.ui.remove(id)
Remove the html snippet registered at id. Note that this will not remove the element from the dom-tree,
it will only remove it from the user interface composer. When the element must be removed it should first
be updated and set to a blank string and a render operation should be done.
cotonic.ui.remove("root");
cotonic.ui.update(id, htmlOrTokens)
Update the registered snippet for the registered element with the given id. The new snippet will be visible
after a render operation.
cotonic.ui.update("root", "<p>Hello Everybody!</p>");
=> The root element on the page will be updated.
cotonic.ui.render()
Trigger a render of all registered elements.
cotonic.ui.render();
=> All elements will be (re)rendered.
cotonic.ui.renderId(id)
Just render the element with the given id.
cotonic.ui.renderId("root");
cotonic.ui.updateStateData(model, states)
Communicate the state of the model to other non-cotonic components on the page. It can
be used to pass model state to SPA's or other modules. It sets a data attribute on the
html tag of the page.
The parameter model should be a string, states is an object
with values. The values of the states object are set as data attributes
on the html tag like this:
data-ui-<model>-<key>="<value>". When an empty object is
passed all data attributes of the model is cleared.
cotonic.ui.updateStateData("auth", {authorized: true});
=> <html data-ui-state-auth-authorized="true">
...
cotonic.ui.updateStateData("auth", {});
=> <html">
...
cotonic.ui.updateStateClass(model, classes)
Update the class of the html tag. This makes it possible to communicate important
state changes to external components like SPA's. The parameter model should be
a string. Parameter classes a list of classes which must be set.
The following elements will be added to the class attribute
ui-state-<model>-<class>. Passing [] will clear all the class
attributes of the model.
cotonic.ui.updateStateClass("auth", ["unauthorized", "pending"]);
=> <html class="ui-state-auth-pending ui-state-auth-unauthorized">
cotonic.ui.updateStateClass("auth", ["authorized"]);
=> <html class="ui-state-auth-authorized">
cotonic.ui.on(topic, msg, event, [options])
Publish a DOM event on the local broker. This allows subscribers to react to user interface
events. Parameter topic is the topic on which the event will be published. The parameters
msg and event are included in the message which is published. The event parameter
is expected to be a DOM event. The options parameter is optional, it
can contain a cancel property which can be set to true, false or
"preventDefault" to indicate if the event should be cancelled or prevented.
The other options can be the normal options found in
publish.
document.addEvenListener("click", function(e) {
const topic = event.target.getAttribute("data-topic");
if(!topic) return;
cotonic.ui.on(topic, {foo: "bar"}, e);
}, {passive: true})
=> When somebody clicks on an element with has a data-topic="a/topic"
attribute, the event will be published on that topic.
tokenizer
The tokenizer transforms text to html tokens. The tokenizer is used by the
user interface composer in order to call the incremental-dom api.
It uses incremental-dom to do in-place diffing of the dom-tree.
Note: The tokenizer does not parse or validate the html. It just
tokenizes the input.
cotonic.tokenizer.tokens(text)
Transforms a string with html tags into a list of tokens. The tokens are
objects of the form: {type: "type", [args]}, with type being one of:
- open
-
- Represents an open tag. Contains the attribute tag which is
set to the tagname of the element, and the attribute attributes
which is a list of attributes of the element.
- close
-
- Represents a close tag. Contains the attribute tag which is
set to the tagname of the close element.
- void
-
- Represents a void element. The attribute tag is set to the
tagname of the void element.
- text
-
- Represents a test element. The attribute data is set to the
text data.
- doctype
-
- Represents a doctype element. The attribute attributes is
set to the attributes of the element.
- pi
-
- Represents a processing instruction element. The attribute tag
is set to the tagname of the processing instruction. The attribute
arguments contains the list of attributes.
- comment
-
- Represents a comment element. The attribute data contains
the text in the comment element.
cotonic.tokenizer.tokens("<div class='example'>Tokenizing<br /> is cool</div>");
=> [{type: "open", tag: "div", attributes: ["class", "example"]},
{type: "text", data: "Tokenizing"},
{type: "void", tag: "br", attributes: []},
{type: "text", data: " is cool"},
{type: "close", tag: "div"}]
cotonic.tokenizer.charref(text)
Transforms a html charref into a character.
cotonic.tokenizer.charref("#128540");
=> "😜"
cotonic.tokenizer.charref("amp");
=> "&"
Worker Functions
Workers are stand alone processes. They have no shared data with the page, nor
with other workers. Their memory and calling context is isolated. They can easily communicate
with other workers, the page, and servers by publising messages on topics, and subscribing
to them. Cotonic provides models, modules which are loaded and
ready for requests.
self.connect()
Connect the worker to the page. When this step succeeds the
on_connect is called.
on_error when it fails.
self.connect();
=> The worker is being connected to the page.
self.disconnect()
Disconnects the worker from the page. After this step it is no
longer possible to send and receive messages from the page.
self.disconnect();
=> The worker is disconnected from the page.
self.is_connected()
Returns true iff the worker is connected to the page,
false otherwise.
self.is_connected();
=> true
self.subscribe(topics, callback, ack_callback)
Subscribe the worker to the topics. When a message is received, the callback is called.
Callback is a function which receives two parameters. The first parameter is the message, the second
parameter an object returned by extract. This can be used to
easily extract elements from topic paths in an object. The parameter topics can be a string, or
a list of strings. The callback ack_callback is used when the page is subscribed, or when
there is a problem. Returns nothing.
function logSpeed(msg, args) {
if(args.boat_id) {
console.log("boat", args.boat_id, "is now moving at", msg.payload);
}
if(args.truck_id) {
console.log("truck", args.truck_id, "is now moving at", msg.payload);
}
}
self.subscribe(["truck/+boat_id/speed", "boat/+boat_id/speed"], logSpeed);
=> The function logSpeed will be called when somebody sends a message which
matches the topics.
self.unsubscribe(topics, callback, ack_callback)
Unsubscribe the worker from page. The worker will no longer receive messages from the specified topics.
self.unsubscribe();
self.publish(topic, message, options)
Publish message on topic. The options can be used to indicate
the quality of service, or if the message should be retained by the broker.
self.publish("world", "hello", {retain: true});
self.call(topic, message, options)
Publishes message on topic and subscribes itself to a reply topic.
Returns a promise which is fulfilled when a message is received on the reply topic. When
no message arrives, the promise is rejected. Returns a promise.
self.call("model/document/get/all")
.then(...)
.reject(...);
self.worker_init
The callback worker_init is called when the worker receives the initialization
message by the page. It can take multiple arguments. The arguments are passed in via
the spawn args argument list.
This function can be used to initialize the worker, but it is optional.
// Worker code
let amount = null;
let targetId = null;
self.worker_init = function(n, id) {
amount = n;
targetId = id;
}
self.on_connect
The on_connect callback will be called after a successfull
connect call.
self.on_connect = function() {
// things to do after a connect
}
self.on_error
The on_error callback will be called after an unsuccessfull
connect call.
self.on_error = function() {
// things to do after an error
}
Models
Because workers run as independent components it is not possible to directly
call api's. Some api's are also not available to web workers. Models are
special modules, or workers, which publish their data, or are ready to
receive calls via mqtt topics.
The convention is that models provide their services via the following topic
tree.
- model/+modelName/get/+
- Call topics. When the model receives a publish, it returns the answer
to the reply topic.
- model/+modelName/post/+
- Topics used to post updates to the model.
- model/+modelName/delete/+
- Topics used to delete items managed by the model.
- model/+modelName/event/+
- Topics which the model publishes events on.
model/document
The document model can be used to retrieve details about the current document.
Get all information on the current document. Includes screen size, cookies, user agent details.
self.call("model/document/get/all")
.then(function(m) {
console.log(m.payload)
});
=> {screen_width: 1280, screen_height: 800,
inner_width: 1047, inner_height: 292,
is_touch: false, …}
Returns the internationalization details of the current page.
self.call("model/document/get/intl")
.then(function(m) {
console.log(m.payload)
});
=> {timezone: {cookie: "", user_agent: "Europe/Amsterdam"},
language: {cookie: "", user_agent: "en-US", document: null}}
Location
The location model can be used to retrieve information on the current location
of the page. It also allows subscription to location changes.
Get the current href.
self.call("model/location/get/href")
.then(function(m) {
console.log(m.payload)
});
=> "https://cotonic.org/#model.location"
Get the current protocol
self.call("model/location/get/protocol")
.then(function(m) {
console.log(m.payload)
});
=> "https"
Get the current host (with port).
self.call("model/location/get/host")
.then(function(m) {
console.log(m.payload)
});
=> "cotonic.org"
Get the current hostname (without port).
self.call("model/location/get/hostname")
.then(function(m) {
console.log(m.payload)
});
=> "cotonic.org"
Get the current origin.
self.call("model/location/get/origin")
.then(function(m) {
console.log(m.payload)
});
=> "https://cotonic.org"
Get the current pathname.
self.call("model/location/get/pathname")
.then(function(m) {
console.log(m.payload)
});
=> "/"
Get the current port.
cotonic.broker.call("model/location/get/port")
.then(function(m) {
console.log(m.payload)
})
=> "" // The default port.
A message containing the query string part of the url will be published when it changes.
Note: the message is retained
cotonic.broker.subscribe("model/location/event/search",
function(m, a) {
console.log("query string changed", m.payload);
});
A message containing the pathname part of the url will be published when it changes.
Note: the message is retained
cotonic.broker.subscribe("model/location/event/pathname",
function(m, a) {
console.log("pathname changed", m.payload);
});
A message containing the hash part of the url will be published when it changes.
Note: the message is retained
cotonic.broker.subscribe("model/location/event/hash",
function(m, a) {
console.log("hash changed", m.payload);
});
When the location model is enabled, a retained message is published on this
topic. By subscribing to this topic it is possible to see when the model is
enabled. The payload of the message pong.
cotonic.broker.subscribe("model/location/event/ping",
function(m) {
console.log("The location model is enabled", m.payload)
})
=> Logs a message on the console when the location model is enabled.
The location has one configuration option.
- pathname_search
- Set to the current search parameters of the page. Alternatively this setting
can can be done as data-cotonic-pathname-search attribute on the body tag. dd>
<script>
window.cotonic = window.cotonic || {};
window.cotonic.config = {
start_service_worker: true,
pathname_search: "q=110&j=yes"
};
</script>
<script src="cotonic.js"></script>
<body data-cotonic-pathname-search="q=110&j=yes">
...
</body>
model/ui
The ui model can be used to update the dom by publishing messages on one of the
model/ui/# topics. Elements can be inserted, deleted and updated. When
elements in the dom-tree are updated, the a message
model/ui/event/dom-updated/+key is published. This can be used to
react to dom-changes when they happen.
Insert a new ui snippet named key into the user interface composer.
Expects a message with the following properties.
- initialData
- The initial data to place in the dom tree.
- inner
- If set to true, the ui composer will update the inner html, when set
to false, the outer html
- priority
- An element with a high priority will be rendered before elements with a low
priority.
self.publish("model/ui/insert/root", {
initialData: "<div class='loading'>Loading</div>",
inner: true
priority: 100
}, {retain: true});
=> When the ui composer receives this message it will add the
loading
Update the contents of ui snippet named key previously registered
by an insert. Expects a message with the html snippet as message.
self.publish("model/ui/update/root",
"");
=> The root element is updated.
Delete ui snippet named key from the user interface composer.
self.publish("model/ui/delete/root");
=> The root element is removed from the composer, but
stays in the DOM tree.
A message containing the user's activity status is published regularly
on this topic. The message is an object which contains a
is_active boolean property which indicates if the has been
active over the last period between publishes.
// worker example
self.subscribe("model/ui/event/user-activity",
function(m) {
const a = m.payload.is_active:"active":"not active";
console.log("The user is: ", a);
}
);
=> Displays the user's activity status.
When the dom-tree is updated a message will be published. The
message is fired after the dom-tree is updated.
cotonic.broker.subscribe("model/ui/event/dom-updated/message-container",
function(m) {
const c = document.getElementById("message-container");
c.scrollTop = c.scrollHeight
}
);
=> Scrolls the messages bubble container to the bottom.
model/serviceWorker
The serviceWorker model makes it possible to communicate with other tabs
from the same site. This makes it possible to communicate important state
to all tabs.
The serviceWorker needs https to work correctly. The Chrome browser
does not accept self-signed certificates for running serviceWorkers.
Either run from localhost, use a real certificate, or follow the instructions
on this page by Dean Hume.
Safari and Firefox accept self-signed certificates for
running the serviceWorker.
Broadcast the message on channel. The message can be received
via the "model/serviceWorker/event/+channel topic. This works
across all open tabs.
// Publish from a worker
self.publish("model/serviceWorker/post/broadcast/background",
{hue: "blue", brightness: 45});
Subscribe to the broadcast channel of the serviceWorker. Messages
posted from other tabs or workers, including messages sent by the publisher
will be received.
// Subscribe on a page
cotonic.broker.subscribe("model/serviceWorker/event/broadcast/background",
function(m) {
console.log("Setting background to", m.payload");
setBackground(m.payload);
})
When the serviceWorker model is enabled it publishes a retained pong
message on this topic. This makes it possible to check if the model is enabled.
// Subscribe on a page
cotonic.broker.subscribe("model/serviceWorker/event/ping",
function() {
console.log("the serviceWorker model is enabled")
});
The service worker model has two configuration options.
- start_service_worker
- When set to false, the service worker will not start.
default true
- service_worker_src
- The url of the service worker which should be started.
default
/cotonic-service-worker.js
<script>
window.cotonic = window.cotonic || {};
window.cotonic.config = {
start_service_worker: true,
service_worker_src: "/cotonic-service-worker.js"
};
</script>
<script src="cotonic.js"></script>
model/localStorage
Gets item key from localStorage. Returns the content as payload.
cotonic.broker.call("model/localStorage/get/a")
.then(
function(m) {
console.log("a is set to:", m.payload)
}
);
Update, or insert message under key in localStorage.
cotonic.broker.publish("model/localStorage/post/a", "Hello world!");
Delete item stored under key from localStorage.
cotonic.broker.publish("model/localStorage/delete/a");
Subscribe to changes or deletions from localStorage. When the message payload
is null the item is delete. Otherwise the payload is set to the
newly updated value.
cotonic.broker.subscribe("model/localStorage/event/+key",
function(m, a) {
if(m.payload === null) {
console.log("localStorage item:", a.key, "deleted");
else {
console.log("localStorage item:", a.key, "changed", m.payload);
}
});
=> Logs update to localStorage elements.
When the localStorage model is enabled it publishes a retained message
under the topic model/localStorage/event/ping. It makes it
possible to detect the localStorage model is enabled.
cotonic.broker.subscribe("model/localStorage/event/ping",
function() {
console.log("localStorage is enabled");
});
model.sessionStorage
The sessionStorage model provids access to the sessionStorage of
the browser. It makes it possible to set and retrieve values via
mqtt topics. For more information about the session storage see:
Get the element stored as key from the sessionStorage. Returns the
contents as payload, or null if it is not found.
self.call("model/sessionStorage/get/item-1")
.then(function(m) {
console.log("item-1", m.payload);
});
=> Logs the item-1 on the console, or null otherwise.
Get a sub element stored as key.subkey from the sessionStorage.
self.call("model/sessionStorage/get/item-2/a")
.then(function(m) {
console.log("item-2", m.payload);
});
=> Logs the item-2 on the console, or null otherwise.
Store a message under key in the sessionStorage.
self.publish("model/sessionStorage/post/item-1", "Cucumbers are sometimes green");
=> New value stored.
Store a message under key.subkey in the sessionStorage. When no
item is stored under key a new object is created, and subkey
is added as sub-element. When key exists it must be an object, then
subkey is added or overwritten.
self.publish("model/sessionStorage/post/item-2/a", "hello");
Delete an element stored under key from the sessionStorage.
self.publish("model/sessionStorage/delete/item-2");
Delete an element stored under key.subkey from the sessionStorage.
self.publish("model/sessionStorage/delete/item-2");
Subscribe to sessionStorage updates and deletes. When entry is updated
you will get a notification.
self.subscribe("model/sessionStorage/subscribe/+key",
function(m, a) {
console.log(m, a);
});
self.publish("model/sessionStorage/post/a", "hello");
self.publish("model/sessionStorage/post/b", "world");
=> Logs the update in the console
When the sessionStorage model is enabled a retained message is published
on the model/sessionStorage/event/ping topic.
let storageReady = false;
cotonic.broker.subscribe("model/sessionStorage/event/+key",
function(msg, args) {
switch(args.key)
case "pong":
storageReady = true;
break;
...
});
=> When the storage is ready a pong message will be available
Links
Introduction to Incremental DOM
MQTT Version 5.0 - OASIS
Web Workers API
sessionStorage API
Using shadow DOM
Change Log
— Nov 2, 2020 —
Diff —
Docs —
Download
-
More documentation.
-
A js based configuration api.
-
And a lot of little fixes.
— Mar 1, 2020 —
Diff —
Docs —
Download
-
Added more documentation and an example.
-
Load the base worker library via a standard importScripts, instead of url blob.
-
The bridge now supports connecting to any MQTT server.
-
Removed the embedded polyfills, and use an external polyfill provider when needed.
Mostly for IE11.
— Feb 10, 2020 —
Diff —
Docs —
Download
-
Added documentation, and
-
made it possible to use the bridge to connect to mqtt servers
— Jan 30, 2020 —
Diff —
Docs —
Download
-
Fixed all the tests, and
-
made the documentation more readable on mobile.
— Jan 23, 2020 —
Docs —
Download