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-06-25 11:17:56

            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          369 : export function Channel(options) {
      14          369 :     const self = this;
      15              : 
      16              :     /* We can trigger events */
      17          369 :     event_mixin(self, { });
      18              : 
      19          369 :     let transport;
      20          369 :     let ready = null;
      21          369 :     let closed = null;
      22          369 :     let waiting = null;
      23          369 :     let received_done = false;
      24          369 :     let sent_done = false;
      25          369 :     let id = null;
      26          369 :     const binary = (options.binary === true);
      27              : 
      28              :     /*
      29              :      * Queue while waiting for transport, items are tuples:
      30              :      * [is_control ? true : false, payload]
      31              :      */
      32          369 :     const queue = [];
      33              : 
      34              :     /* Handy for callers, but not used by us */
      35          369 :     self.valid = true;
      36          369 :     self.options = options;
      37          369 :     self.binary = binary;
      38          369 :     self.id = id;
      39              : 
      40          366 :     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          366 :             self.dispatchEvent("message", payload);
      46          366 :         }
      47          366 :     }
      48              : 
      49          388 :     function on_close(data) {
      50          388 :         closed = data;
      51          388 :         self.valid = false;
      52          388 :         if (transport && id)
      53          388 :             transport.unregister(id);
      54          186 :         if (closed.message && !options.err)
      55          104 :             console.warn(closed.message);
      56          388 :         self.dispatchEvent("close", closed);
      57          388 :         if (waiting)
      58           89 :             waiting.resolve(closed);
      59          388 :     }
      60              : 
      61          366 :     function on_ready(data) {
      62          366 :         ready = data;
      63          366 :         self.dispatchEvent("ready", ready);
      64          366 :     }
      65              : 
      66          369 :     function on_control(data) {
      67          368 :         if (data.command == "close") {
      68          368 :             on_close(data);
      69          368 :             return;
      70          365 :         } else if (data.command == "ready") {
      71          366 :             on_ready(data);
      72          366 :         }
      73              : 
      74          366 :         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          366 :             if (done)
      80          365 :                 received_done = true;
      81          366 :             self.dispatchEvent("control", data);
      82          366 :         }
      83          369 :     }
      84              : 
      85          369 :     function send_payload(payload) {
      86          369 :         if (!binary) {
      87          369 :             if (typeof payload !== "string")
      88           67 :                 payload = String(payload);
      89          369 :         }
      90          369 :         transport.send_message(payload, id);
      91          369 :     }
      92              : 
      93          369 :     ensure_transport(function(trans) {
      94          369 :         transport = trans;
      95          369 :         if (closed)
      96          369 :             return;
      97              : 
      98          369 :         id = transport.next_channel();
      99          369 :         self.id = id;
     100              : 
     101              :         /* Register channel handlers */
     102          369 :         transport.register(id, on_control, on_message);
     103              : 
     104              :         /* Now open the channel */
     105          369 :         const command = { };
     106          369 :         for (const i in options)
     107          369 :             if (i !== "binary")
     108          369 :                 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          369 :         if (binary)
     112           79 :             command.binary = "raw";
     113          369 :         command.command = "open";
     114          369 :         command.channel = id;
     115              : 
     116          369 :         if (!command.host) {
     117          369 :             if (transport_globals.default_host)
     118          366 :                 command.host = transport_globals.default_host;
     119          369 :         }
     120              : 
     121          369 :         command["flow-control"] = true;
     122          369 :         transport.send_control(command);
     123              : 
     124              :         /* Now drain the queue */
     125          369 :         while (queue.length > 0) {
     126          369 :             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          369 :                 send_payload(item[1]);
     132          369 :             }
     133          369 :         }
     134          369 :     });
     135              : 
     136          369 :     self.send = function send(message) {
     137          369 :         if (closed)
     138           67 :             console.warn("sending message on closed channel");
     139          369 :         else if (sent_done)
     140           67 :             console.warn("sending message after done");
     141          369 :         else if (!transport)
     142          366 :             queue.push([false, message]);
     143              :         else
     144          366 :             send_payload(message);
     145          369 :     };
     146              : 
     147           44 :     self.control = function control(options) {
     148            6 :         options = options || { };
     149           44 :         if (!options.command)
     150           16 :             options.command = "options";
     151           44 :         if (options.command === "done")
     152           35 :             sent_done = true;
     153           44 :         options.channel = id;
     154           44 :         if (!transport)
     155            6 :             queue.push([true, options]);
     156              :         else
     157           44 :             transport.send_control(options);
     158           44 :     };
     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          282 :     self.close = function close(options) {
     183          282 :         if (closed)
     184          282 :             return;
     185              : 
     186          281 :         if (!options)
     187           80 :             options = { };
     188          253 :         else if (typeof options == "string")
     189          171 :             options = { problem: options };
     190          281 :         options.command = "close";
     191          281 :         options.channel = id;
     192              : 
     193          281 :         if (!transport)
     194           35 :             queue.push([true, options]);
     195              :         else
     196          281 :             transport.send_control(options);
     197          281 :         on_close(options);
     198          282 :     };
     199              : 
     200          361 :     self.buffer = function buffer(callback) {
     201          361 :         const buffers = [];
     202          361 :         buffers.callback = callback;
     203          359 :         buffers.squash = function squash() {
     204          359 :             return join_data(buffers, binary);
     205          359 :         };
     206              : 
     207          357 :         function on_message(event, data) {
     208          357 :             buffers.push(data);
     209          219 :             if (buffers.callback) {
     210          219 :                 const block = join_data(buffers, binary);
     211          219 :                 if (block.length > 0) {
     212          219 :                     const consumed = buffers.callback.call(self, block);
     213           63 :                     if (typeof consumed !== "number" || consumed === block.length) {
     214          219 :                         buffers.length = 0;
     215           63 :                     } else if (consumed === 0) {
     216           63 :                         buffers.length = 1;
     217           63 :                         buffers[0] = block;
     218           63 :                     } else if (consumed !== 0) {
     219           63 :                         buffers.length = 1;
     220           63 :                         if (block.subarray)
     221           63 :                             buffers[0] = block.subarray(consumed);
     222           63 :                         else if (block.substring)
     223           63 :                             buffers[0] = block.substring(consumed);
     224              :                         else
     225           63 :                             buffers[0] = block.slice(consumed);
     226           63 :                     }
     227          219 :                 }
     228          219 :             }
     229          357 :         }
     230              : 
     231          359 :         function on_close() {
     232          359 :             self.removeEventListener("message", on_message);
     233          359 :             self.removeEventListener("close", on_close);
     234          359 :         }
     235              : 
     236          361 :         self.addEventListener("message", on_message);
     237          361 :         self.addEventListener("close", on_close);
     238              : 
     239          361 :         return buffers;
     240          361 :     };
     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          369 : }
        

Generated by: LCOV version 2.0-1