cockpit.js: DBus Client DBus API communication

Cockpit allows access to DBus services via this API.

## DBus Types

DBus values are represented as javascript values and objects as follows:

`BYTE 'y'`

  Javascript number.

`BOOLEAN 'b'`

  Javascript boolean.

`INT16 'n'`

  Javascript number.

`UINT16 'q'`

  Javascript number.

`INT32 'i'`

  Javascript number.

`UINT32 'u'`

  Javascript number.

`INT64 'x'`

  Javascript number.

`UINT64 't'`

  Javascript number.

`DOUBLE 'd'`

  Javascript number.

`STRING 's'`

  Javascript string.

`OBJECT_PATH 'o'`

  Javascript string.

`SIGNATURE 'g'`

  Javascript string.

`ARRAY of BYTE 'ay'`

  A string containing base64 encoded data.

`ARRAY of DICT_ENTRY with STRING keys 'a{s?}'`

  A javascript plain object with the keys as property names.

`ARRAY of DICT_ENTRY with other keys 'a{??}'`

  A javascript plain object each key JSON encoded into a string property name.

`ARRAY of other`

  A javascript array.

`VARIANT`

  A javascript plain object with the `"t"` property set to a DBus type string, and the `"v"` property set to a value.

`HANDLE 'h'`

  A javascript object that describes a cockpit channel which represents the passed file descriptor. The `payload` is always set to `stream`. Pass it to cockpit.channel() to create the channel and start reading or writing on it. Handles can only be received, not sent from within cockpit.

## cockpit.dbus()

    client = cockpit.dbus(name, [options])

Create a DBus client for the given bus `name` (eg: service name). Use the following functions to make DBus method calls, watch for events, etc. The optional `options` argument is a javascript plain object, and may include:

`"bus"`

  The DBus bus to connect to. Specifying `"session"` will connect to the DBus user session bus, `"user"` will connect to the user bus (on some systems this is identical to the session bus), and `"system"` will connect to the DBus system bus. This defaults to "system" if not present.

`"host"`

  The host to open the channel to. If an alternate user or port is required it can be specified as `"user@myhost:port"`. If no host is specified then the correct one will be automatically selected based on the page calling this function.

`"superuser"`

  Set to `"require"` to talk to this service as root. The DBus service will see the DBus method calls and accesses as coming from root, rather than the logged in user. This is useful for talking to services that do not correctly use polkit to authorize administrative users. If the currently logged in user is not permitted to become root (eg: via `pkexec`) then the `client` will immediately be closed with a `"access-denied"` problem code.

  Set to `"try"` to try to talk as root, but if that fails, fall back to unprivileged.

`"track"`

  It is valid for a DBus service to exit, and be restarted in such a way that clients continue to talk to it across the restart. Some services are not written with this in mind. If the `"track"` option is set to `true` then the channel will close when the service exits and/or disconnects from the DBus bus.

If the `name` argument is null, and no options other than `"bus"` are specified, then a shared DBus `client` is created. When using such a client with a DBus bus, a `"name"` option must be specified on various other methods in order to specify which client to talk to.

## client.wait()

    promise = client.wait([callback])

Returns a `promise` that is ready when the client is ready, or fails if the client closes. If a `callback` is specified, it is attached to the promise.

## client.close()

    client.close([problem])

Close the DBus client. If `problem` is specified it should be a problem code string.

## client.onclose

    client.addEventListener("close", function(options) { ... })

An event triggered when the DBus client closes. This can happen either because client.close() function was called, or the DBus service went away, or some other problem or disconnection.

The `options` will contain various close information, including a `"problem"` field which will be set if the channel was closed because of a problem.

## client.onowner

    client.addEventListener("owner", function(event, owner) { ... })

An event triggered when the owner of the DBus name changes. The owner value will be the id of the name owner on the bus or null if the name is unowned. The absence of an owner should not be treated as a disconnection. However this makes it possible to take some action based on the actual status of the service, for example disconnecting a pending signal handler.

## client.options

Set to the options used when creating the client. Will not change for the life of the client.

## client.unique_name

The unique DBus name of the client. Initially null, and becomes valid once the the client is ready.

## client.proxy()

    proxy = client.proxy([interface, path], [options])

Create proxy javascript object for a DBus `interface`. At the specified DBus object `path`. The proxy will have properties, methods and signals from to the DBus interface, and allows for natural interaction. If no `interface` is specified then the DBus bus name of the client is used. If no `path` is specified, then the DBus name of the client is converted to a path.

If creating lots of proxies for a given `interface` it is more efficient to use the `client.proxies()` function.

The proxy is loaded when the `proxy.valid` field is `true`, and it is set to `false` if the underlying `interface` and/or `path` don't or no longer exist, or the `client` has closed. You can wait for proxy to become valid by passing a callback to its `proxy.wait()` function. The `proxy.onchanged` event will also fire when the proxy becomes valid or invalid. DBus properties and methods on the proxy are not defined until the proxy becomes valid.

    value = proxy.Prop1
    proxy.WritableProp = value

All DBus properties on the `interface` that start with an upper case letter (as is convention) will be automatically defined on this proxy, and will update their values as the DBus property values change. In addition the `proxy.onchanged` event will fire every time the properties change.

If you assign a value to a writable property on the proxy, the proxy will try to set that property on the DBus `interface` at `path`. The actual proxy property value will not update until the DBus service has notified the proxy of the change. If setting a property fails a warning will be logged. In order to have more reliable setting of properties, or track when they have been set, or if setting fails, use the `client.call()` directly. It should be noted that DBus service implementations may also be inconsistent in their behavior when setting a property fails.

You can access the raw property data using the `proxy.data` field, including data for properties that do not start with an upper case letter.

    proxy.Method(arg1, arg2)
        .done(function(retval1, retval2) {
            ...
        })
        .fail(function(ex) {
            ...
        });

All DBus methods on the `interface` that start with an upper case letter (as is convention) will be automatically defined on this proxy. These methods are called with arguments as normal javascript arguments. A promise that will complete successfully when the method returns, or fail if an error occurs. The return values from the DBus method will be passed to the `done` handler function directly.

Methods that do not start with an upper case letter can be invoked by using the usual `proxy.call()` directly.

    proxy.addEventListener("Signal", function(event, arg1, arg2) {
        ...
    });

All DBus signals on the `interface` that start with an upper case letter (as is convention) will be automatically emit events on this proxy. These events will contain the signal arguments after the standard `event` argument.

Signals that do not start with an upper case letter can be subscribed to by using `proxy.onsignal` directly.

Usually a proxy asks the `client` to watch and notify it of changes to the relevant object or path. You can pass an `options` argument with the `watch` field set to `false` to prevent this.

## proxy.client

Set to the DBus client of the proxy. Will not change for the life of the proxy.

## proxy.path

Set to the DBus object path of the proxy. Will not change for the life of the proxy.

## proxy.iface

Set to the DBus interface name of the proxy. Will not change for the life of the proxy.

## proxy.valid

Set to `true` when the proxy's DBus interface is present at its DBus path, and all information for the proxy has loaded. Is set to `false` while loading, and after the proxy no longer refers a DBus interface and path. Also set to `false` if the `client` closes.

Use the by `proxy.wait()` function to wait for a proxy to load. The `proxy.onchanged` event will also be emitted when the proxy becomes valid or invalid. DBus properties and methods on the proxy are not defined until the proxy becomes valid.

## proxy.data

A plain javascript object containing all the raw property data that this proxy has loaded. This will be updated automatically as the proxy is notified of property changes from the DBus service. The `proxy.onchanged` event will be emitted when it changes.

## proxy.call()

    invocation = proxy.call(method, args, [options])

Make a DBus method call on this proxy.

For DBus methods that start with an upper case letter, is usually more convenient to call the method directly on the proxy. However if methods that do not follow the usual DBus convention, or specify additional options, or the caller cannot be sure that the method actually exists, you can use this method.

This function also works on proxies that have are still loading and have not become valid yet.

The `method` should be a DBus method name, and the `args` should be an array of arguments to pass to the method. The `options` are described elsewhere.

The returned value is identical to the one returned from client.call(). It is a promise that will complete successfully when the method returns, or fail if an error occurs.

## proxy.wait()

    promise = proxy.wait()
    proxy.wait(function() {
        ...
    });

Wait for a proxy to finish loading. This function returns a promise. If a callback function is passed as an argument then that function will be invoked when the proxy is ready. If this method is called after a proxy has already loaded, then the promise will be resolved immediately, and any callback will be invoked immediately. Use the promise or `proxy.valid` to determine whether the proxy is valid.

## proxy.onchanged

    proxy.addEventListener("changed", function(data) {
        ...
    });

This event is emitted when the proxy's properties change.

The `data` has the following form, and will only include properties that have changed:

    {
        "Prop1": "value",
        "Prop2": 5
    }

## proxy.onsignal

    proxy.addEventListener("signal", function(event, name, args) {
        ...
    });

This event is emitted when the proxy's emits an event.

For most events, that have names which start with an upper case letter, you can just connect to that event as a signal directly. However if you wish to be notified when any signal is emitted, or for signals that do not follow the usual DBus convention, you can connect to this event.

The `name` is the DBus signal name, and the `args` is an array of arguments that were emitted with the signal.

## client.proxies()

    proxies = client.proxies([interface], [path_namespace], [options])

Create proxy javascript objects for a DBus interfaces. The proxies will have properties, methods and signals from the DBus `interface`, and allow for natural interaction. If no `interface` is specified then the DBus bus name of the client is used. If no `path_namespace` is provided then `"/"` will be used.

Proxies will be automatically created for instances of the `interface` available at the DBus service. The optional `path_namespace` argument can be used to restrict the proxies for instances that have DBus paths which have the namespace path prefix.

    proxy1 = proxies["/dbus/path1"];
    proxy2 = proxies["/dbus/path2"];
    for (proxy in proxies) {
        ...
    }

The returned `proxies` object will is used as a dictionary, and will have values containing proxies for DBus interface instances, with the keys being the DBus paths of those instances. It is possible to enumerate over the returned `proxies`.

Proxies will be automatically added and removed from the `proxies` object as they appear and disappear in the service. The `proxies.onadded` and `proxies.onremoved` events will be emitted. DBus services may not support notifications of paths disappearing.

Use the `proxies.wait()` function to be notified when the initial set of proxies has been populated.

Usually a proxies ask the `client` to watch and be notified of changes to the relevant object or path. You can pass an `options` argument with the `watch` field set to `false` to prevent this.

## proxies.wait()

    promise = proxies.wait()
    proxies.wait(function() {
        ...
    });

Wait for a `proxies` object to populate its initial set of proxies. This function returns a promise. If a callback function is passed as an argument then that function will be invoked when the proxies are ready. If this method is called after the proxies have populated, then the promise will be resolved immediately, and any callback will be invoked immediately.

## proxies.client

Set to the DBus client of the proxies. Will not change.

## proxies.iface

Set to the DBus interface name of the proxies. Will not change.

## proxies.path_namespace

Set to the DBus path namespace used which the proxies must have as a DBus path prefix. Will not change.

## proxies.onadded

    proxies.addEventListener("added", function(event, proxy) {
        ...
    })

This event is emitted when a proxy is added to the `proxies` object. The proxy will already have loaded.

## proxies.onchanged

    proxies.addEventListener("changed", function(event, proxy) {
        ...
    })

This event is emitted when one of the proxy in the `proxies` object changes its properties.

## proxies.onremoved

    proxies.addEventListener("removed", function(event, proxy) {
        ...
    })

This event is emitted when a proxy is removed to the `proxies` object.

## client.call()

    invocation = client.call(path, interface, method, args, [options])

Make a DBus method call.

The `path` is the DBus object path to make the call on, `interface` is the DBus interface for the method and `method` is the name of the method to call. The `args` is an array of arguments to pass to the method, each of which must be appropriate for the expected DBus type of that argument. The `args` may be `null` if no arguments are to be sent.

The returned value is a promise that will complete successfully when the method returns, or fail if an error occurs.

If `options` is specified it should be a plain javascript object, which may contain the following properties:

`flags`

  A string containing DBus message flags. The character `"i"` indicates to the dbus service that interactive authentication is allowed. If the entire `flags` field is missing, then `"i"` is set by default.

`type`

  A valid DBus type signature to use when calling the method. In the absence of this, the DBus service will be introspected (and the result cached) to ask what the method type signature is.

`timeout`

  The timeout of the call in milliseconds. The call will fail with the `"timeout"` problem code. If "timeout" is not given, the call will never time out.

## invocation.done()

    invocation.done(function(args, options) { ... })

This is a standard promise method. It sets up a handler to be called when the DBus method call finishes successfully.

The `args` argument is an array of return values from the DBus method. Each of them will be converted to an appropriate javascript type.

The `options` argument may contain additional information about the reply. If the `type` option was specified when performing the method call, then the `options` in the reply here will also contain a `type` field containing the DBus type signature of the output. If the `flags` option was specified when performing the call then the `options` in the reply here will contain message flags. Possible out message flags are:

`>`

  A big endian message.

`<`

  A little endian message.

## invocation.fail()

    invocation.fail(function(exception) { ... })

This is a standard jQuery promise method. It sets up a handler to be called when the DBus method call fails.

The `exception` object passed to the handler can have the following properties:

`problem`

  A problem code string when a problem occurred starting or communicating with the DBus service. This is `null` in the cases where an actual DBus error was occurred.

`name`

  The DBus error name. This will be `null` in cases where the failure was not due to a DBus error.

`message`

  A DBus error message. This will be `null` in cases where the failure was not due to a DBus error.

## invocation.always()

    invocation.always(function() { ... })

This is a standard jQuery promise method. It sets up a handler to be called when the DBus method call finishes whether successfully, or fails.

## client.subscribe()

    subscription = client.subscribe(match, function(path, interface, signal, args) { ... })

Subscribe to signals. The `match` argument is a javascript plain object which defines what signals to subscribe to. Each property in the `match` argument restricts signals subscribed to. If a property is not present then it is treated as a wildcard, matching anything. If an empty object is specified as `match` then all signals will be subscribed to. The `match` argument may contain the following properties:

`interface`

  A DBus interface to match.

`path`

  A DBus object path to match. May not be used together with the `path_namespace` property. It should be a valid DBus object path, that is, it should have no trailing slash.

`path_namespace`

  A DBus object path prefix to match. Any paths in the hierarchy below this top path will match. May not be used together with the `path` property.

`member`

  The DBus signal name to match.

`arg0`

  Matches the first argument of a DBus message, which must be a string.

The handler passed as the second argument will be invoked when the signal is received. A `subscription` is returned which can be used to remove the subscription by calling its `subscription.remove()` method.

It is not a problem to subscribe to the same signals more than once, with identical or slightly different `match` arguments.

## subscription.remove()

    subscription.remove()

Unsubscribe from the DBus signal subscription.

## client.watch()

    watch = client.watch(path)
    watch = client.watch({ "path_namespace": path_namespace, "interface": interface })

Watch for property and interface changes on the given DBus object `path` DBus `path_namespace`. If `interface` is specified only properties on that DBus interface will be watched.

The `client.proxy()` and `client.proxies()` functions and the objects they return are high level wrappers around `client.watch()`.

The property and interface changes will be available in raw form on the `client.onnotify` event.

Property and interface changes that are caused by a method call or signal will show up before that method call reply is received, or signal event is triggered. It should be possible to rely on this guarantee, unless the DBus service in question behaves incorrectly. Internally these watches work well with code that implements the ObjectManager portion of the DBus specification. If no ObjectManager implementation is available, the watch falls back to using DBus Introspection along with the usual PropertiesChanged signal. If the DBus service implements none of these, or implements them in an inconsistent manner, then this function will provide inconsistent or unexpected results.

The parameter is either a DBus `path` or a plain javascript object with zero or more of the following fields. If an empty javascript object is used as an argument, then all paths, interfaces and properties will be watched.

`interface`

  Watch properties on this DBus interface.

`path`

  Watch interfaces and properties at this DBus path. May not be used together with the `path_namespace` property.

`path_namespace`

  Watch interfaces and properties under this DBus path. It should be a valid DBus object path, that is, it should have no trailing slash. If an ObjectManager implementation is available at this interface, then it is used. May not be used together with the `path` property.

The returned value is a promise that will complete successfully when the watch has populated its initial set of properties and interfaces, and these have been notified via `client.onnotify`.

A watch can be removed by calling the `watch.remove()` method on the returned value. If identical watches are added more than once, then they must also be removed the same number of times before the removal takes effect.

## watch.done()

    watch.done(function() { ... })

This is a standard promise method. It sets up a handler to be called when the watch has populated its initial properties and interfaces.

## watch.fail()

    watch.fail(function() { ... })

This is a standard jQuery promise method. It sets up a handler to be called if the watch fails to populate its initial properties and interfaces. Note that a watch will only fail if the DBus client closes or is somehow disconnected. It does not fail in the case of missing interfaces or properties.

## watch.always()

    watch.always(function() { ... })

This is a standard jQuery promise method. It sets up a handler to be called when the watch has populated its initial properties and interfaces or has failed to do so.

## watch.remove()

    watch.remove()

Remove the watch. This may not have any immediate effect if other watches are in place. In particular, if identical watches are added more than once, then they must also be removed the same number of times before the removal takes effect.

## client.onnotify

    client.addEventListener("notify", function(data) { ... })

An event triggered when watched properties or interfaces change.

The `client.proxy()` and `client.proxies()` functions and the objects they return are high level wrappers around the `data` provided by this event.

The `data` has the following form:

    {
        "/path1": {
            "org.Interface1": {
                "Prop1": "value",
                "Prop2": 5
            },
            "org.Interface2": null
        }
    }

Multiple paths may be present, each of which may have multiple interfaces, each of which may have multiple properties. The first time a given path and interface is emitted from this signal, it will have all its properties and interfaces. Thereafter only changes are noted. If an interface is set to `null`, then that interface has disappeared.

## client.notify()

    client.notify(data)

Emits a synthetic `notify` event. The `data` argument should follow the same layout as described for the `notify` event.

## client.onmeta

    client.onmeta = function(ev, data) { ... }

An event triggered when the meta data about watched interfaces is loaded.

The `client.proxy()` and `client.proxies()` functions and the objects they return are high level wrappers around the `data` provided by this event.

The `data` has the following form:

      {
          "org.Interface": {
              "methods": {
                  "Method1": {
                      "in": [ "s", "v" ],
                      "out": [ "i" ]
                  },
                  "Method2": { }
              },
              "signals": {
                  "Signal": {
                      "in": [ "b", "s" ]
                  }
              },
              "properties": {
                  "Prop1": {
                      "flags": "rw",
                      "type": "s"
                  },
                  "Prop2": {
                      "flags": "r",
                      "type": "b"
                  }
              }
          }
      }

Multiple interfaces may be present, each of which may have methods and properties. This is emitted before the first `client.onnotify` event for the relevant interface.

## client.meta()

    client.meta(data)

Populate DBus introspection metadata about DBus interfaces. Usually this metadata is automatically introspected from the DBus services called by the `client`, but in certain cases it is useful to populate this info from javascript code. One such case is when exporting a DBus service from javascript.

The `client.onmeta` event will be emitted by this method call. It's documentation includes information about the form of the `data`.

## client.publish()

    published = client.publish(path, interface, object, [options])

Publish a DBus `interface` at the specified object `path`. Methods on the Javascript `object` will be invoked when DBus methods on the specified `interface` are invoked by callers.

The meta data for the DBus interface must have been populated using the `client.meta()` function. The returned `published` object is a promise object. It will resolve when the DBus interface has been published on the bus. It is not an error to publish the same interface and path repeatedly: Previously published objects at the interface and path are replaced by those published later.

## published.remove()

    published.remove()

Un-publishes a DBus interface at an object path. This method can be invoked on the `published` result returned from the `client.publish()` function.

## cockpit.variant()

    variant = cockpit.variant(type, value)

A DBus variant is represented as a plain javascript object with a `"t"` property represesting the full DBus type of the variant, and a `"v"` property containing the variant value.

This is a helper function for creating such a variant object.

## cockpit.byte_array()

    byte_array = cockpit.byte_array(value)

A DBus byte array is represented as base64 data encoded in a string. This is a helper function for creating such a byte array.