Skip to content
Merged
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
541 changes: 0 additions & 541 deletions .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs

This file was deleted.

9 changes: 0 additions & 9 deletions .yarn/plugins/@yarnpkg/plugin-typescript.cjs

This file was deleted.

28 changes: 0 additions & 28 deletions .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs

This file was deleted.

873 changes: 0 additions & 873 deletions .yarn/releases/yarn-3.5.0.cjs

This file was deleted.

942 changes: 942 additions & 0 deletions .yarn/releases/yarn-4.12.0.cjs

Large diffs are not rendered by default.

20 changes: 11 additions & 9 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
catalog:
"@types/react": ~19.1.0
"@types/react-dom": ~19.1.0
react: 19.1.0
react-dom: 19.1.0

checksumBehavior: update

compressionLevel: mixed

enableGlobalCache: false

initScope: stream-io

nodeLinker: node-modules

plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
spec: "@yarnpkg/plugin-workspace-tools"
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: "@yarnpkg/plugin-typescript"

yarnPath: .yarn/releases/yarn-3.5.0.cjs
yarnPath: .yarn/releases/yarn-4.12.0.cjs
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,26 @@
"name": "@stream-io/feeds-js-root",
"description": "Stream Feeds JS SDKs",
"author": "https://getstream.io",
"packageManager": "yarn@3.5.0",
"packageManager": "yarn@4.12.0",
"private": true,
"license": "See license in LICENSE",
"workspaces": [
"packages/*",
"sample-apps/**"
],
"scripts": {
"build:all": "yarn workspaces foreach -v --topological-dev run build",
"build:libs": "yarn workspaces foreach -v --topological-dev --no-private run build",
"build:all": "yarn workspaces foreach -Avp --topological-dev run build",
"build:libs": "yarn workspaces foreach -Avp --topological-dev --no-private run build",
"build:client": "yarn workspace @stream-io/feeds-client run build",
"build:react-native-sdk": "yarn workspace @stream-io/feeds-react-native-sdk run build",
"build:react-sdk": "yarn workspace @stream-io/feeds-react-sdk run build",
"build:react-tutorial": "yarn build:client && yarn build:react-sdk && yarn workspace stream-feeds-react-tutorial run build",
"clean:all": "yarn workspaces foreach -vt run clean",
"build:react-tutorial": "yarn workspaces foreach -Avp --topological-dev --include '{packages/{feeds-client,react-sdk},sample-apps/react-tutorial}' run build",
"clean:all": "yarn workspaces foreach -Avp run clean",
"lint:all": "eslint --max-warnings=0 --cache '**/*.{ts,tsx}'",
"lint:all:fix": "eslint --max-warnings=0 '**/*.{ts,tsx}' --fix",
"test:ci:all": "yarn workspaces foreach -vt run test-ci",
"test:ci:libs": "yarn workspaces foreach -vt --no-private run test-ci",
"test:docs-snippets": "yarn workspaces foreach -vt --no-private run test-docs-snippets",
"test:ci:all": "yarn workspaces foreach -Avt run test-ci",
"test:ci:libs": "yarn workspaces foreach -Avt --no-private run test-ci",
"test:docs-snippets": "yarn workspaces foreach -Avt --no-private run test-docs-snippets",
"generate": "./generate-openapi.sh",
"lint:gen": "eslint --fix packages/**/src/gen/**/*ts && prettier packages/**/src/gen/**/*ts --write",
"release": "nx run-many --target version --parallel=1",
Expand Down
21 changes: 10 additions & 11 deletions packages/feeds-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"packageManager": "[email protected]",
"main": "./dist/cjs/index.js",
"module": "./dist/es/index.mjs",
"types": "./dist/types/index.d.ts",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
Expand All @@ -13,7 +14,7 @@
},
"import": "./dist/es/index.mjs",
"require": "./dist/cjs/index.js",
"default": "./dist/es/index.mjs"
"default": "./dist/cjs/index.js"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've changed these to default to CommonJS, it's safer.

},
"./react-bindings": {
"types": "./dist/types/bindings/react/index.d.ts",
Expand All @@ -23,15 +24,14 @@
},
"import": "./dist/es/react-bindings.mjs",
"require": "./dist/cjs/react-bindings.js",
"default": "./dist/es/react-bindings.mjs"
"default": "./dist/cjs/react-bindings.js"
}
},
"types": "./dist/types/index.d.ts",
"license": "See license in LICENSE",
"scripts": {
"clean": "rimraf dist",
"start": "vite build --watch",
"build": "vite build",
"start": "yarn clean; concurrently 'vite build --watch' 'tsc -p tsconfig.lib.json --watch'",
"build": "yarn clean; concurrently 'vite build' 'tsc -p tsconfig.lib.json'",
"test": "vitest",
"test:unit": "vitest --exclude '__integration-tests__/**'",
"test-ci": "vitest --exclude '__integration-tests__/docs-snippets/**' --coverage",
Expand All @@ -56,19 +56,18 @@
},
"devDependencies": {
"@stream-io/node-sdk": "0.6.0",
"@types/react": "^19.1.8",
"@types/react": "catalog:",
"@vitest/coverage-v8": "3.2.4",
"concurrently": "^9.2.1",
"dotenv": "^16.4.5",
"human-id": "^4.1.1",
"react": "19.0.0",
"rimraf": "^6.0.1",
"typescript": "^5.8.3",
"vite": "^7.1.11",
"vite-plugin-dts": "^4.5.4",
"typescript": "^5.9.3",
"vite": "^7",
"vitest": "^3.2.4"
},
"peerDependencies": {
"react": "^18 || ^19"
"react": "^17 || ^18 || ^19"
},
"peerDependenciesMeta": {
"react": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
disconnectActivityFromFeed,
isAnyFeedWatched,
} from '../feeds-client/active-activity';
import type { GetCommentsRequest } from '@self';
import type { GetCommentsRequest } from '../types';
import { deepEqual } from '../utils/deep-equal';

type GetActivityConfig = {
Expand Down Expand Up @@ -164,7 +164,7 @@ export class ActivityWithStateUpdates {
}) {
this.feed = connectActivityToFeed.call(this.feedsClient, { fid });

this.feed.state.partialNext({
this.feed?.state.partialNext({
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Find the activity-with-state-updates.ts file
fd -t f "activity-with-state-updates.ts" 

# List files in packages/feeds-client/src to confirm directory structure
fd -t f -e ts "." "packages/feeds-client/src" | head -20

Repository: GetStream/stream-feeds-js

Length of output: 1799


🏁 Script executed:

#!/bin/bash

# Get the exact file path
find . -name "activity-with-state-updates.ts" -type f

# Read the file
if [ -f "packages/feeds-client/src/activity-with-state-updates/activity-with-state-updates.ts" ]; then
  cat -n "packages/feeds-client/src/activity-with-state-updates/activity-with-state-updates.ts"
fi

Repository: GetStream/stream-feeds-js

Length of output: 6941


🏁 Script executed:

#!/bin/bash
# Find and examine connectActivityToFeed implementation
fd -t f "active-activity.ts" "packages/feeds-client/src"

# Read the file
find . -name "active-activity.ts" -type f | xargs cat -n

Repository: GetStream/stream-feeds-js

Length of output: 1275


🏁 Script executed:

#!/bin/bash
# Check if empty feeds arrays are ever valid/expected
rg -n "feeds.*\[\]|feeds\.length.*===.*0" packages/feeds-client/src --type ts -A 2 -B 2

# Check how activityResponse.feeds is used elsewhere
rg -n "\.feeds\[0\]|\.feeds\?" packages/feeds-client/src --type ts -B 2 -A 2

# Check if there are tests that cover empty feeds scenarios
fd -t f "\.test\.ts$" packages/feeds-client/src | xargs grep -l "feeds.*\[\]|empty.*feeds" 2>/dev/null

# Search for activity response type definition
fd -t f "models.*\.(ts|d\.ts)$" packages/feeds-client/src | head -5

Repository: GetStream/stream-feeds-js

Length of output: 10974


🏁 Script executed:

#!/bin/bash
# Check the ActivityResponse type definition more carefully
grep -A 10 "export interface ActivityResponse" packages/feeds-client/src/gen/models/index.ts

# Check if there are any tests that validate the empty feeds scenario
grep -r "feeds.*\[\]" packages/feeds-client/src --include="*.test.ts" -B 3 -A 3

# Check if there's validation in getActivity or any API handlers
grep -n "feeds" packages/feeds-client/src/feeds-client/feeds-client.ts | head -20

Repository: GetStream/stream-feeds-js

Length of output: 5536


🏁 Script executed:

#!/bin/bash
# Check if there's any validation or error handling around the feeds array access
grep -B 5 -A 10 "feeds\[0\]" packages/feeds-client/src/activity-with-state-updates/activity-with-state-updates.ts

# Check test file to see if there are tests for empty feeds scenarios
cat packages/feeds-client/src/activity-with-state-updates/activity-with-state-updates.test.ts | head -100

Repository: GetStream/stream-feeds-js

Length of output: 3862


🏁 Script executed:

#!/bin/bash
# Let's check if there's any type safety that prevents undefined from being passed
grep -B 5 -A 10 "fid: string" packages/feeds-client/src/feeds-client/active-activity.ts

# Check if there's any validation in setFeed or error handling
grep -B 10 -A 15 "private setFeed" packages/feeds-client/src/activity-with-state-updates/activity-with-state-updates.ts

# Let's verify what happens when we split undefined
node -e "console.log(undefined.split(':'))" 2>&1 || echo "Error as expected"

# Check if there's any validation that ensures feeds is not empty
rg "feeds.length|feeds\?" packages/feeds-client/src/test-utils/response-generators.ts -A 2 -B 2

Repository: GetStream/stream-feeds-js

Length of output: 1494


Accessing feeds[0] without bounds checking can cause a runtime crash.

If activityResponse.feeds is empty, feeds[0] is undefined. Passing undefined as fid to connectActivityToFeed will crash when attempting to split it (line 12 in active-activity.ts calls fid.split(':')), not silently fail as the optional chaining might suggest.

Add validation to ensure activityResponse.feeds is non-empty before calling setFeed:

if (!activityResponse.feeds.length) {
  throw new Error('Activity must belong to at least one feed');
}

this.setFeed({
  fid: activityResponse.feeds[0],
  initialState: activityResponse,
});
🤖 Prompt for AI Agents
In
packages/feeds-client/src/activity-with-state-updates/activity-with-state-updates.ts
around line 167, the code calls this.setFeed using activityResponse.feeds[0]
without verifying feeds is non-empty which can pass undefined into
connectActivityToFeed and crash when split is called; add a guard that checks
activityResponse.feeds exists and has length > 0, and if not throw a clear
Error('Activity must belong to at least one feed') (or otherwise handle the
empty case), then call this.setFeed with fid: activityResponse.feeds[0] and
initialState: activityResponse; ensure the check satisfies TypeScript narrowing
so the first element is typed as string.

activities: [initialState],
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createContext, useContext } from 'react';
import type { Feed } from '@self';

import type { Feed } from '../../../feed';

export const StreamFeedContext = createContext<Feed | undefined>(undefined);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createContext, useContext } from 'react';
import type { FeedsClient } from '@self';

import type { FeedsClient } from '../../../feeds-client';

export const StreamFeedsContext = createContext<FeedsClient | undefined>(undefined);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createContext, useContext } from 'react';
import type { SearchController } from '@self';

import type { SearchController } from '../../../common/search';

export const StreamSearchContext = createContext<SearchController | undefined>(undefined);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createContext, useContext } from 'react';
import type { SearchSource } from '@self';

import type { SearchSource } from '../../../common/search';

export const StreamSearchResultsContext = createContext<
SearchSource | undefined
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { FeedsClientState } from '@self';
import { useStateStore } from '@stream-io/state-store/react-bindings';

import { useFeedsClient } from '../../contexts/StreamFeedsContext';
import type { FeedsClientState } from '../../../../feeds-client';

/**
* A React hook that returns the currently connected user on a `FeedsClient` instance and null otherwise.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { FeedsClientState } from '@self';
import { useStateStore } from '@stream-io/state-store/react-bindings';

import { useFeedsClient } from '../../contexts/StreamFeedsContext';
import type { FeedsClientState } from '../../../../feeds-client';

/**
* A React hook that returns the websocket connection state of `FeedsClient`.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import { useCallback, useMemo } from 'react';
import {
type ActivityResponse,
type CommentResponse,
type Feed,
type FeedState,
checkHasAnotherPage,
type ActivityWithStateUpdates,
type ActivityState,
type StateStore,
} from '@self';
import { useFeedContext } from '../../contexts/StreamFeedContext';
import { useStateStore } from '@stream-io/state-store/react-bindings';
import type { StateStore } from '@stream-io/state-store';

import { useFeedContext } from '../../contexts/StreamFeedContext';
import { checkHasAnotherPage } from '../../../../utils';
import type { Feed, FeedState } from '../../../../feed';
import type { ActivityState, ActivityWithStateUpdates } from '../../../../activity-with-state-updates/activity-with-state-updates';
import type { ActivityResponse, CommentResponse } from '../../../../gen/models';

const canLoadComments = (
feedOrActivity: Feed | ActivityResponse | ActivityWithStateUpdates,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { Feed, FeedState } from '@self';
import { useMemo } from 'react';
import { useStateStore } from '@stream-io/state-store/react-bindings';

import { useFeedContext } from '../../contexts/StreamFeedContext';
import { useMemo } from 'react';
import { useStableCallback } from '../internal';
import type { Feed, FeedState } from '../../../../feed';

const selector = ({
is_loading_activities,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { useCallback, useMemo } from 'react';
import {
type ActivityResponse,
type CommentResponse,
type CommentParent,
type Feed,
type FeedState,
checkHasAnotherPage,
isCommentResponse,
} from '@self';
import { useStateStore } from '@stream-io/state-store/react-bindings';

import { useFeedContext } from '../../contexts/StreamFeedContext';
import { checkHasAnotherPage, isCommentResponse } from '../../../../utils';
import type { ActivityResponse, CommentResponse } from '../../../../gen/models';
import type { Feed, FeedState } from '../../../../feed';
import type { CommentParent } from '../../../../types';

type UseCommentsReturnType<T extends ActivityResponse | CommentResponse> = {
comments: NonNullable<
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useFeedContext } from '../../contexts/StreamFeedContext';
import type { Feed, FeedState } from '@self';
import { useStateStore } from '@stream-io/state-store/react-bindings';
import { useMemo } from 'react';
import { useStateStore } from '@stream-io/state-store/react-bindings';

import { useFeedContext } from '../../contexts/StreamFeedContext';
import { useStableCallback } from '../internal';
import type { Feed, FeedState } from '../../../../feed';

/**
* A React hook that returns a reactive object containing the current activities,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { Feed, FeedState } from '@self';
import { useFeedContext } from '../../contexts/StreamFeedContext';
import { useStateStore } from '@stream-io/state-store/react-bindings';

import { useFeedContext } from '../../contexts/StreamFeedContext';
import type { Feed, FeedState } from '../../../../feed';

/**
* A React hook that returns a reactive object containing some often used
* metadata for a feed.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useCallback, useMemo } from 'react';
import { type Feed, type FeedState, checkHasAnotherPage } from '@self';
import { useStateStore } from '@stream-io/state-store/react-bindings';
import { useFeedContext } from '../../contexts/StreamFeedContext';
import type { Feed, FeedState } from '../../../../feed';
import { checkHasAnotherPage } from '../../../../utils';

const selector = ({
follower_count,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useCallback, useMemo } from 'react';
import { type Feed, type FeedState, checkHasAnotherPage } from '@self';
import { useStateStore } from '@stream-io/state-store/react-bindings';

import { useFeedContext } from '../../contexts/StreamFeedContext';
import { checkHasAnotherPage } from '../../../../utils';
import type { Feed, FeedState } from '../../../../feed';

const selector = ({
following_count,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useMemo } from 'react';

import { useFeedContext } from '../../contexts/StreamFeedContext';
import { useNotificationStatus } from './useNotificationStatus';
import { useMemo } from 'react';
import type { AggregatedActivityResponse, Feed } from '@self';
import type { Feed } from '../../../../feed';
import type { AggregatedActivityResponse } from '../../../../gen/models';

export const useIsAggregatedActivityRead = ({
feed: feedFromProps,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useMemo } from 'react';

import { useFeedContext } from '../../contexts/StreamFeedContext';
import { useNotificationStatus } from './useNotificationStatus';
import { useMemo } from 'react';
import type { AggregatedActivityResponse, Feed } from '@self';
import type { Feed } from '../../../../feed';
import type { AggregatedActivityResponse } from '../../../../gen/models';

export const useIsAggregatedActivitySeen = ({
feed: feedFromProps,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Feed, FeedState, NotificationStatusResponse } from '@self';
import { useStateStore } from '@stream-io/state-store/react-bindings';

import { useFeedContext } from '../../contexts/StreamFeedContext';
import type { Feed, FeedState } from '../../../../feed';
import type { NotificationStatusResponse } from '../../../../gen/models';

const selector = ({ notification_status }: FeedState) =>
({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type { FeedOwnCapability, Feed, FeedsClientState } from '@self';
import { useCallback } from 'react';
import { useStateStore } from '@stream-io/state-store/react-bindings';

import { useFeedContext } from '../../contexts/StreamFeedContext';
import { useFeedsClient } from '../../contexts/StreamFeedsContext';
import { useCallback } from 'react';
import type { Feed } from '../../../../feed';
import type { FeedOwnCapability } from '../../../../gen/models';
import type { FeedsClientState } from '../../../../feeds-client';

const stableEmptyArray: readonly FeedOwnCapability[] = [];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { Feed, FeedState } from '@self';
import { useFeedContext } from '../../contexts/StreamFeedContext';
import { useStateStore } from '@stream-io/state-store/react-bindings';

import { useFeedContext } from '../../contexts/StreamFeedContext';
import type { Feed, FeedState } from '../../../../feed';

/**
* A React hook that returns a reactive array of feeds that the current user
* owns and are following the respective feed that we are observing.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type { SearchController, SearchControllerState } from '@self';
import { useSearchContext } from '../../contexts/StreamSearchContext';
import { useStateStore } from '@stream-io/state-store/react-bindings';

import type {
SearchController,
SearchControllerState,
} from '../../../../common/search';
import { useSearchContext } from '../../contexts/StreamSearchContext';

export const useSearchQuery = (controllerFromProps?: SearchController) => {
const controllerFromState = useSearchContext();
const controller = controllerFromProps ?? controllerFromState;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { SearchSource, SearchSourceState } from '@self';
import { useMemo } from 'react';
import { useSearchResultsContext } from '../../contexts/StreamSearchResultsContext';
import { useStateStore } from '@stream-io/state-store/react-bindings';

import { useSearchResultsContext } from '../../contexts/StreamSearchResultsContext';
import { useStableCallback } from '../internal';
import type { SearchSourceState } from '../../../../common/types';
import type { SearchSource } from '../../../../common/search';

export const useSearchResult = (sourceFromProps?: SearchSource) => {
const sourceFromContext = useSearchResultsContext();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import type {
SearchController,
SearchControllerState,
} from '@self';
import { useSearchContext } from '../../contexts/StreamSearchContext';
import { useStateStore } from '@stream-io/state-store/react-bindings';

import { useSearchContext } from '../../contexts/StreamSearchContext';
import type { SearchController, SearchControllerState } from '../../../../common/search';

export const useSearchSources = (controllerFromProps?: SearchController) => {
const controllerFromState = useSearchContext();
const controller = controllerFromProps ?? controllerFromState;
Expand Down
Loading