ABCDEFGHIJKLMNOPQRSTUVWXYZ
1
IssueExampleCurrent Behavior/DiscussionChange to...
2
3
Different closeCode/closeReason for locally-initiated closeawait channel.sink.close();
// Server responds with a close frame.
// timing will change the result for dart:html
// await Future.delay(const Duration(seconds: 1));
expect(channel.closeCode, ?);
expect(channel.closeReason, ?);
dart:html: If closed locally then the values of `channel.closeCode`/`channel.closeReason` will either be null or the server-provided code/reason depending on timing, i.e., whether the JavaScript `onClose` event has been received or not.
dart:io: If closed locally, the channel waits up to 5 seconds for the server to respond with a close code/reason and then uses that code/reason for `channel.closeCode`/`channel.closeReason` before completing the `channel.sink.close` future.
Rename `closeCode` to `serverCloseCode`.
Rename `closeReason` to `serverCloseReason`.

`serverCloseCode` and `serverCloseReason` will always be `null` if `channel.sink.close` is called before a remote close frame is received (or there is a failure e.g. 1006 for a network disconnect). The `channel.stream` will always be done when `serverCloseCode` and `serverCloseReason` are non-null.
4
Different closeCode/closeReason for locally-initiated close when the server does not respond quickly with a close frameawait channel.sink.close(4444, 'client initiated');
// Server responds with a close frame after 5s.
expect(channel.closeCode, ?);
expect(channel.closeReason, ?);
dart:html: The **local** code/reason are used for `channel.closeCode`/`channel.closeReason`. Chrome seems to generate an onClose event with the local code/reason if the server does not respond quickly enough.
dart:io: `channel.closeCode`/`channel.closeReason` are `null`
Rename `closeCode` to `serverCloseCode`.
Rename `closeReason` to `serverCloseReason`.

`serverCloseCode` and `serverCloseReason` will always be `null` if `channel.sink.close` is called before a remote close frame is received (or there is a failure e.g. 1006 for a network disconnect). The `channel.stream` will always be done when `serverCloseCode` and `serverCloseReason` are non-null.
5
Different handling of sink.add when the datatype is not supportedchannel.sink.add(const Duration(seconds: 5))dart:html: Stringifies the Duration and transmits it as text. Stringifies all types except ones that it supports directly, which does *not* include List<int>
dart:io: Unhandled exception for all types other than String and List<int>
All implementations must support String (for text frames) and List<int> (for binary frames). Implementations can support other types (e.g. web implementations can suppport Blob). All implementations must throw UnsupportedError for types that they don't understand, i.e., converting them to a string and sending a text frame is not acceptable. If the input is List<int> then ArgumentError must be thrown if any values are >255 or <0.
6
Different handling of sink.close()await channel.sink.close();
expect(await channel.stream.isEmpty, true);
dart:html: When the sink is closed, no new data will be received on the stream but existing data can still be read. The test passes.
dart:io: When the sink is closed, the stream is also closed so channel.stream.isEmpty throws a BadStateError
dart:html
7
Different handling of sink.add after sink.addErrorchannel.sink.addError(Exception('what should this do?'));
channel.sink.add('Hello World!');
expect(await channel.stream.isEmpty, true);
await expectLater(channel.sink.close(), throwsException);
dart:html: nothing is transmitted to the server, unhandled exception, all test expectations met
dart:io: nothing is transmitted to the server, unhandled exception, `await channel.stream.isEmpty` never returns

IOSink.addError documents "See flush or done for how to get any errors generated by this call.", so getting the exception on .close() might be the expected behavior (though channel.sink is a StreamSink, not an IOSink)

But, since there is no reason to call `addError`, just throw UnsupportedError() instead of dealing with this semantic mess.
channel.sink.addError should throw UnsupportedError() and not close `channel.sink`.
8
expect(channel.protocol, ?); // Where no protocol was negotiated
dart:html: `channel.protocol` is the empty string
dart:io: `channel.protocol` is `null`
dart:io
9
Different exception when websocket upgrade not acceptedawait expectLater(
channel.ready, throwsA(isA<WebSocketChannelException>()));
expect(channel.closeCode, null)
dart:html: throws `WebSocketChannelException` (with text "WebSocket connection failed.") as expected. `channel.closeCode` is `null`. An error is sent to channel.stream and channel.stream is closed.
dart:io: throws `WebSocketException`, which cannot be caught without importing dart:io. `channel.closeCode` is `null`. No error is sent to channel.stream and channel.stream is not closed.

I think that define a common interface that is backwards compatible by ensuring that all exceptions are `WebSocketChannelException` but also implement the dart:io type e.g.

class IOWebSocketException
extends WebSocketChannelException // Usable on web and VM
implements WebSocketException { // Backwards compatible
}
channel.ready finishes with a WebSocketChannelException (that may be a subclass of WebSocketException/SocketException)

channel.stream is closed.
channel.sink.done is complete without error.
no error is sent to channel.stream
10
Different closeCode when underlying socket disconnected
await expectLater(channel.ready, completes);
// Server disconnects the socket immediately after establishing the connection.
expect(await channel.stream.isEmpty, true);
expect(channel.closeCode, 1006);

channel.sink.add('Hello World!');
await channel.sink.close();
dart:html: `channel.closeCode` is 1006. No error is sent on channel.stream. channel.sink.done is complete.
dart:io: `channel.closeCode` is 1005. No error is sent on channel.stream. channel.sink.done is not complete.
Allow close codes to be different.

channel.stream is closed.
channel.sink.done is complete without error.
no error is sent to channel.stream
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100