Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
18 changes: 2 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,6 @@
"default": "./dist/esm/index.mjs"
}
},
"browser": {
"crypto": false,
"https": false,
"jsonwebtoken": false,
"ws": false,
"zlib": false
},
"license": "SEE LICENSE IN LICENSE",
"keywords": [
"chat",
Expand All @@ -51,23 +44,16 @@
],
"dependencies": {
"@stream-io/logger": "^2.0.0",
"@types/jsonwebtoken": "^9.0.8",
"@types/ws": "^8.18.1",
"axios": "^1.16.1",
"base64-js": "^1.5.1",
"form-data": "^4.0.5",
"isomorphic-ws": "^5.0.0",
"jsonwebtoken": "^9.0.3",
"linkifyjs": "^4.3.3",
"ws": "^8.20.1"
"linkifyjs": "^4.3.3"
},
"devDependencies": {
"@commitlint/cli": "^21.0.1",
"@commitlint/config-conventional": "^21.0.1",
"@eslint/js": "^9.39.4",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@types/node": "^22.19.19",
"@types/node": "^22",
"@types/sinon": "^10.0.6",
"@vitest/coverage-v8": "^4.1.7",
"concurrently": "^9.2.1",
Expand Down
2 changes: 1 addition & 1 deletion scripts/bundle.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const modules = Object.keys({
// do not externalize modules that are ignored in browser field
// externalizing them will cause esbuild to not replace the imports
// in the bundles
const browserIgnoreModules = Object.keys(packageJson.browser);
const browserIgnoreModules = []; // Object.keys(packageJson.browser);
const browserExternal = modules.filter(
(module) => !browserIgnoreModules.includes(module),
);
Expand Down
3 changes: 1 addition & 2 deletions src/api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export class ApiClient {

sendFile(
url: string,
uri: string | NodeJS.ReadableStream | Buffer | File,
uri: string | File,
name?: string,
contentType?: string,
user?: UserResponse,
Expand All @@ -92,7 +92,6 @@ export class ApiClient {
if (user != null) data.append('user', JSON.stringify(user));

return this._doRequest<SendFileAPIResponse>('post', url, data, {
headers: data.getHeaders ? data.getHeaders() : {},
timeout: 0,
maxContentLength: Infinity,
maxBodyLength: Infinity,
Expand Down
78 changes: 0 additions & 78 deletions src/base64.ts

This file was deleted.

8 changes: 4 additions & 4 deletions src/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,13 +497,13 @@ export class Channel extends ChannelApi {
*
* @param uri - File source: URL string, `File`, `Buffer`, or readable stream (Node).
* @param name - File name sent in the multipart body (optional).
* @param contentType - MIME type; defaults are applied when omitted (optional).
* @param contentType - MIME type; required for React Native URI uploads (optional).
* @param user - User payload appended to the form as JSON (optional).
* @param axiosRequestConfig - Axios per-request config, merged after upload defaults, e.g. `onUploadProgress`, `signal` from `AbortController` (optional).
* @returns A promise resolving to `{ file: string, ... }` with the CDN URL.
*/
sendFile(
uri: string | NodeJS.ReadableStream | Buffer | File,
uri: string | File,
name?: string,
contentType?: string,
user?: UserResponse,
Expand All @@ -524,13 +524,13 @@ export class Channel extends ChannelApi {
*
* @param uri - Image source: URL string, `File`, or readable stream (Node). For `Buffer` uploads, use `sendFile` toward the channel file endpoint instead.
* @param name - File name sent in the multipart body (optional).
* @param contentType - MIME type (optional).
* @param contentType - MIME type; required for React Native URI uploads (optional).
* @param user - User payload appended to the form as JSON (optional).
* @param axiosRequestConfig - Axios per-request config, merged after upload defaults, e.g. `onUploadProgress`, `signal` (optional).
* @returns A promise resolving to `{ file: string, ... }` with the CDN URL.
*/
sendImage(
uri: string | NodeJS.ReadableStream | File,
uri: string | File,
name?: string,
contentType?: string,
user?: UserResponse,
Expand Down
16 changes: 6 additions & 10 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import type { AxiosInstance, AxiosRequestConfig } from 'axios';
import axios from 'axios';
import https from 'https';

import { Channel } from './channel';
import { ClientState } from './client_state';
Expand Down Expand Up @@ -159,7 +158,7 @@ export class StreamChat extends ChatApi {
axiosInstance: AxiosInstance;
baseURL?: string;
browser: boolean;
cleaningIntervalRef?: NodeJS.Timeout;
cleaningIntervalRef?: ReturnType<typeof setTimeout>;
clientId?: string;
key: string;
listeners: Map<EventType, Set<EventHandler>>;
Expand Down Expand Up @@ -248,7 +247,7 @@ export class StreamChat extends ChatApi {
* @param options.logLevel - Minimum log level for the default sink (optional, defaults to `'info'`).
* @param options.logOptions - Per-scope sink/level overrides for `chatLoggerSystem` (optional).
* @param options.timeout - Request timeout (optional, defaults to `3000`).
* @param options.httpsAgent - Custom `httpsAgent` (optional, in Node defaults to `https.agent()`).
* @param options.httpsAgent - Custom `httpsAgent` (optional).
*/
constructor(key: string, options: StreamChatOptions = {}) {
// generated client requires ApiClient right away
Expand Down Expand Up @@ -290,9 +289,6 @@ export class StreamChat extends ChatApi {
this.axiosInstance = axios.create({
timeout: 3000,
withCredentials: false,
httpsAgent: this.node
? new https.Agent({ keepAlive: true, keepAliveMsecs: 3000 })
: undefined,
...this.options.axiosRequestConfig,
paramsSerializer: axiosParamsSerializer,
});
Expand Down Expand Up @@ -2283,13 +2279,13 @@ export class StreamChat extends ChatApi {
*
* @param uri - The file to upload.
* @param name - The name of the file (optional).
* @param contentType - The content type of the file (optional).
* @param contentType - MIME type; required for React Native URI uploads (optional).
* @param user - User information (optional).
* @param axiosRequestConfig - Axios config, e.g. `onUploadProgress` for progress tracking (optional).
* @returns Response containing the file URL.
*/
uploadFile_(
uri: string | NodeJS.ReadableStream | Buffer | File,
uri: string | File,
name?: string,
contentType?: string,
user?: UserResponse,
Expand All @@ -2310,13 +2306,13 @@ export class StreamChat extends ChatApi {
*
* @param uri - The image to upload.
* @param name - The name of the image (optional).
* @param contentType - The content type of the image (optional).
* @param contentType - MIME type; required for React Native URI uploads (optional).
* @param user - User information (optional).
* @param axiosRequestConfig - Axios config, e.g. `onUploadProgress` for progress tracking (optional).
* @returns Response containing the image URL.
*/
uploadImage_(
uri: string | NodeJS.ReadableStream | File,
uri: string | File,
name?: string,
contentType?: string,
user?: UserResponse,
Expand Down
44 changes: 24 additions & 20 deletions src/connection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import WebSocket from 'isomorphic-ws';
import {
addConnectionEventListeners,
chatCodes,
Expand All @@ -24,12 +23,22 @@ const logger = chatLoggerSystem.getLogger('connection');

// Type guards to check WebSocket error type
const isCloseEvent = (
res: WebSocket.CloseEvent | WebSocket.Data | WebSocket.ErrorEvent,
): res is WebSocket.CloseEvent => (res as WebSocket.CloseEvent).code !== undefined;
res: CloseEvent | MessageEvent | ErrorEvent | Event,
): res is CloseEvent => (res as CloseEvent).code !== undefined;

const isErrorEvent = (
res: WebSocket.CloseEvent | WebSocket.Data | WebSocket.ErrorEvent,
): res is WebSocket.ErrorEvent => (res as WebSocket.ErrorEvent).error !== undefined;
res: CloseEvent | MessageEvent | ErrorEvent | Event,
): res is ErrorEvent => (res as ErrorEvent).error !== undefined;

class WSCloseError extends Error {
public reason?: string;
public wasClean?: boolean;
public code?: number;
public target?: EventTarget | null;
constructor(message?: string, errorOptions?: ErrorOptions) {
super(message, errorOptions);
}
}

/**
* A WS connection that reconnects upon failure.
Expand Down Expand Up @@ -252,19 +261,14 @@ export class StableWSConnection {

this.isHealthy = false;

// remove ws handlers...
if (this.ws && this.ws.removeAllListeners) {
this.ws.removeAllListeners();
}

let isClosedPromise: Promise<void>;
// and finally close...
// Assigning to local here because we will remove it from this before the
// promise resolves.
const { ws } = this;
if (ws && ws.close && ws.readyState === ws.OPEN) {
isClosedPromise = new Promise((resolve) => {
const onclose = (event: WebSocket.CloseEvent) => {
const onclose = (event: CloseEvent) => {
logger
.withExtraTags('disconnect')
.debug(
Expand Down Expand Up @@ -337,7 +341,10 @@ export class StableWSConnection {
wsURL,
requestID: this.requestID,
});
this.ws = new WebSocket(wsURL);

const WS = this.client.options.WebSocketImpl ?? WebSocket;
this.ws = new WS(wsURL);
Comment on lines +345 to +346

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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


this.ws.onopen = this.onopen.bind(this, this.wsID);
this.ws.onclose = this.onclose.bind(this, this.wsID);
this.ws.onerror = this.onerror.bind(this, this.wsID);
Expand Down Expand Up @@ -503,7 +510,7 @@ export class StableWSConnection {
});
};

onmessage = (wsId: number, event: WebSocket.MessageEvent) => {
onmessage = (wsId: number, event: MessageEvent) => {
if (this.wsID !== wsId) return;

logger.withExtraTags('onmessage').trace('WebSocket onmessage callback fired.', {
Expand Down Expand Up @@ -539,7 +546,7 @@ export class StableWSConnection {
this.scheduleConnectionCheck();
};

onclose = (wsId: number, event: WebSocket.CloseEvent) => {
onclose = (wsId: number, event: CloseEvent) => {
if (this.wsID !== wsId) return;

logger
Expand All @@ -552,9 +559,7 @@ export class StableWSConnection {
if (event.code === chatCodes.WS_CLOSED_SUCCESS) {
// this is a permanent error raised by stream..
// usually caused by invalid auth details
const error = new Error(
`WS connection reject with error ${event.reason}`,
) as Error & WebSocket.CloseEvent;
const error = new WSCloseError(`WS connection reject with error ${event.reason}`);

error.reason = event.reason;
error.code = event.code;
Expand Down Expand Up @@ -584,7 +589,7 @@ export class StableWSConnection {
}
};

onerror = (wsId: number, event: WebSocket.ErrorEvent) => {
onerror = (wsId: number, event: Event) => {
if (this.wsID !== wsId) return;

this.consecutiveFailures += 1;
Expand Down Expand Up @@ -630,7 +635,7 @@ export class StableWSConnection {
* @returns A normalized error describing the WS failure.
*/
_errorFromWSEvent = (
event: WebSocket.CloseEvent | WebSocket.Data | WebSocket.ErrorEvent,
event: CloseEvent | MessageEvent | ErrorEvent | Event,
isWSFailure = true,
) => {
let code;
Expand Down Expand Up @@ -679,7 +684,6 @@ export class StableWSConnection {
this.wsID += 1;

try {
this?.ws?.removeAllListeners();
this?.ws?.close();
} catch (e) {
// we don't care
Expand Down
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './base64';
export * from './client';
export * from './client_state';
export * from './channel';
Expand Down
Loading