Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions app/sagas/__tests__/deepLinking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import createSagaMiddleware from 'redux-saga';
import { deepLinkingOpen } from '../../actions/deepLinking';
import { loginSuccess } from '../../actions/login';
import { selectServerSuccess } from '../../actions/server';
import { connectSuccess } from '../../actions/connect';
import { appStart } from '../../actions/app';
import { RootEnum } from '../../definitions';
import reducers from '../../reducers';
Expand Down Expand Up @@ -204,6 +205,11 @@ describe('deepLinking saga — Regression race (new server + token + room path)'
store.dispatch(selectServerSuccess({ ...makeServerRecord(), name: 'open.rocket.chat', server: HOST }));
await flushSagaMicrotasks();

// The saga waits for METEOR.SUCCESS ('connected') before dispatching
// loginRequest, so it never logs in on a still-connecting socket.
store.dispatch(connectSuccess());
await flushSagaMicrotasks();

// Saga is now waiting for LOGIN.SUCCESS
expect(jest.mocked(goRoom)).not.toHaveBeenCalled();

Expand Down Expand Up @@ -240,6 +246,11 @@ describe('deepLinking saga — Regression race (new server + token + room path)'
store.dispatch(selectServerSuccess({ ...makeServerRecord(), name: 'open.rocket.chat', server: HOST }));
await flushSagaMicrotasks();

// The saga waits for METEOR.SUCCESS ('connected') before dispatching
// loginRequest, so it never logs in on a still-connecting socket.
store.dispatch(connectSuccess());
await flushSagaMicrotasks();

store.dispatch(loginSuccess({ id: 'user-1', token: makeStoredUser() } as any));
await flushSagaMicrotasks();

Expand Down Expand Up @@ -272,6 +283,11 @@ describe('deepLinking saga — Regression race (new server + token + room path)'
store.dispatch(selectServerSuccess({ ...makeServerRecord(), name: 'open.rocket.chat', server: HOST }));
await flushSagaMicrotasks();

// The saga waits for METEOR.SUCCESS ('connected') before dispatching
// loginRequest, so it never logs in on a still-connecting socket.
store.dispatch(connectSuccess());
await flushSagaMicrotasks();

// Dispatch LOGIN.SUCCESS AND APP.START(ROOT_INSIDE) synchronously before any flush.
// The reducer processes both dispatches before the saga's select runs,
// so the select sees ROOT_INSIDE and skips the take.
Expand Down Expand Up @@ -301,6 +317,11 @@ describe('deepLinking saga — Regression race (new server + token + room path)'
store.dispatch(selectServerSuccess({ ...makeServerRecord(), name: 'open.rocket.chat', server: HOST }));
await flushSagaMicrotasks();

// The saga waits for METEOR.SUCCESS ('connected') before dispatching
// loginRequest, so it never logs in on a still-connecting socket.
store.dispatch(connectSuccess());
await flushSagaMicrotasks();

store.dispatch(loginSuccess({ id: 'user-1', token: makeStoredUser() } as any));
await flushSagaMicrotasks();

Expand Down Expand Up @@ -336,6 +357,11 @@ describe('deepLinking saga — Regression race (new server + token + room path)'
store.dispatch(selectServerSuccess({ ...makeServerRecord(), name: 'open.rocket.chat', server: HOST }));
await flushSagaMicrotasks();

// The saga waits for METEOR.SUCCESS ('connected') before dispatching
// loginRequest, so it never logs in on a still-connecting socket.
store.dispatch(connectSuccess());
await flushSagaMicrotasks();

store.dispatch(loginSuccess({ id: 'user-1', token: makeStoredUser() } as any));
await flushSagaMicrotasks();

Expand Down
11 changes: 11 additions & 0 deletions app/sagas/deepLinking.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ const handleOpen = function* handleOpen({ params }) {
if (params.token) {
if (!hostAlreadyConnected) {
yield take(types.SERVER.SELECT_SUCCESS);
// Wait for the socket to reach 'connected' (METEOR.SUCCESS) before logging in.
// Dispatching loginRequest while the socket is still CONNECTING makes the SDK
// login-guard open a second, orphaned socket whose later close clobbers Redux
// and falsely shows "Waiting for network". When the host is already connected we
// skip this — the socket is live, so there's no race (and METEOR.SUCCESS won't refire).
Comment thread
diegolmello marked this conversation as resolved.
Outdated
yield take(types.METEOR.SUCCESS);
}
yield put(loginRequest({ resume: params.token }, true));
yield take(types.LOGIN.SUCCESS);
Expand Down Expand Up @@ -332,6 +338,11 @@ const handleClickCallPush = function* handleClickCallPush({ params }) {
EventEmitter.emit('NewServer', { server: host });
if (params.token) {
yield take(types.SERVER.SELECT_SUCCESS);
// Wait for the socket to reach 'connected' (METEOR.SUCCESS) before logging in.
// Dispatching loginRequest while the socket is still CONNECTING makes the SDK
// login-guard open a second, orphaned socket whose later close clobbers Redux
// and falsely shows "Waiting for network".
Comment thread
diegolmello marked this conversation as resolved.
Outdated
yield take(types.METEOR.SUCCESS);
yield put(loginRequest({ resume: params.token }, true));
yield take(types.LOGIN.SUCCESS);
yield handleNavigateCallRoom({ params });
Expand Down
49 changes: 46 additions & 3 deletions patches/@rocket.chat+sdk+1.3.3-mobile.patch
Original file line number Diff line number Diff line change
@@ -1,8 +1,51 @@
diff --git a/node_modules/@rocket.chat/sdk/lib/drivers/ddp.ts b/node_modules/@rocket.chat/sdk/lib/drivers/ddp.ts
index 19d31ae..3f33982 100644
index 19d31ae..07bda5f 100644
--- a/node_modules/@rocket.chat/sdk/lib/drivers/ddp.ts
+++ b/node_modules/@rocket.chat/sdk/lib/drivers/ddp.ts
@@ -175,15 +175,115 @@ export class Socket extends EventEmitter {
@@ -101,9 +101,25 @@ export class Socket extends EventEmitter {
this.logger.error(err)
return reject(err)
}
+ // Tear down the previous connection before replacing it.
+ // The `this.connected` early-return above means we only reach here when the
+ // existing socket isn't healthy, so detaching its handlers and closing it stops
+ // a stale or still-connecting socket from later firing onClose and clobbering the
+ // live connection. Mirrors forceReopen's teardown.
+ if (this.connection) {
+ try {
+ this.connection.onopen = null as any
+ this.connection.onmessage = null as any
+ this.connection.onerror = null as any
+ this.connection.onclose = null as any
+ this.connection.close(userDisconnectCloseCode)
+ } catch (err) {
+ this.logger.debug(`[ddp] open: previous connection teardown failed: ${(err as Error).message}`)
+ }
+ }
this.connection = connection
this.connection.onmessage = this.onMessage.bind(this)
- this.connection.onclose = this.onClose.bind(this)
+ this.connection.onclose = (ev: any) => this.onClose(ev, connection) // pass closing socket so onClose can compare identity
this.connection.onopen = this.onOpen.bind(this, resolve)
this.emit('connecting')
})
@@ -125,7 +141,14 @@ export class Socket extends EventEmitter {
}

/** Emit close event so it can be used for promise resolve in close() */
- onClose = (e: any) => {
+ onClose = (e: any, closedConnection?: WebSocket) => {
+ // Ignore close events from a socket we've already replaced (an
+ // orphan). Only the current connection's close should flip app state or trigger a
+ // reopen; otherwise a zombie socket's late close clobbers the live connection and
+ // the app falsely shows "Waiting for network".
+ if (closedConnection && closedConnection !== this.connection) {
+ return
+ }
this.emit('close', e)
try {
if (e?.code !== userDisconnectCloseCode) {
@@ -175,15 +198,115 @@ export class Socket extends EventEmitter {
return Promise.resolve()
}

Expand Down Expand Up @@ -125,7 +168,7 @@ index 19d31ae..3f33982 100644
}

/** Clear connection and try to connect again. */
@@ -549,7 +649,9 @@ export class DDPDriver extends EventEmitter implements ISocket, IDriver {
@@ -549,7 +672,9 @@ export class DDPDriver extends EventEmitter implements ISocket, IDriver {
'uiInteraction',
'e2ekeyRequest',
'userData',
Expand Down
Loading