Conversation
…ng after a token load Two gaps in StableWSConnection, both reachable through closeConnection() while the token provider is still loading: - _connect() never re-checked isDisconnected after awaiting the token, so a socket was still built after disconnect(). It carried the current wsID, so its callbacks were live and it published a connection id and events behind the close. - _reconnect() called _settleConnectPromises() even when _connect() returned early, resolving setUserPromise for a connection that was never made. That defeats connectUser's check that its own attempt is still the current one.
Restores v9's WSConnectionFallback at src/connection_fallback.ts and the enableWSFallback client option, with v9's switching logic back in client.connect(). The class changes only where v10 removed what it relied on: it writes client.wsConnection.state instead of dispatching connection.changed, drives ConnectionIdManager, follows client.networkConnection instead of window events, polls /api/v2/longpoll, and sends connection_id in the URL so the request layer's connection-id gate does not hold its polls and close.
| // hosts without a network API mirrors the WebSocket, so its "offline" only means the socket | ||
| // is down, and is ignored. | ||
| const { networkConnection } = this.client; | ||
| const isDeviceOffline = |
There was a problem hiding this comment.
We actually cannot tell reliably if the device is offline if using custom reporter or the default WS reporter. In that case long-poll would be applied only in browsers and not RN (which relies on WS based reporter)?
There was a problem hiding this comment.
We never rely on default WS reporter here, exactly because that is not reliable. We trust custom reporter, the reasoning for that is in the above comment.
There was a problem hiding this comment.
But that means that the RN SDK will not use WSConnectionFallback unless custom reporter is provided. Is that correct?
There was a problem hiding this comment.
No, if the default reporer is used in RN, we will fallback, exactly because we ignore the unreliable offline signal from the default reporter.
RN SDK itself installs a custom reporter, and fallback works with that one too.
| }) | ||
| | { type: 'connection.recovered' } | ||
| // `enableWSFallback` switched from the WebSocket to long-polling. | ||
| | ({ type: 'transport.changed' } & { mode: string }) |
There was a problem hiding this comment.
I think that 'connection.recovered' and 'transport.changed' belong to the same family / group of events meaning related to the WS connection (or just connection). I would probably namespace them the same way so that it is clear they belong together. Word 'transport' sounds ok, but could also be misleading if standing alone referring to any kind of transport protocol not related to maintaining the real-time connection.
There was a problem hiding this comment.
This is the same name brought back from v9, I'm not really sure it makes the breaking change to rename it, but if you want to do this and have a name in mind, I can rename it
There was a problem hiding this comment.
What about 'connection.fallback'. WDYT @szuperaz and @isekovanic ? Even though we are bringing something back, we have a unique opportunity to make it better.
There was a problem hiding this comment.
We seem to be using verbs in the past tense here, so in the end used connection.fallback_activated
| const baseURL = | ||
| resolved === LONG_POLL_PATH | ||
| ? // replace port if present for testing with local API | ||
| this.client.baseURL?.replace(':3030', ':8900') |
There was a problem hiding this comment.
Why writing code for test purposes? Is there a purpose beyond fitting the test suite expectations?
There was a problem hiding this comment.
We have the same thing for WS URL too: https://github.com/GetStream/stream-chat-js/blob/release-v10/src/client.ts#L516 - we need these swaps to run the SDK with a local backend. Both are existing concepts in master too.
There was a problem hiding this comment.
Anyways looks smelly to hard-code this stuff. I think this belongs to the config service rather than hard-code some ports in the SDK code.
There was a problem hiding this comment.
Yeah, we can do that, but then you have config params just for testing with local backend, because integrators would never need to set this. In any case, I don't think this PR is related. We can track it in Linear if we want to, but I don't think we should fix it here.
| * | ||
| * @internal | ||
| */ | ||
| disconnect = async (timeout = 2000, connectionId?: string) => { |
There was a problem hiding this comment.
Do we need to pass the connectionId if we have access to the connectionIdManager? We already access the connectionIdManager here for example: https://github.com/GetStream/stream-chat-js/pull/1889/changes#diff-de6aeb8ccbca31560cf64bbc3f76666ba4dfbf4786de3368cda54d90987269f3R76
There was a problem hiding this comment.
We pass because connection id is wiped as soon as disconnect starts. But we need the connection id to be able to send close request. WS doesn't have the same issue.
Since connection id is reset by client we make this connection explicit by providing this as a method param, instead of relying only on client calling the method after wsFallback doesn't need it anymore, which would be implicit.
Breaking changes
transport.changedevent renamed toconnection.fallback_activatedhttps://linear.app/stream/issue/REACT-1181/reenable-ws-fallback
Reintroduce long-poll fallback if WS connection fails (and fallback is enabled).
How WS fallback works?
connection_idconnection.okeven if that was successful. If an error happens that can be retired, we set state to unhealthy, and wait for an "online" event to reconnect. Since the fallback sets theclient.wsConnect.isHealthyflag, the recovery manager can kickstart a state recovery when necessary, so it should work the same way as it does for regular WS.Implementation details
The main goal was to be as close to v9 as possible, Claude flagged a lot of potential bugs with ws fallback implementation, none of them is fixed on purpose to avoid unnecessary changes. Any change comes from adapting to v10's logic:
enableWSFallback-> moved to config service instead of ctr paramenableWSFallback: true, this is not there in v10, integrators can use theconnectTimeoutMsparam to lower the default 15secs if they want a shorter trial periodisHealthyon theclient.wsConnectionclass, just like on v9 the fallback took WS's place to report thisChanges on this branch that are not fallback related:
StableWSConnectionwe abort an in-progress connection if disconnect was called while we were waiting on the token -> this potentially could've opened a WS connection after disconnect (in case of longpoll it would have meant receiving events on WS and long poll too).disconnectUserwill not resettokenManagerif a new connection was started while it waited for WS to close. This is a preexisting bug, nothing to do with this PR, but since Claude likes to report it anytime we have any WS-related change, it's fixed here finally.ApiClientno longer waits forconnectionIdon requests whereconnection_idis part of the API request, but nowatch/presenceflag is set. This only affectsstopWatching, connection id is now waited inclientinstead ofApiClient. Not a customer-facing change. The reason: forlongPollwe have to be able to send the connection request withoutconnection_idset.ApiClientwaits for in-progress tokens -> not related to this PR, but noticed while testing it, it seems waiting for the token wasn't ported, it doesn't cause issues when you wait for WS connection anyway, so that's why we missed it all this time (same in v9)