LCOV - code coverage report
Current view: top level - pkg/lib/cockpit/_internal - channel.js Coverage Total Hit
Test: cockpit Lines: 97.9 % 190 186
Test Date: 2026-07-13 10:00:01

            Line data    Source code
       1              : // SPDX-License-Identifier: LGPL-2.1-or-later
       2              : import { join_data } from './common';
       3              : import { Deferred } from './deferred';
       4              : import { event_mixin } from './event-mixin';
       5              : import { ensure_transport, transport_globals } from './transport';
       6              : 
       7              : /* -------------------------------------------------------------------------
       8              :  * Channels
       9              :  *
      10              :  * Public: https://cockpit-project.org/guide/latest/api-base1.html
      11              :  */
      12              : 
      13          368 : export function Channel(options) {
      14          368 :     const self = this;
      15              : 
      16              :     /* We can trigger events */
      17          368 :     event_mixin(self, { });
      18              : 
      19          368 :     let transport;
      20          368 :     let ready = null;
      21          368 :     let closed = null;
      22          368 :     let waiting = null;
      23          368 :     let received_done = false;
      24          368 :     let sent_done = false;
      25          368 :     let id = null;
      26          368 :     const binary = (options.binary === true);
      27              : 
      28              :     /*
      29              :      * Queue while waiting for transport, items are tuples:
      30              :      * [is_control ? true : false, payload]
      31              :      */
      32          368 :     const queue = [];
      33              : 
      34              :     /* Handy for callers, but not used by us */
      35          368 :     self.valid = true;
      36          368 :     self.options = options;
      37          368 :     self.binary = binary;
      38          368 :     self.id = id;
      39              : 
      40          365 :     function on_message(payload) {
      41           67 :         if (received_done) {
      42           67 :             console.warn("received message after done");
      43           67 :             self.close("protocol-error");
      44           67 :         } else {
      45          365 :             self.dispatchEvent("message", payload);
      46          365 :         }
      47          365 :     }
      48              : 
      49          387 :     function on_close(data) {
      50          387 :         closed = data;
      51          387 :         self.valid = false;
      52          387 :         if (transport && id)
      53          387 :             transport.unregister(id);
      54          182 :         if (closed.message && !options.err)
      55          104 :             console.warn(closed.message);
      56          387 :         self.dispatchEvent("close", closed);
      57          387 :         if (waiting)
      58           89 :             waiting.resolve(closed);
      59          387 :     }
      60              : 
      61          365 :     function on_ready(data) {
      62          365 :         ready = data;
      63          365 :         self.dispatchEvent("ready", ready);
      64          365 :     }
      65              : 
      66          368 :     function on_control(data) {
      67          367 :         if (data.command == "close") {
      68          367 :             on_close(data);
      69          367 :             return;
      70          364 :         } else if (data.command == "ready") {
      71          365 :             on_ready(data);
      72          365 :         }
      73              : 
      74          365 :         const done = data.command === "done";
      75           67 :         if (done && received_done) {
      76           67 :             console.warn("received two done commands on channel");
      77           67 :             self.close("protocol-error");
      78           67 :         } else {
      79          365 :             if (done)
      80          364 :                 received_done = true;
      81          365 :             self.dispatchEvent("control", data);
      82          365 :         }
      83          368 :     }
      84              : 
      85          368 :     function send_payload(payload) {
      86          368 :         if (!binary) {
      87          368 :             if (typeof payload !== "string")
      88           67 :                 payload = String(payload);
      89          368 :         }
      90          368 :         transport.send_message(payload, id);
      91          368 :     }
      92              : 
      93          368 :     ensure_transport(function(trans) {
      94          368 :         transport = trans;
      95          368 :         if (closed)
      96          368 :             return;
      97              : 
      98          368 :         id = transport.next_channel();
      99          368 :         self.id = id;
     100              : 
     101              :         /* Register channel handlers */
     102          368 :         transport.register(id, on_control, on_message);
     103              : 
     104              :         /* Now open the channel */
     105          368 :         const command = { };
     106          368 :         for (const i in options)
     107          368 :             if (i !== "binary")
     108          368 :                 command[i] = options[i];
     109              :         /* handle binary specially: Our JS API has always been boolean, while the wire protocol is
     110              :          * a string with the only valid value "raw". */
     111          368 :         if (binary)
     112           79 :             command.binary = "raw";
     113          368 :         command.command = "open";
     114          368 :         command.channel = id;
     115              : 
     116          368 :         if (!command.host) {
     117          368 :             if (transport_globals.default_host)
     118          365 :                 command.host = transport_globals.default_host;
     119          368 :         }
     120              : 
     121          368 :         command["flow-control"] = true;
     122          368 :         transport.send_control(command);
     123              : 
     124              :         /* Now drain the queue */
     125          368 :         while (queue.length > 0) {
     126          368 :             const item = queue.shift();
     127           67 :             if (item[0]) {
     128           67 :                 item[1].channel = id;
     129           67 :                 transport.send_control(item[1]);
     130           67 :             } else {
     131          368 :                 send_payload(item[1]);
     132          368 :             }
     133          368 :         }
     134          368 :     });
     135              : 
     136          368 :     self.send = function send(message) {
     137          368 :         if (closed)
     138           67 :             console.warn("sending message on closed channel");
     139          368 :         else if (sent_done)
     140           67 :             console.warn("sending message after done");
     141          368 :         else if (!transport)
     142          365 :             queue.push([false, message]);
     143              :         else
     144          365 :             send_payload(message);
     145          368 :     };
     146              : 
     147           42 :     self.control = function control(options) {
     148            4 :         options = options || { };
     149           42 :         if (!options.command)
     150           14 :             options.command = "options";
     151           42 :         if (options.command === "done")
     152           33 :             sent_done = true;
     153           42 :         options.channel = id;
     154           42 :         if (!transport)
     155            4 :             queue.push([true, options]);
     156              :         else
     157           42 :             transport.send_control(options);
     158           42 :     };
     159              : 
     160           35 :     self.wait = function wait(callback) {
     161           35 :         if (!waiting) {
     162           35 :             waiting = new Deferred();
     163            4 :             if (closed) {
     164            4 :                 waiting.reject(closed);
     165            4 :             } else if (ready) {
     166            5 :                 waiting.resolve(ready);
     167            5 :             } else {
     168           34 :                 self.addEventListener("ready", function(event, data) {
     169           34 :                     waiting.resolve(data);
     170           34 :                 });
     171            3 :                 self.addEventListener("close", function(event, data) {
     172            3 :                     waiting.reject(data);
     173            3 :                 });
     174           35 :             }
     175           35 :         }
     176           35 :         const promise = waiting.promise;
     177           35 :         if (callback)
     178           34 :             promise.then(callback, callback);
     179           35 :         return promise;
     180           35 :     };
     181              : 
     182          280 :     self.close = function close(options) {
     183          280 :         if (closed)
     184          280 :             return;
     185              : 
     186          279 :         if (!options)
     187           77 :             options = { };
     188          248 :         else if (typeof options == "string")
     189          167 :             options = { problem: options };
     190          279 :         options.command = "close";
     191          279 :         options.channel = id;
     192              : 
     193          279 :         if (!transport)
     194           32 :             queue.push([true, options]);
     195              :         else
     196          279 :             transport.send_control(options);
     197          279 :         on_close(options);
     198          280 :     };
     199              : 
     200          360 :     self.buffer = function buffer(callback) {
     201          360 :         const buffers = [];
     202          360 :         buffers.callback = callback;
     203          355 :         buffers.squash = function squash() {
     204          355 :             return join_data(buffers, binary);
     205          355 :         };
     206              : 
     207          353 :         function on_message(event, data) {
     208          353 :             buffers.push(data);
     209          221 :             if (buffers.callback) {
     210          221 :                 const block = join_data(buffers, binary);
     211          221 :                 if (block.length > 0) {
     212          221 :                     const consumed = buffers.callback.call(self, block);
     213           62 :                     if (typeof consumed !== "number" || consumed === block.length) {
     214          221 :                         buffers.length = 0;
     215           62 :                     } else if (consumed === 0) {
     216           62 :                         buffers.length = 1;
     217           62 :                         buffers[0] = block;
     218           62 :                     } else if (consumed !== 0) {
     219           62 :                         buffers.length = 1;
     220           62 :                         if (block.subarray)
     221           62 :                             buffers[0] = block.subarray(consumed);
     222           62 :                         else if (block.substring)
     223           62 :                             buffers[0] = block.substring(consumed);
     224              :                         else
     225           62 :                             buffers[0] = block.slice(consumed);
     226           62 :                     }
     227          221 :                 }
     228          221 :             }
     229          353 :         }
     230              : 
     231          355 :         function on_close() {
     232          355 :             self.removeEventListener("message", on_message);
     233          355 :             self.removeEventListener("close", on_close);
     234          355 :         }
     235              : 
     236          360 :         self.addEventListener("message", on_message);
     237          360 :         self.addEventListener("close", on_close);
     238              : 
     239          360 :         return buffers;
     240          360 :     };
     241              : 
     242            0 :     self.toString = function toString() {
     243            0 :         const host = options.host || "localhost";
     244            0 :         return "[Channel " + (self.valid ? id : "<invalid>") + " -> " + host + "]";
     245            0 :     };
     246          368 : }
        

Generated by: LCOV version 2.0-1