Skip to content
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

LongPoll #814

Merged
merged 55 commits into from
Dec 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
c9d4f52
set received_at in dispatchEvent
mahboubii Nov 22, 2021
b54d329
break _buildUrl
mahboubii Nov 22, 2021
ba9d5ec
first draft of ws poll
mahboubii Nov 22, 2021
e935fb7
fix tests
mahboubii Nov 22, 2021
8e4c629
Merge branch 'master' into longpoll
mahboubii Nov 24, 2021
50a56d6
refactor connection state
mahboubii Nov 24, 2021
4099feb
APIErrorCodes
mahboubii Nov 24, 2021
428c373
Merge branch 'master' into longpoll
mahboubii Nov 24, 2021
a32ac24
remove temp fallback
mahboubii Nov 24, 2021
c379c33
Merge branch 'master' into longpoll
mahboubii Nov 26, 2021
59449c9
fix wrong err code
mahboubii Nov 26, 2021
8b2b539
add ConnectionIDNotFoundError
mahboubii Nov 26, 2021
6a10060
_setConnectionID
mahboubii Nov 26, 2021
7a5c799
reset longpoll on CONNECTION_ID_ERROR
mahboubii Nov 26, 2021
eb1cb86
rm space
mahboubii Nov 29, 2021
dc2b0a7
rate limit
mahboubii Nov 29, 2021
7df789f
refactor _buildWSPayload
mahboubii Nov 29, 2021
3926056
_getConnectionID
mahboubii Nov 29, 2021
7818ee1
waitForHealthy timeout from connect
mahboubii Nov 29, 2021
3c2f056
clean fallback from WS class
mahboubii Nov 29, 2021
564aefb
fallback isHealthy
mahboubii Nov 29, 2021
de7e384
client run fallback ws
mahboubii Nov 29, 2021
ac77ddc
Merge branch 'master' into longpoll
Nov 29, 2021
8adcad9
close longpoll properly
mahboubii Nov 29, 2021
ac4fe43
delete errors.ts
mahboubii Nov 29, 2021
cb72f7e
client use fallback if WS error
mahboubii Nov 29, 2021
e8ef9e3
setLocalDevice check for wsfallback
mahboubii Nov 29, 2021
7035fd7
connection isDisconnected flag
mahboubii Nov 29, 2021
2dd8dfa
closeConnection promise
mahboubii Nov 29, 2021
b878e70
fix tests
mahboubii Nov 29, 2021
8dd4169
set this.isConnecting to false on err
mahboubii Nov 29, 2021
813a231
Merge branch 'master' into longpoll
mahboubii Nov 29, 2021
afaa2c4
reset connection id on connect
mahboubii Nov 30, 2021
ac909ab
refactor errors
mahboubii Nov 30, 2021
4268d46
retry logic
mahboubii Nov 30, 2021
e8462da
replace local port
mahboubii Nov 30, 2021
8d66397
typo
mahboubii Nov 30, 2021
db18d0a
Merge branch 'master' into longpoll
Nov 30, 2021
6d9ff79
poll sleep on err
mahboubii Nov 30, 2021
88c54b9
fix multiple recover call
mahboubii Nov 30, 2021
6e586b0
disconnect in case of non retryable errors
mahboubii Nov 30, 2021
08e8429
fallback recover state
mahboubii Nov 30, 2021
fd558ab
enableWSFallback flag warning
mahboubii Nov 30, 2021
f5a943c
check browser is online before longpoll
mahboubii Nov 30, 2021
9021139
refactor addConnectionEventListeners
mahboubii Nov 30, 2021
020a090
connection.changed events
mahboubii Dec 1, 2021
9cc7b2d
fallback logger
mahboubii Dec 1, 2021
13c95b9
removed todo
mahboubii Dec 1, 2021
840cfbd
fix event condition
mahboubii Dec 1, 2021
7c61d87
4.5.0-beta.0
mahboubii Dec 1, 2021
7b4ac06
warn for missing navigation
mahboubii Dec 1, 2021
8a989b5
fix APIError type
mahboubii Dec 1, 2021
9dbafe3
fix isOnline
mahboubii Dec 2, 2021
8229b7d
export ConnectionState
mahboubii Dec 2, 2021
a7ee9ae
test client
mahboubii Dec 2, 2021
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
1 change: 0 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
"babel/no-invalid-this": 2,
"array-callback-return": 2,
"valid-typeof": 2,
"arrow-body-style": 2,
"react/prop-types": 0,
"no-var": 2,
"linebreak-style": [2, "unix"],
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "stream-chat",
"version": "4.4.3",
"version": "4.5.0-beta.0",
"description": "JS SDK for the Stream Chat API",
"author": "GetStream",
"homepage": "https://getstream.io/chat/",
Expand Down
97 changes: 76 additions & 21 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { StableWSConnection } from './connection';
import { isValidEventType } from './events';
import { JWTUserToken, DevToken, CheckSignature } from './signing';
import { TokenManager } from './token_manager';
import { WSConnectionFallback } from './connection_fallback';
import { isWSFailure } from './errors';
import {
isFunction,
isOwnUserBaseProperty,
Expand All @@ -20,6 +22,7 @@ import {
randomId,
sleep,
retryInterval,
isOnline,
} from './utils';

import {
Expand Down Expand Up @@ -146,7 +149,6 @@ export class StreamChat<
cleaningIntervalRef?: NodeJS.Timeout;
clientID?: string;
configs: Configs<CommandType>;
connectionID?: string;
key: string;
listeners: {
[key: string]: Array<
Expand Down Expand Up @@ -185,9 +187,20 @@ export class StreamChat<
MessageType,
ReactionType
> | null;
wsFallback?: WSConnectionFallback<
AttachmentType,
ChannelType,
CommandType,
EventType,
MessageType,
ReactionType,
UserType
>;
wsPromise: ConnectAPIResponse<ChannelType, CommandType, UserType> | null;
consecutiveFailures: number;
insightMetrics: InsightMetrics;
defaultWSTimeoutWithFallback: number;
defaultWSTimeout: number;

/**
* Initialize a client
Expand Down Expand Up @@ -273,6 +286,9 @@ export class StreamChat<
this.consecutiveFailures = 0;
this.insightMetrics = new InsightMetrics();

this.defaultWSTimeoutWithFallback = 6000;
this.defaultWSTimeout = 15000;

/**
* logger function should accept 3 parameters:
* @param logLevel string
Expand Down Expand Up @@ -433,7 +449,9 @@ export class StreamChat<
this.wsBaseURL = this.baseURL.replace('http', 'ws').replace(':3030', ':8800');
}

_hasConnectionID = () => Boolean(this.wsConnection?.connectionID);
_getConnectionID = () => this.wsConnection?.connectionID || this.wsFallback?.connectionID;

_hasConnectionID = () => Boolean(this._getConnectionID());

/**
* connectUser - Set the current user and open a WebSocket connection
Expand Down Expand Up @@ -534,17 +552,14 @@ export class StreamChat<
* @param timeout Max number of ms, to wait for close event of websocket, before forcefully assuming succesful disconnection.
* https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
*/
closeConnection = (timeout?: number) => {
closeConnection = async (timeout?: number) => {
if (this.cleaningIntervalRef != null) {
clearInterval(this.cleaningIntervalRef);
this.cleaningIntervalRef = undefined;
}

if (!this.wsConnection) {
return Promise.resolve();
}

return this.wsConnection.disconnect(timeout);
await Promise.all([this.wsConnection?.disconnect(timeout), this.wsFallback?.disconnect(timeout)]);
return Promise.resolve();
};

/**
Expand All @@ -555,7 +570,7 @@ export class StreamChat<
throw Error('User is not set on client, use client.connectUser or client.connectAnonymousUser instead');
}

if (this.wsConnection?.isHealthy && this._hasConnectionID()) {
if ((this.wsConnection?.isHealthy || this.wsFallback?.isHealthy()) && this._hasConnectionID()) {
this.logger('info', 'client:openConnection() - openConnection called twice, healthy connection already exists', {
tags: ['connection', 'client'],
});
Expand Down Expand Up @@ -736,7 +751,7 @@ export class StreamChat<
// reset client state
this.state = new ClientState();
// reset token manager
this.tokenManager.reset();
setTimeout(this.tokenManager.reset); // delay reseting to use token for disconnect calls
mahboubii marked this conversation as resolved.
Show resolved Hide resolved

// close the WS connection
return closePromise;
Expand Down Expand Up @@ -1023,6 +1038,7 @@ export class StreamChat<
this._logApiError(type, url, e);
this.consecutiveFailures += 1;
if (e.response) {
/** connection_fallback depends on this token expiration logic */
if (e.response.data.code === chatCodes.TOKEN_EXPIRED && !this.tokenManager.isStatic()) {
if (this.consecutiveFailures > 1) {
await sleep(retryInterval(this.consecutiveFailures));
Expand Down Expand Up @@ -1100,6 +1116,8 @@ export class StreamChat<
dispatchEvent = (
event: Event<AttachmentType, ChannelType, CommandType, EventType, MessageType, ReactionType, UserType>,
) => {
if (!event.received_at) event.received_at = new Date();

// client event handlers
const postListenerCallbacks = this._handleClientEvent(event);

Expand Down Expand Up @@ -1131,7 +1149,6 @@ export class StreamChat<
ReactionType,
UserType
>;
event.received_at = new Date();
this.dispatchEvent(event);
};

Expand Down Expand Up @@ -1370,13 +1387,9 @@ export class StreamChat<
};

recoverState = async () => {
this.logger(
'info',
`client:recoverState() - Start of recoverState with connectionID ${this.wsConnection?.connectionID}`,
{
tags: ['connection'],
},
);
this.logger('info', `client:recoverState() - Start of recoverState with connectionID ${this._getConnectionID()}`, {
tags: ['connection'],
});

const cids = Object.keys(this.activeChannels);
if (cids.length && this.recoverStateOnReconnect) {
Expand Down Expand Up @@ -1436,7 +1449,33 @@ export class StreamChat<
ReactionType
>({ client: this });

return await this.wsConnection.connect();
try {
// if WSFallback is enabled, ws connect should timeout faster so fallback can try
return await this.wsConnection.connect(
this.options.enableWSFallback ? this.defaultWSTimeoutWithFallback : this.defaultWSTimeout,
);
} catch (err) {
// run fallback only if it's WS/Network error and not a normal API error
// make sure browser is online before even trying the longpoll
if (this.options.enableWSFallback && isWSFailure(err) && isOnline()) {
mahboubii marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we shouldn't remove thhe online check. If you're offline the this.wsConnection.connect fails and isWsFailure will return false anyways. On the other hand, if it could be the case that you have a wsFailure and you're offline, in RN this check wouldn't work, and we would trigger the fallback while offline. I'm not sure if I'm missing something, though. Does it make sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we did a bad job with naming here but isWSFailure here returns true if you are offline, basically, isWSFailure is in contrast with isAPIFailure. isWSFailure is true when we couldn't reach API for any reason, including network issue

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following up on this: so I just tested on RN. If you access window.navigator it looks like following:

{"product": "ReactNative"}

And thus isOnline() function will return always undefined -> fallback will never be executed. Maybe we can provide a way so that RN SDK can let JS client know about the online status (e.g., we can provide js client with async function to fetch online status).

this.logger('info', 'client:connect() - WS failed, fallback to longpoll', { tags: ['connection', 'client'] });

this.wsConnection._destroyCurrentWSConnection();
this.wsConnection.disconnect().then(); // close WS so no retry
mahboubii marked this conversation as resolved.
Show resolved Hide resolved
this.wsFallback = new WSConnectionFallback<
AttachmentType,
ChannelType,
CommandType,
EventType,
MessageType,
ReactionType,
UserType
>({ client: this });
return await this.wsFallback.connect();
}

throw err;
}
}

/**
Expand Down Expand Up @@ -1672,7 +1711,7 @@ export class StreamChat<
*
*/
setLocalDevice(device: BaseDeviceFields) {
if (this.wsConnection) {
if (this.wsConnection || this.wsFallback) {
throw new Error('you can only set device before opening a websocket connection');
}

Expand Down Expand Up @@ -2540,7 +2579,7 @@ export class StreamChat<
user_id: this.userID,
...options.params,
api_key: this.key,
connection_id: this.wsConnection?.connectionID,
connection_id: this._getConnectionID(),
},
headers: {
...authorization,
Expand Down Expand Up @@ -2571,6 +2610,22 @@ export class StreamChat<
}, 500);
}

/**
* encode ws url payload
* @private
* @returns json string
*/
_buildWSPayload = (client_request_id?: string) => {
return JSON.stringify({
user_id: this.userID,
user_details: this._user,
user_token: this.tokenManager.getToken(),
server_determines_connection_id: true,
mahboubii marked this conversation as resolved.
Show resolved Hide resolved
device: this.options.device,
client_request_id,
});
};

verifyWebhook(requestBody: string, xSignature: string) {
return !!this.secret && CheckSignature(requestBody, this.secret, xSignature);
}
Expand Down
Loading