-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
new Channel API #20731
new Channel API #20731
Conversation
5a1804e
to
272a1c0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first 5 commits are unrelated/independent. Sending a separate review for these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this looks nice! I have a few nitpicks, but mostly questions.
this.#received[command] = control; | ||
this.emit(command, control); | ||
} else { | ||
this.emit('control', control); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked a lot of the umpteen uncovered code complaints. Many are really unreachable "should not happen" code, and things like this are because we just ported over one channel type. This should get covered if we e.g. port the file channel to this (anything that supports a binary channel).
dea421a
to
a1fefe1
Compare
48bb540
to
b68c2a5
Compare
So the new warnings about 'send messages after close' are showing that we sometimes call I wonder if we should try to fix that at the use site (I guess fsinfo?) or try to be more tolerant in this particular case... |
af30ac5
to
1263b0b
Compare
I'm almost 💯 sure that this is a flake, but let's retry to compare. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! There's only a bunch of questions and small stuff left. This grew so big, I hope the next iteration can be "it". 😁
1263b0b
to
b390fd1
Compare
Hmm, these unit tests hanging on exactly all Debian/Ubuntus sounds too much for a coincidence 😢 |
Wait -- these tests fail in exactly the same way in #20772 -- did we break main somehow? |
Last landed PR #20763 from 8 hours ago had green tests. This happens during .deb package build, so it can't access the internet even. So what on earth changed!? |
Could be a case of landing two PRs in parallel, each tested against their common ancestor, but never tested together until they hit main. |
I'm gonna assume this has something to do with #20768... it's acting like the systemd mainloop isn't being properly registered. |
...and indeed we landed #20768 without a full slate because I didn't think pytest ran during image builds. |
→ #20773 |
- disable the base no-use-before-define rule: it doesn't understand that TypeScript allows using types before they're defined. Enable the TypeScript-specific rule for it for TypeScript files. - add an 'unused variables' config matching the default rules of the TypeScript compiler Both of these rule stanzas were copied from the documentation of the typescript-eslint plugin. Links to those pages are provided in comments, which are valid in the extended JSON syntax used for eslintrc. Tweak our test/static-code rule for JSON to also exempt eslintrc from our strict checking.
Having these in the "global" namespace alongside everything in node_modules seems like a disaster waiting to happen. Let's make a `cockpit` dir and import them like `'cockpit/fsinfo'`, etc.
Stop using array_from_raw_string() and join_data() to create binary frames and replace it with open-coded uses of `TextEncoder.encode()` and `Uint8Array.set()`. Drop the now-unused `array_from_raw_string()`. Tweak the type check for binary messages to 'anything not a string'.
There are two APIs in pkg/lib/_internal/transport.js that aren't used in any way outside of that file. Don't "export" them.
Take the offer of the TypeScript compiler to "Convert function to an ES2015 class". This was a completely automated operation.
This class defines a single event, and the only code that connects to it lives inside of the same file. We can move this to our new API quite easily.
Avoiding `function` means that `this` is the same thing throughout the entire constructor function. Change a couple of `window` event listeners while we're at it.
We no longer need this alias for `this` — since we only use arrows inside of the constructor, we can access `this` directly.
Currently all of the functionality of this class is defined inside of the constructor. We want to start moving some of it out to separate functions, so move all of the private state to instance private properties.
These methods no longer depend on anything inside of the constructor() function and can be moved out. This is safe because there are no call sites that use these functions as callbacks, so `this` will always be correctly bound. The methods formerly exported on the `self` object are turned into public methods and the others are turned into privates with `#`. The one tricky case here is `dispatch_data()` which is now a public method, but also used as the WebSocket `.onmessage` handler. In that case we use an arrow to make sure `this` is properly provided: this.#ws.onmessage = event => this.dispatch_data(event);
Use a spread operator instead. Also let's us avoid using `.apply()`.
We can safely assume by now that the browser always supports WebSocket. This lets us eliminate a fallback path and clean up the logic a bit.
This essentially follows all the steps that we did with Transport, but is small enough to do it in a single go.
Instead of having the transport code reach in to the global `cockpit` object to set properties, just have the `init_callback` (already part of the `cockpit` code) do it for us.
"in" filtering is used by the shell to inspect all incoming messages for forwarding to the appropriate frame, but nothing uses "out" filters, outside of a single test case which exists only to test the feature. Removing this will substantially simplify the code and allow for further cleanups, so let's drop it.
Now that we dropped support for filters, this only has a single call site, and the types it has to deal with are somewhat simplified. Merge it directly into that site, combining the logic for reading the channel name with the logic for splitting out the payload part of the message.
Instead of setting this to null by default, make it an empty list. That way we can avoid the check for initializing it, and can do a simple iteration, unconditionally.
Instead of checking for truthiness, check that the value is actually a string before assigning it to a variable that's used as a string.
This seems like a bit of a strange thing to do, but it will make it easier to add annotations when porting to TypeScript. Annotating object literals requires providing a type separate from the value, whereas a class allows the annotations to be given inline.
After all the prep work, this is a reasonably straight-forward exercise.
This is the API that we intend all future channel implementations (including users outside of cockpit.js) to use. Port fsinfo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rebased after the pytest fix, cheers! This should go green now. 🚀
if (!transport_globals.outgoing_filters) | ||
transport_globals.outgoing_filters = []; | ||
transport_globals.outgoing_filters.push(callback); | ||
console.error("'out' filters are no longer supported"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This added line is not executed by any test.
@@ -33,7 +33,7 @@ if (typeof window.debugging === "undefined") { | |||
get: function() { return window.sessionStorage.debugging || window.localStorage.debugging }, | |||
set: function(x) { window.sessionStorage.debugging = x } | |||
}); | |||
} catch (e) { } | |||
} catch { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This added line is not executed by any test.
@@ -7,7 +7,7 @@ function get_url_root(): string | null { | |||
try { | |||
// Sometimes this throws a SecurityError such as during testing | |||
return window.localStorage.getItem('url-root'); | |||
} catch (e) { | |||
} catch { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This added line is not executed by any test.
readyState = 0; | ||
|
||
// essentially signal handlers: these are assigned to from Transport | ||
onopen(): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This added line is not executed by any test.
onopen(): void { | ||
} | ||
|
||
onclose(): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This added line is not executed by any test.
if (this.#received.ready) { | ||
resolve(this.#received.ready); | ||
} else if (this.#received.close) { | ||
reject(this.#received.close); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 4 added lines are not executed by any test.
this.on('ready', resolve); | ||
this.on('close', reject); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 2 added lines are not executed by any test.
toString(): string { | ||
const state = | ||
(!this.id && 'waiting for transport') || | ||
(this.#received.close?.problem && `${this.id} error ${this.#received.close.problem}`) || | ||
(this.#received.close && `${this.id} closed`) || | ||
(this.#received.ready && `${this.id} opened`) || | ||
`${this.id} waiting for open`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 7 added lines are not executed by any test.
(this.#received.ready && `${this.id} opened`) || | ||
`${this.id} waiting for open`; | ||
|
||
const host = this.options?.host || "localhost"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This added line is not executed by any test.
|
||
const host = this.options?.host || "localhost"; | ||
|
||
return `[Channel ${state} -> ${this.options?.payload}@${host}]`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This added line is not executed by any test.
This brings along a new approach to typing
Channel
which should make it easier to implement derived channel types.