fix: Resolve cargo clippy errors and add CI workflow plan
## Changes
### Clippy Fixes
- Fixed deprecated `cargo_bin` usage in 27 test files (added #![allow(deprecated)])
- Fixed uninlined_format_args in zombienet-sdk-tests
- Fixed subxt API changes in revive/rpc/tests.rs (fetch signature, StorageValue)
- Fixed dead_code warnings in validator-pool and identity-kyc mocks
- Fixed field name `i` -> `_i` in tasks example
### CI Infrastructure
- Added .claude/WORKFLOW_PLAN.md for tracking CI fix progress
- Updated lychee.toml and taplo.toml configs
### Files Modified
- 27 test files with deprecated cargo_bin fix
- bizinikiwi/pezframe/revive/rpc/src/tests.rs (subxt API)
- pezkuwi/pezpallets/validator-pool/src/{mock,tests}.rs
- pezcumulus/teyrchains/pezpallets/identity-kyc/src/mock.rs
- bizinikiwi/pezframe/examples/tasks/src/tests.rs
## Status
- cargo clippy: PASSING
- Next: cargo fmt, zepter, workspace checks
This commit is contained in:
@@ -1,40 +0,0 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! Default platform for WASM environments.
|
||||
|
||||
#[cfg(feature = "web")]
|
||||
mod wasm_helpers;
|
||||
#[cfg(feature = "web")]
|
||||
mod wasm_platform;
|
||||
#[cfg(feature = "web")]
|
||||
mod wasm_socket;
|
||||
|
||||
pub use helpers::{DefaultPlatform, build_platform};
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
mod helpers {
|
||||
use smoldot_light::platform::default::DefaultPlatform as Platform;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type DefaultPlatform = Arc<Platform>;
|
||||
|
||||
pub fn build_platform() -> DefaultPlatform {
|
||||
Platform::new(
|
||||
"subxt-light-client".into(),
|
||||
env!("CARGO_PKG_VERSION").into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "web")]
|
||||
mod helpers {
|
||||
use super::wasm_platform::SubxtPlatform as Platform;
|
||||
|
||||
pub type DefaultPlatform = Platform;
|
||||
|
||||
pub fn build_platform() -> DefaultPlatform {
|
||||
Platform::new()
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! Wasm implementation for the light client's platform using
|
||||
//! custom websockets.
|
||||
|
||||
use super::wasm_socket::WasmSocket;
|
||||
|
||||
use core::time::Duration;
|
||||
use futures_util::{FutureExt, future};
|
||||
|
||||
pub fn now_from_unix_epoch() -> Duration {
|
||||
web_time::SystemTime::now()
|
||||
.duration_since(web_time::SystemTime::UNIX_EPOCH)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!("Invalid systime cannot be configured earlier than `UNIX_EPOCH`")
|
||||
})
|
||||
}
|
||||
|
||||
pub type Instant = web_time::Instant;
|
||||
|
||||
pub fn now() -> Instant {
|
||||
web_time::Instant::now()
|
||||
}
|
||||
|
||||
pub type Delay = future::BoxFuture<'static, ()>;
|
||||
|
||||
pub fn sleep(duration: Duration) -> Delay {
|
||||
futures_timer::Delay::new(duration).boxed()
|
||||
}
|
||||
|
||||
/// Implementation detail of a stream from the `SubxtPlatform`.
|
||||
#[pin_project::pin_project]
|
||||
pub struct Stream(
|
||||
#[pin]
|
||||
pub smoldot::libp2p::with_buffers::WithBuffers<
|
||||
future::BoxFuture<'static, Result<WasmSocket, std::io::Error>>,
|
||||
WasmSocket,
|
||||
Instant,
|
||||
>,
|
||||
);
|
||||
@@ -1,226 +0,0 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use super::wasm_socket::WasmSocket;
|
||||
|
||||
use core::{
|
||||
fmt::{self, Write as _},
|
||||
net::IpAddr,
|
||||
time::Duration,
|
||||
};
|
||||
use futures::prelude::*;
|
||||
use smoldot::libp2p::with_buffers;
|
||||
use smoldot_light::platform::{
|
||||
Address, ConnectionType, LogLevel, MultiStreamAddress, MultiStreamWebRtcConnection,
|
||||
PlatformRef, SubstreamDirection,
|
||||
};
|
||||
|
||||
use std::{io, net::SocketAddr, pin::Pin};
|
||||
|
||||
const LOG_TARGET: &str = "subxt-platform-wasm";
|
||||
|
||||
/// Subxt platform implementation for wasm.
|
||||
///
|
||||
/// This implementation is a conversion of the implementation from the smoldot:
|
||||
/// https://github.com/smol-dot/smoldot/blob/6401d4df90569e23073d646b14a8fbf9f7e6bdd3/light-base/src/platform/default.rs#L83.
|
||||
///
|
||||
/// This platform will evolve over time and we'll need to keep this code in sync.
|
||||
#[derive(Clone)]
|
||||
pub struct SubxtPlatform {}
|
||||
|
||||
impl SubxtPlatform {
|
||||
pub fn new() -> Self {
|
||||
SubxtPlatform {}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformRef for SubxtPlatform {
|
||||
type Delay = super::wasm_helpers::Delay;
|
||||
type Instant = super::wasm_helpers::Instant;
|
||||
type MultiStream = std::convert::Infallible;
|
||||
type Stream = super::wasm_helpers::Stream;
|
||||
type StreamConnectFuture = future::Ready<Self::Stream>;
|
||||
type MultiStreamConnectFuture = future::Pending<MultiStreamWebRtcConnection<Self::MultiStream>>;
|
||||
type ReadWriteAccess<'a> = with_buffers::ReadWriteAccess<'a, Self::Instant>;
|
||||
type StreamUpdateFuture<'a> = future::BoxFuture<'a, ()>;
|
||||
type StreamErrorRef<'a> = &'a std::io::Error;
|
||||
type NextSubstreamFuture<'a> = future::Pending<Option<(Self::Stream, SubstreamDirection)>>;
|
||||
|
||||
fn now_from_unix_epoch(&self) -> Duration {
|
||||
super::wasm_helpers::now_from_unix_epoch()
|
||||
}
|
||||
|
||||
fn now(&self) -> Self::Instant {
|
||||
super::wasm_helpers::now()
|
||||
}
|
||||
|
||||
fn fill_random_bytes(&self, buffer: &mut [u8]) {
|
||||
// This could fail if the system does not have access to a good source of entropy.
|
||||
// Note: `rand::RngCore::fill_bytes` also panics on errors and `rand::OsCore` calls
|
||||
// identically into `getrandom::getrandom`.
|
||||
getrandom::getrandom(buffer).expect("Cannot fill random bytes");
|
||||
}
|
||||
|
||||
fn sleep(&self, duration: Duration) -> Self::Delay {
|
||||
super::wasm_helpers::sleep(duration)
|
||||
}
|
||||
|
||||
fn sleep_until(&self, when: Self::Instant) -> Self::Delay {
|
||||
self.sleep(when.saturating_duration_since(self.now()))
|
||||
}
|
||||
|
||||
fn spawn_task(
|
||||
&self,
|
||||
_task_name: std::borrow::Cow<'_, str>,
|
||||
task: impl future::Future<Output = ()> + Send + 'static,
|
||||
) {
|
||||
wasm_bindgen_futures::spawn_local(task);
|
||||
}
|
||||
|
||||
fn client_name(&self) -> std::borrow::Cow<'_, str> {
|
||||
"subxt-light-client".into()
|
||||
}
|
||||
|
||||
fn client_version(&self) -> std::borrow::Cow<'_, str> {
|
||||
env!("CARGO_PKG_VERSION").into()
|
||||
}
|
||||
|
||||
fn supports_connection_type(&self, connection_type: ConnectionType) -> bool {
|
||||
let result = matches!(
|
||||
connection_type,
|
||||
ConnectionType::WebSocketIpv4 { .. }
|
||||
| ConnectionType::WebSocketIpv6 { .. }
|
||||
| ConnectionType::WebSocketDns { .. }
|
||||
);
|
||||
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
"Supports connection type={:?} result={}",
|
||||
connection_type, result
|
||||
);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn connect_stream(&self, multiaddr: Address) -> Self::StreamConnectFuture {
|
||||
tracing::trace!(target: LOG_TARGET, "Connect stream to multiaddr={:?}", multiaddr);
|
||||
|
||||
// `PlatformRef` trait guarantees that `connect_stream` is only called with addresses
|
||||
// stated in `supports_connection_type`.
|
||||
let addr = match multiaddr {
|
||||
Address::WebSocketDns {
|
||||
hostname,
|
||||
port,
|
||||
secure: true,
|
||||
} => {
|
||||
format!("wss://{hostname}:{port}")
|
||||
}
|
||||
Address::WebSocketDns {
|
||||
hostname,
|
||||
port,
|
||||
secure: false,
|
||||
} => {
|
||||
format!("ws://{hostname}:{port}")
|
||||
}
|
||||
Address::WebSocketIp {
|
||||
ip: IpAddr::V4(ip),
|
||||
port,
|
||||
} => {
|
||||
let addr = SocketAddr::from((ip, port));
|
||||
format!("ws://{addr}")
|
||||
}
|
||||
Address::WebSocketIp {
|
||||
ip: IpAddr::V6(ip),
|
||||
port,
|
||||
} => {
|
||||
let addr = SocketAddr::from((ip, port));
|
||||
format!("ws://{addr}")
|
||||
}
|
||||
|
||||
// The API user of the `PlatformRef` trait is never supposed to open connections of
|
||||
// a type that isn't supported.
|
||||
_ => {
|
||||
unreachable!(
|
||||
"Connecting to an address not supported. This code path indicates a bug in smoldot. Please raise an issue at https://github.com/smol-dot/smoldot/issues"
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let socket_future = async move {
|
||||
tracing::debug!(target: LOG_TARGET, "Connecting to addr={addr}");
|
||||
WasmSocket::new(addr.as_str()).map_err(|err| std::io::Error::other(err.to_string()))
|
||||
};
|
||||
|
||||
future::ready(super::wasm_helpers::Stream(with_buffers::WithBuffers::new(
|
||||
Box::pin(socket_future),
|
||||
)))
|
||||
}
|
||||
|
||||
fn connect_multistream(&self, _address: MultiStreamAddress) -> Self::MultiStreamConnectFuture {
|
||||
panic!(
|
||||
"Multistreams are not currently supported. This code path indicates a bug in smoldot. Please raise an issue at https://github.com/smol-dot/smoldot/issues"
|
||||
)
|
||||
}
|
||||
|
||||
fn open_out_substream(&self, c: &mut Self::MultiStream) {
|
||||
// This function can only be called with so-called "multi-stream" connections. We never
|
||||
// open such connection.
|
||||
match *c {}
|
||||
}
|
||||
|
||||
fn next_substream(&self, c: &'_ mut Self::MultiStream) -> Self::NextSubstreamFuture<'_> {
|
||||
// This function can only be called with so-called "multi-stream" connections. We never
|
||||
// open such connection.
|
||||
match *c {}
|
||||
}
|
||||
|
||||
fn read_write_access<'a>(
|
||||
&self,
|
||||
stream: Pin<&'a mut Self::Stream>,
|
||||
) -> Result<Self::ReadWriteAccess<'a>, &'a io::Error> {
|
||||
let stream = stream.project();
|
||||
stream.0.read_write_access(Self::Instant::now())
|
||||
}
|
||||
|
||||
fn wait_read_write_again<'a>(
|
||||
&self,
|
||||
stream: Pin<&'a mut Self::Stream>,
|
||||
) -> Self::StreamUpdateFuture<'a> {
|
||||
let stream = stream.project();
|
||||
Box::pin(stream.0.wait_read_write_again(|when| async move {
|
||||
let now = super::wasm_helpers::now();
|
||||
let duration = when.saturating_duration_since(now);
|
||||
super::wasm_helpers::sleep(duration).await;
|
||||
}))
|
||||
}
|
||||
|
||||
fn log<'a>(
|
||||
&self,
|
||||
log_level: LogLevel,
|
||||
log_target: &'a str,
|
||||
message: &'a str,
|
||||
key_values: impl Iterator<Item = (&'a str, &'a dyn fmt::Display)>,
|
||||
) {
|
||||
let mut message_build = String::with_capacity(128);
|
||||
message_build.push_str(message);
|
||||
let mut first = true;
|
||||
for (key, value) in key_values {
|
||||
if first {
|
||||
let _ = write!(message_build, "; ");
|
||||
first = false;
|
||||
} else {
|
||||
let _ = write!(message_build, ", ");
|
||||
}
|
||||
let _ = write!(message_build, "{key}={value}");
|
||||
}
|
||||
|
||||
match log_level {
|
||||
LogLevel::Error => tracing::error!("target={log_target} {message_build}"),
|
||||
LogLevel::Warn => tracing::warn!("target={log_target} {message_build}"),
|
||||
LogLevel::Info => tracing::info!("target={log_target} {message_build}"),
|
||||
LogLevel::Debug => tracing::debug!("target={log_target} {message_build}"),
|
||||
LogLevel::Trace => tracing::trace!("target={log_target} {message_build}"),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use futures::{io, prelude::*};
|
||||
use send_wrapper::SendWrapper;
|
||||
use wasm_bindgen::{JsCast, prelude::*};
|
||||
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
pin::Pin,
|
||||
sync::{Arc, Mutex},
|
||||
task::Poll,
|
||||
task::{Context, Waker},
|
||||
};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Failed to connect {0}")]
|
||||
ConnectionError(String),
|
||||
}
|
||||
|
||||
/// Websocket for WASM environments.
|
||||
///
|
||||
/// This is a rust-based wrapper around browser's WebSocket API.
|
||||
///
|
||||
// Warning: It is not safe to have `Clone` on this structure.
|
||||
pub struct WasmSocket {
|
||||
/// Inner data shared between `poll` and web_sys callbacks.
|
||||
inner: Arc<Mutex<InnerWasmSocket>>,
|
||||
/// This implements `Send` and panics if the value is accessed
|
||||
/// or dropped from another thread.
|
||||
///
|
||||
/// This is safe in wasm environments.
|
||||
socket: SendWrapper<web_sys::WebSocket>,
|
||||
/// In memory callbacks to handle messages from the browser socket.
|
||||
_callbacks: SendWrapper<Callbacks>,
|
||||
}
|
||||
|
||||
/// The state of the [`WasmSocket`].
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
enum ConnectionState {
|
||||
/// Initial state of the socket.
|
||||
Connecting,
|
||||
/// Socket is fully opened.
|
||||
Opened,
|
||||
/// Socket is closed.
|
||||
Closed,
|
||||
/// Error reported by callbacks.
|
||||
Error,
|
||||
}
|
||||
|
||||
struct InnerWasmSocket {
|
||||
/// The state of the connection.
|
||||
state: ConnectionState,
|
||||
/// Data buffer for the socket.
|
||||
data: VecDeque<u8>,
|
||||
/// Waker from `poll_read` / `poll_write`.
|
||||
waker: Option<Waker>,
|
||||
}
|
||||
|
||||
/// Registered callbacks of the [`WasmSocket`].
|
||||
///
|
||||
/// These need to be kept around until the socket is dropped.
|
||||
type Callbacks = (
|
||||
Closure<dyn FnMut()>,
|
||||
Closure<dyn FnMut(web_sys::MessageEvent)>,
|
||||
Closure<dyn FnMut(web_sys::Event)>,
|
||||
Closure<dyn FnMut(web_sys::CloseEvent)>,
|
||||
);
|
||||
|
||||
impl WasmSocket {
|
||||
/// Establish a WebSocket connection.
|
||||
///
|
||||
/// The error is a string representing the browser error.
|
||||
/// Visit [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket#exceptions_thrown)
|
||||
/// for more info.
|
||||
pub fn new(addr: &str) -> Result<Self, Error> {
|
||||
let socket = match web_sys::WebSocket::new(addr) {
|
||||
Ok(socket) => socket,
|
||||
Err(err) => return Err(Error::ConnectionError(format!("{err:?}"))),
|
||||
};
|
||||
|
||||
socket.set_binary_type(web_sys::BinaryType::Arraybuffer);
|
||||
|
||||
let inner = Arc::new(Mutex::new(InnerWasmSocket {
|
||||
state: ConnectionState::Connecting,
|
||||
data: VecDeque::with_capacity(16384),
|
||||
waker: None,
|
||||
}));
|
||||
|
||||
let open_callback = Closure::<dyn FnMut()>::new({
|
||||
let inner = inner.clone();
|
||||
move || {
|
||||
let mut inner = inner.lock().expect("Mutex is poised; qed");
|
||||
inner.state = ConnectionState::Opened;
|
||||
|
||||
if let Some(waker) = inner.waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
});
|
||||
socket.set_onopen(Some(open_callback.as_ref().unchecked_ref()));
|
||||
|
||||
let message_callback = Closure::<dyn FnMut(_)>::new({
|
||||
let inner = inner.clone();
|
||||
move |event: web_sys::MessageEvent| {
|
||||
let Ok(buffer) = event.data().dyn_into::<js_sys::ArrayBuffer>() else {
|
||||
panic!("Unexpected data format {:?}", event.data());
|
||||
};
|
||||
|
||||
let mut inner = inner.lock().expect("Mutex is poised; qed");
|
||||
let bytes = js_sys::Uint8Array::new(&buffer).to_vec();
|
||||
inner.data.extend(bytes);
|
||||
|
||||
if let Some(waker) = inner.waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
});
|
||||
socket.set_onmessage(Some(message_callback.as_ref().unchecked_ref()));
|
||||
|
||||
let error_callback = Closure::<dyn FnMut(_)>::new({
|
||||
let inner = inner.clone();
|
||||
move |_event: web_sys::Event| {
|
||||
// Callback does not provide useful information, signal it back to the stream.
|
||||
let mut inner = inner.lock().expect("Mutex is poised; qed");
|
||||
inner.state = ConnectionState::Error;
|
||||
|
||||
if let Some(waker) = inner.waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
});
|
||||
socket.set_onerror(Some(error_callback.as_ref().unchecked_ref()));
|
||||
|
||||
let close_callback = Closure::<dyn FnMut(_)>::new({
|
||||
let inner = inner.clone();
|
||||
move |_event: web_sys::CloseEvent| {
|
||||
let mut inner = inner.lock().expect("Mutex is poised; qed");
|
||||
inner.state = ConnectionState::Closed;
|
||||
|
||||
if let Some(waker) = inner.waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
});
|
||||
socket.set_onclose(Some(close_callback.as_ref().unchecked_ref()));
|
||||
|
||||
let callbacks = (
|
||||
open_callback,
|
||||
message_callback,
|
||||
error_callback,
|
||||
close_callback,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
inner,
|
||||
socket: SendWrapper::new(socket),
|
||||
_callbacks: SendWrapper::new(callbacks),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for WasmSocket {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
let mut inner = self.inner.lock().expect("Mutex is poised; qed");
|
||||
inner.waker = Some(cx.waker().clone());
|
||||
|
||||
if self.socket.ready_state() == web_sys::WebSocket::CONNECTING {
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
match inner.state {
|
||||
ConnectionState::Error => Poll::Ready(Err(io::Error::other("Socket error"))),
|
||||
ConnectionState::Closed => Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())),
|
||||
ConnectionState::Connecting => Poll::Pending,
|
||||
ConnectionState::Opened => {
|
||||
if inner.data.is_empty() {
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
let n = inner.data.len().min(buf.len());
|
||||
for k in buf.iter_mut().take(n) {
|
||||
*k = inner.data.pop_front().expect("Buffer non empty; qed");
|
||||
}
|
||||
Poll::Ready(Ok(n))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for WasmSocket {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
let mut inner = self.inner.lock().expect("Mutex is poised; qed");
|
||||
inner.waker = Some(cx.waker().clone());
|
||||
|
||||
match inner.state {
|
||||
ConnectionState::Error => Poll::Ready(Err(io::Error::other("Socket error"))),
|
||||
ConnectionState::Closed => Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())),
|
||||
ConnectionState::Connecting => Poll::Pending,
|
||||
ConnectionState::Opened => match self.socket.send_with_u8_array(buf) {
|
||||
Ok(()) => Poll::Ready(Ok(buf.len())),
|
||||
Err(err) => Poll::Ready(Err(io::Error::other(format!("Write error: {err:?}")))),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
if self.socket.ready_state() == web_sys::WebSocket::CLOSED {
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
|
||||
if self.socket.ready_state() != web_sys::WebSocket::CLOSING {
|
||||
let _ = self.socket.close();
|
||||
}
|
||||
|
||||
let mut inner = self.inner.lock().expect("Mutex is poised; qed");
|
||||
inner.waker = Some(cx.waker().clone());
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WasmSocket {
|
||||
fn drop(&mut self) {
|
||||
if self.socket.ready_state() != web_sys::WebSocket::CLOSING {
|
||||
let _ = self.socket.close();
|
||||
}
|
||||
|
||||
self.socket.set_onopen(None);
|
||||
self.socket.set_onmessage(None);
|
||||
self.socket.set_onerror(None);
|
||||
self.socket.set_onclose(None);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user