diff --git a/packages/feeds-client/__integration-tests__/skip-activity-current_feed.test.ts b/packages/feeds-client/__integration-tests__/skip-activity-current_feed.test.ts new file mode 100644 index 00000000..1bb714d4 --- /dev/null +++ b/packages/feeds-client/__integration-tests__/skip-activity-current_feed.test.ts @@ -0,0 +1,46 @@ +import { beforeAll, describe, expect, it } from 'vitest'; +import type { Feed } from '../src'; +import { + createTestClient, + createTestTokenGenerator, + getTestUser, +} from './utils'; + +describe('skip_activity_current_feed', () => { + const user1 = getTestUser(); + const user2 = getTestUser(); + const client1 = createTestClient(); + const client2 = createTestClient(); + let feed1: Feed; + let feed2: Feed; + + beforeAll(async () => { + await client1.connectUser(user1, createTestTokenGenerator(user1)); + await client2.connectUser(user2, createTestTokenGenerator(user2)); + feed1 = client1.feed('timeline', user1.id); + feed2 = client2.feed('timeline', user2.id); + await feed1.getOrCreate(); + await feed2.getOrCreate(); + await feed1.follow(feed2.feed); + await feed2.addActivity({ + type: 'post', + text: 'Hello, world!', + }); + }); + + it('should not create a feed for the activity current feed if read with skip_activity_current_feed', async () => { + await feed1.getOrCreate({ + enrichment_options: { skip_activity_current_feed: true }, + }); + + expect(feed1.currentState.activities?.length).toBe(1); + expect(client1['activeFeeds'][feed2.feed]).toBeUndefined(); + }); + + it('should create a feed for the activity current feed if read without skip_activity_current_feed', async () => { + await feed1.getOrCreate(); + + expect(feed1.currentState.activities?.length).toBe(1); + expect(client1['activeFeeds'][feed2.feed]).toBeDefined(); + }); +}); diff --git a/packages/feeds-client/src/feed/event-handlers/activity/handle-activity-added.test.ts b/packages/feeds-client/src/feed/event-handlers/activity/handle-activity-added.test.ts index 852b9342..55dda23e 100644 --- a/packages/feeds-client/src/feed/event-handlers/activity/handle-activity-added.test.ts +++ b/packages/feeds-client/src/feed/event-handlers/activity/handle-activity-added.test.ts @@ -70,17 +70,24 @@ describe(handleActivityAdded.name, () => { const existing = generateActivityResponse(); feed.state.partialNext({ activities: [existing] }); const event = generateActivityAddedEvent(); + const newActivitiesAddedSpy = vi.spyOn(feed, 'newActivitiesAdded' as any); handleActivityAdded.call(feed, event); const stateAfter = feed.currentState; expect(stateAfter.activities).toHaveLength(2); expect(stateAfter.activities?.[0]).toBe(existing); expect(stateAfter.activities?.[1]).toBe(event.activity); + expect(newActivitiesAddedSpy).toHaveBeenCalledWith([event.activity], { + fromWebSocket: true, + }); + + vi.resetAllMocks(); }); it('does not duplicate if activity already exists', () => { const existing = generateActivityResponse(); feed.state.partialNext({ activities: [existing] }); + const newActivitiesAddedSpy = vi.spyOn(feed, 'newActivitiesAdded' as any); const event = generateActivityAddedEvent({ activity: { id: existing.id }, @@ -93,6 +100,9 @@ describe(handleActivityAdded.name, () => { expect(stateAfter).toBe(stateBefore); expect(stateAfter.activities).toHaveLength(1); expect(stateAfter.activities?.[0]).toBe(existing); + expect(newActivitiesAddedSpy).not.toHaveBeenCalled(); + + vi.resetAllMocks(); }); it(`onActivityAdded filters out activity if it returns false`, () => { diff --git a/packages/feeds-client/src/feed/event-handlers/activity/handle-activity-added.ts b/packages/feeds-client/src/feed/event-handlers/activity/handle-activity-added.ts index a93973f4..b736c74b 100644 --- a/packages/feeds-client/src/feed/event-handlers/activity/handle-activity-added.ts +++ b/packages/feeds-client/src/feed/event-handlers/activity/handle-activity-added.ts @@ -7,6 +7,7 @@ export function addActivitiesToState( newActivities: ActivityResponse[], activities: ActivityResponse[] | undefined, position: 'start' | 'end', + { fromWebSocket }: { fromWebSocket: boolean } = { fromWebSocket: false }, ) { if (activities === undefined) { return { @@ -33,7 +34,7 @@ export function addActivitiesToState( ...activities, ...(position === 'end' ? newActivitiesDeduplicated : []), ]; - this.newActivitiesAdded(newActivitiesDeduplicated); + this.newActivitiesAdded(newActivitiesDeduplicated, { fromWebSocket }); result = { changed: true, activities: updatedActivities }; } @@ -55,6 +56,7 @@ export function handleActivityAdded( [event.activity], currentActivities, this.currentState.addNewActivitiesTo, + { fromWebSocket: true }, ); if (result.changed) { const activity = event.activity; diff --git a/packages/feeds-client/src/feed/feed.test.ts b/packages/feeds-client/src/feed/feed.test.ts index 434799ff..6edad79d 100644 --- a/packages/feeds-client/src/feed/feed.test.ts +++ b/packages/feeds-client/src/feed/feed.test.ts @@ -128,14 +128,6 @@ describe(`getOrCreate`, () => { user_score: 0.8, }, watch: true, - activity_selector_options: { - sort: [ - { - field: 'created_at', - direction: 'desc', - }, - ], - }, interest_weights: { technology: 0.8, travel: -1, @@ -219,3 +211,105 @@ describe(`getOrCreate`, () => { ).rejects.toThrow('Only one getOrCreate call is allowed at a time'); }); }); + +describe(`newActivitiesAdded`, () => { + let feed: Feed; + let client: Record; + + beforeEach(() => { + client = { + getOrCreateActiveFeed: vi.fn(), + hydrateCapabilitiesCache: vi.fn(), + hydratePollCache: vi.fn(), + } as unknown as Record; + const feedResponse = generateFeedResponse({ + id: 'user-123', + group_id: 'user', + }); + feed = new Feed( + client as unknown as FeedsClient, + feedResponse.group_id, + feedResponse.id, + feedResponse, + ); + }); + + it('should not create feeds if enrichment options are set to skip_all', () => { + feed.state.partialNext({ + last_get_or_create_request_config: { + enrichment_options: { + skip_all: true, + }, + }, + }); + + feed['newActivitiesAdded']([generateActivityResponse()]); + + expect(client['getOrCreateActiveFeed']).not.toHaveBeenCalled(); + }); + + it('should not create feeds if enrichment options are set to skip_activity_current_feed', () => { + feed.state.partialNext({ + last_get_or_create_request_config: { + enrichment_options: { + skip_activity_current_feed: true, + }, + }, + }); + + feed['newActivitiesAdded']([generateActivityResponse()]); + + expect(client['getOrCreateActiveFeed']).not.toHaveBeenCalled(); + }); + + it('should deduplicate feeds from acitivties', () => { + const feed1 = generateFeedResponse({ + group_id: 'user', + id: '123', + feed: 'user:123', + }); + const feed2 = generateFeedResponse({ + group_id: 'user', + id: '456', + feed: 'user:456', + }); + const activity1 = generateActivityResponse({ current_feed: feed1 }); + const activity2 = generateActivityResponse({ current_feed: feed2 }); + const activity3 = generateActivityResponse({ current_feed: feed1 }); + + feed['newActivitiesAdded']([activity1, activity2, activity3]); + + expect(client['getOrCreateActiveFeed']).toHaveBeenCalledTimes(2); + expect(client['getOrCreateActiveFeed']).toHaveBeenCalledWith({ + group: feed1.group_id, + id: feed1.id, + data: feed1, + fromWebSocket: false, + }); + expect(client['getOrCreateActiveFeed']).toHaveBeenCalledWith({ + group: feed2.group_id, + id: feed2.id, + data: feed2, + fromWebSocket: false, + }); + }); + + it(`should set fromWebSocket flag to true if activities are added from a WebSocket event`, () => { + const currentFeed = generateFeedResponse({ + group_id: 'user', + id: '123', + feed: 'user:123', + }); + feed['newActivitiesAdded']( + [generateActivityResponse({ current_feed: currentFeed })], + { fromWebSocket: true }, + ); + + expect(client['getOrCreateActiveFeed']).toHaveBeenCalledWith({ + group: currentFeed.group_id, + id: currentFeed.id, + data: currentFeed, + fromWebSocket: true, + }); + }); +}); diff --git a/packages/feeds-client/src/feed/feed.ts b/packages/feeds-client/src/feed/feed.ts index 9847a218..531d2a8f 100644 --- a/packages/feeds-client/src/feed/feed.ts +++ b/packages/feeds-client/src/feed/feed.ts @@ -63,6 +63,7 @@ import { checkHasAnotherPage, Constants, feedsLoggerSystem, + ownFeedFields, uniqueArrayMerge, } from '../utils'; import { handleActivityFeedback } from './event-handlers/activity/handle-activity-feedback'; @@ -953,12 +954,13 @@ export class Feed extends FeedApi { event.activity.current_feed && currentActivity?.current_feed ) { - event.activity.current_feed.own_capabilities = - currentActivity.current_feed.own_capabilities; - event.activity.current_feed.own_follows = - currentActivity.current_feed.own_follows; - event.activity.current_feed.own_membership = - currentActivity.current_feed.own_membership; + ownFeedFields.forEach((field) => { + if (field in currentActivity.current_feed!) { + // @ts-expect-error TODO: fix this + event.activity.current_feed![field] = + currentActivity.current_feed![field]; + } + }); } } // @ts-expect-error intersection of handler arguments results to never @@ -974,17 +976,48 @@ export class Feed extends FeedApi { this.eventDispatcher.dispatch(event); } - protected newActivitiesAdded(activities: ActivityResponse[]) { + protected newActivitiesAdded( + activities: ActivityResponse[], + options: { + fromWebSocket: boolean; + } = { fromWebSocket: false }, + ) { this.client.hydratePollCache(activities); + this.getOrCreateFeeds(activities, options); + } - activities.forEach((activity) => { - if (activity.current_feed) { - getOrCreateActiveFeed.bind(this.client)( - activity.current_feed.group_id, - activity.current_feed.id, - activity.current_feed, - ); - } - }); + private getOrCreateFeeds( + activities: ActivityResponse[], + options: { + fromWebSocket: boolean; + }, + ) { + const enrichmentOptions = + this.currentState.last_get_or_create_request_config?.enrichment_options; + if ( + !enrichmentOptions?.skip_activity_current_feed && + !enrichmentOptions?.skip_all + ) { + const feedsToGetOrCreate = new Map(); + activities.forEach((activity) => { + if ( + activity.current_feed && + !feedsToGetOrCreate.has(activity.current_feed.feed) + ) { + feedsToGetOrCreate.set( + activity.current_feed.feed, + activity.current_feed, + ); + } + }); + feedsToGetOrCreate.values().forEach((feed) => { + getOrCreateActiveFeed.bind(this.client)({ + group: feed.group_id, + id: feed.id, + data: feed, + fromWebSocket: options.fromWebSocket, + }); + }); + } } } diff --git a/packages/feeds-client/src/feeds-client/feeds-client.test.ts b/packages/feeds-client/src/feeds-client/feeds-client.test.ts index 58f35b52..ef4ac792 100644 --- a/packages/feeds-client/src/feeds-client/feeds-client.test.ts +++ b/packages/feeds-client/src/feeds-client/feeds-client.test.ts @@ -1,6 +1,12 @@ -import { beforeEach, describe, expect, it } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { FeedsClient } from './feeds-client'; -import { generateActivityResponse } from '../test-utils'; +import { + generateActivityResponse, + generateFeedMemberResponse, + generateFeedResponse, + generateFollowResponse, +} from '../test-utils'; +import { FeedOwnCapability } from '..'; describe('Feeds client tests', () => { let client: FeedsClient; @@ -28,4 +34,194 @@ describe('Feeds client tests', () => { expect(feeds).toContain(feedC); expect(feeds).not.toContain(feedB); }); + + describe('client.feed', () => { + it('should initialize feed state with the given data if feed does not exist', async () => { + const data = generateFeedResponse({ feed: 'timeline:feed' }); + client['getOrCreateActiveFeed']({ group: 'timeline', id: 'feed', data }); + + expect( + client['activeFeeds']['timeline:feed']?.currentState, + ).toMatchObject(data); + }); + + it('should update feed state if data is newer than the current state', async () => { + const data = generateFeedResponse({ + feed: 'timeline:feed', + follower_count: 5, + }); + client['getOrCreateActiveFeed']({ group: 'timeline', id: 'feed', data }); + + expect( + client['activeFeeds']['timeline:feed']?.currentState, + ).toMatchObject(data); + + const newData = { ...data }; + newData.updated_at = new Date(data.updated_at.getTime() + 1000); + newData.follower_count = 10; + client['getOrCreateActiveFeed']({ + group: 'timeline', + id: 'feed', + data: newData, + }); + + expect( + client['activeFeeds']['timeline:feed']?.currentState, + ).toMatchObject(newData); + expect( + client['activeFeeds']['timeline:feed']?.currentState.follower_count, + ).toBe(10); + }); + + it(`should not update feed state if data is older than the current state`, async () => { + const data = generateFeedResponse({ + feed: 'timeline:feed', + follower_count: 5, + }); + client['getOrCreateActiveFeed']({ group: 'timeline', id: 'feed', data }); + + expect( + client['activeFeeds']['timeline:feed']?.currentState, + ).toMatchObject(data); + + const oldData = { ...data }; + oldData.updated_at = new Date(data.updated_at.getTime() - 1000); + oldData.follower_count = 3; + client['getOrCreateActiveFeed']({ + group: 'timeline', + id: 'feed', + data: oldData, + }); + + expect( + client['activeFeeds']['timeline:feed']?.currentState, + ).toMatchObject(data); + expect( + client['activeFeeds']['timeline:feed']?.currentState.follower_count, + ).toBe(5); + }); + + it(`should not update own_ fields if data is from WebSocket`, async () => { + const ownFollows = [ + generateFollowResponse({ + source_feed: generateFeedResponse({ feed: 'timeline:feed' }), + target_feed: generateFeedResponse({ feed: 'user:123' }), + }), + ]; + const ownMembership = generateFeedMemberResponse(); + const ownCapabilities = [FeedOwnCapability.ADD_ACTIVITY]; + const data = generateFeedResponse({ + feed: 'user:123', + follower_count: 5, + own_follows: ownFollows, + own_membership: ownMembership, + own_capabilities: ownCapabilities, + }); + client['getOrCreateActiveFeed']({ group: 'user', id: '123', data }); + + const dataFromWebSocket = { ...data }; + delete dataFromWebSocket.own_follows; + delete dataFromWebSocket.own_membership; + delete dataFromWebSocket.own_capabilities; + client['getOrCreateActiveFeed']({ + group: 'user', + id: '123', + data: dataFromWebSocket, + fromWebSocket: true, + }); + + expect(client['activeFeeds']['user:123']?.currentState).toMatchObject( + data, + ); + expect( + client['activeFeeds']['user:123']?.currentState.own_follows, + ).toEqual(ownFollows); + expect( + client['activeFeeds']['user:123']?.currentState.own_membership, + ).toEqual(ownMembership); + expect( + // @ts-expect-error - own_capabilities is currently excluded from type + client['activeFeeds']['user:123']?.currentState.own_capabilities, + ).toEqual(ownCapabilities); + }); + }); + + it(`should only update _own fields which are changed`, async () => { + const ownFollows = [ + generateFollowResponse({ + source_feed: generateFeedResponse({ feed: 'timeline:feed' }), + target_feed: generateFeedResponse({ feed: 'user:123' }), + }), + ]; + const ownMembership = generateFeedMemberResponse(); + const ownCapabilities = [FeedOwnCapability.ADD_ACTIVITY]; + const data = generateFeedResponse({ + feed: 'user:123', + follower_count: 5, + own_follows: ownFollows, + own_membership: ownMembership, + own_capabilities: ownCapabilities, + }); + client['getOrCreateActiveFeed']({ group: 'user', id: '123', data }); + + const spy = vi.fn(); + const feed = client['activeFeeds']['user:123']; + feed.state.subscribe(spy); + spy.mockClear(); + + const newData = { ...data }; + data.own_follows = [...ownFollows]; + client['getOrCreateActiveFeed']({ + group: 'user', + id: '123', + data: newData, + fromWebSocket: false, + }); + + expect(spy).toHaveBeenCalledTimes(0); + + const newOwnFollows = [ + ...ownFollows, + generateFollowResponse({ + source_feed: generateFeedResponse({ feed: 'timeline:feed' }), + target_feed: generateFeedResponse({ feed: 'user:456' }), + }), + ]; + newData.own_follows = newOwnFollows; + + client['getOrCreateActiveFeed']({ + group: 'user', + id: '123', + data: newData, + }); + + expect(spy).toHaveBeenCalledTimes(1); + expect(spy.mock.lastCall?.[0]).toMatchObject({ + own_follows: newOwnFollows, + }); + + spy.mockClear(); + newData.own_membership = { ...newData.own_membership! }; + client['getOrCreateActiveFeed']({ + group: 'user', + id: '123', + data: newData, + }); + + expect(spy).toHaveBeenCalledTimes(0); + + newData.own_membership!.updated_at = new Date( + newData.own_membership!.updated_at.getTime() + 1000, + ); + client['getOrCreateActiveFeed']({ + group: 'user', + id: '123', + data: newData, + }); + + expect(spy).toHaveBeenCalledTimes(1); + expect(spy.mock.lastCall?.[0]).toMatchObject({ + own_membership: newData.own_membership, + }); + }); }); diff --git a/packages/feeds-client/src/feeds-client/feeds-client.ts b/packages/feeds-client/src/feeds-client/feeds-client.ts index 412d5204..432456ad 100644 --- a/packages/feeds-client/src/feeds-client/feeds-client.ts +++ b/packages/feeds-client/src/feeds-client/feeds-client.ts @@ -93,6 +93,10 @@ import { } from '../utils/throttling'; import { ActivityWithStateUpdates } from '../activity-with-state-updates/activity-with-state-updates'; import { getFeed } from '../activity-with-state-updates/get-feed'; +import { + isOwnFollowsEqual, + isOwnMembershipEqual, +} from '../utils/check-own-fields-equality'; export type FeedsClientState = { connected_user: ConnectedUser | undefined; @@ -175,11 +179,11 @@ export class FeedsClient extends FeedsApi { case 'feeds.feed.created': { if (this.activeFeeds[event.feed.id]) break; - this.getOrCreateActiveFeed( - event.feed.group_id, - event.feed.id, - event.feed, - ); + this.getOrCreateActiveFeed({ + group: event.feed.group_id, + id: event.feed.id, + data: event.feed, + }); break; } @@ -676,13 +680,11 @@ export class FeedsClient extends FeedsApi { activityAddedEventFilter?: (event: ActivityAddedEvent) => boolean; }, ) => { - return this.getOrCreateActiveFeed( - groupId, + return this.getOrCreateActiveFeed({ + group: groupId, id, - undefined, - undefined, options, - ); + }); }; /** @@ -708,12 +710,12 @@ export class FeedsClient extends FeedsApi { const feedResponses = response.feeds; const feeds = feedResponses.map((feedResponse) => - this.getOrCreateActiveFeed( - feedResponse.group_id, - feedResponse.id, - feedResponse, - request?.watch, - ), + this.getOrCreateActiveFeed({ + group: feedResponse.group_id, + id: feedResponse.id, + data: feedResponse, + watch: request?.watch, + }), ); this.hydrateCapabilitiesCache(feedResponses); @@ -846,27 +848,35 @@ export class FeedsClient extends FeedsApi { const response = await super.getFollowSuggestions(...params); response.suggestions.forEach((suggestion) => { - this.getOrCreateActiveFeed( - suggestion.group_id, - suggestion.id, - suggestion, - ); + this.getOrCreateActiveFeed({ + group: suggestion.group_id, + id: suggestion.id, + data: suggestion, + }); }); // TODO: return feed instance here https://linear.app/stream/issue/REACT-669/return-feed-instance-from-followsuggestions-breaking return response; } - protected readonly getOrCreateActiveFeed = ( - group: string, - id: string, - data?: FeedResponse, - watch?: boolean, + protected readonly getOrCreateActiveFeed = ({ + group, + id, + data, + watch, + options, + fromWebSocket = false, + }: { + group: string; + id: string; + data?: FeedResponse; + watch?: boolean; options?: { addNewActivitiesTo?: 'start' | 'end'; activityAddedEventFilter?: (event: ActivityAddedEvent) => boolean; - }, - ) => { + }; + fromWebSocket?: boolean; + }) => { const fid = `${group}:${id}`; let isCreated = false; @@ -895,8 +905,37 @@ export class FeedsClient extends FeedsApi { } if (!feed.currentState.watch) { - // feed isn't watched and may be stale, update it - if (data) handleFeedUpdated.call(feed, { feed: data }); + if (!isCreated && data) { + if ( + (feed.currentState.updated_at?.getTime() ?? 0) < + data.updated_at.getTime() + ) { + handleFeedUpdated.call(feed, { feed: data }); + } else if ( + (feed.currentState.updated_at?.getTime() ?? 0) === + data.updated_at.getTime() && + !fromWebSocket + ) { + const fieldsToUpdate: Array = []; + if (!isOwnFollowsEqual(feed.currentState, data)) { + fieldsToUpdate.push('own_follows'); + } + if (!isOwnMembershipEqual(feed.currentState, data)) { + fieldsToUpdate.push('own_membership'); + } + if (fieldsToUpdate.length > 0) { + const fieldsToUpdateData = fieldsToUpdate.reduce( + (acc: Partial, field) => { + // @ts-expect-error TODO: fix this + acc[field] = data[field]; + return acc; + }, + {}, + ); + feed.state.partialNext(fieldsToUpdateData); + } + } + } if (watch) handleWatchStarted.call(feed); } diff --git a/packages/feeds-client/src/feeds-client/get-or-create-active-feed.ts b/packages/feeds-client/src/feeds-client/get-or-create-active-feed.ts index 2ec2dc32..e181e972 100644 --- a/packages/feeds-client/src/feeds-client/get-or-create-active-feed.ts +++ b/packages/feeds-client/src/feeds-client/get-or-create-active-feed.ts @@ -1,12 +1,8 @@ -import type { FeedResponse } from '../gen/models'; import type { FeedsClient } from './feeds-client'; export function getOrCreateActiveFeed( this: FeedsClient, - group: string, - id: string, - data?: FeedResponse, - watch?: boolean, + ...args: Parameters ) { - return this.getOrCreateActiveFeed(group, id, data, watch); + return this.getOrCreateActiveFeed(...args); } diff --git a/packages/feeds-client/src/test-utils/response-generators.ts b/packages/feeds-client/src/test-utils/response-generators.ts index eae44566..65616fcf 100644 --- a/packages/feeds-client/src/test-utils/response-generators.ts +++ b/packages/feeds-client/src/test-utils/response-generators.ts @@ -106,9 +106,10 @@ export const generateFeedResponse = ( name: humanId({ separator: ' ' }), pin_count: 0, custom: {}, + own_capabilities: [], + activity_count: 0, ...overrides, feed, - own_capabilities: [], created_by: generateUserResponse(overrides.created_by), }; }; diff --git a/packages/feeds-client/src/utils/check-own-fields-equality.ts b/packages/feeds-client/src/utils/check-own-fields-equality.ts new file mode 100644 index 00000000..ef30245a --- /dev/null +++ b/packages/feeds-client/src/utils/check-own-fields-equality.ts @@ -0,0 +1,40 @@ +import type { FeedState } from '../feed'; +import type { FeedResponse } from '../gen/models'; + +export const isOwnFollowsEqual = ( + currentState: FeedState, + newState: FeedResponse, +) => { + const existingFollows = new Set( + currentState.own_follows?.map( + (f) => + `${f.source_feed.feed}:${f.target_feed.feed}:${f.updated_at.getTime()}`, + ), + ); + const newFollows = new Set( + newState.own_follows?.map( + (f) => + `${f.source_feed.feed}:${f.target_feed.feed}:${f.updated_at.getTime()}`, + ), + ); + if (existingFollows.size === newFollows.size) { + const areEqual = Array.from(existingFollows).every((f) => + newFollows.has(f), + ); + if (areEqual) { + return true; + } + } + + return false; +}; + +export const isOwnMembershipEqual = ( + currentState: FeedState, + newState: FeedResponse, +) => { + return ( + (currentState.own_membership?.updated_at.getTime() ?? 0) === + (newState.own_membership?.updated_at.getTime() ?? 0) + ); +}; diff --git a/packages/feeds-client/src/utils/index.ts b/packages/feeds-client/src/utils/index.ts index 976caac1..8690a4cc 100644 --- a/packages/feeds-client/src/utils/index.ts +++ b/packages/feeds-client/src/utils/index.ts @@ -6,3 +6,4 @@ export * from './state-update-queue'; export * from './update-entity-in-array'; export * from './logger'; export * from './ensure-exhausted'; +export * from './own-feed-fields'; diff --git a/packages/feeds-client/src/utils/own-feed-fields.ts b/packages/feeds-client/src/utils/own-feed-fields.ts new file mode 100644 index 00000000..9199dd81 --- /dev/null +++ b/packages/feeds-client/src/utils/own-feed-fields.ts @@ -0,0 +1,6 @@ +import type { FeedResponse } from '../gen/models'; + +export const ownFeedFields: Array> = ['own_capabilities', 'own_follows', 'own_membership']; diff --git a/sample-apps/react-tutorial/src/App.tsx b/sample-apps/react-tutorial/src/App.tsx index 6776d671..342b9270 100644 --- a/sample-apps/react-tutorial/src/App.tsx +++ b/sample-apps/react-tutorial/src/App.tsx @@ -26,8 +26,9 @@ if (!userIdFromUrl) { const CURRENT_USER = { id: USER_ID, name: import.meta.env.VITE_USER_NAME ?? USER_ID, - token: - (import.meta.env.VITE_USER_TOKEN ?? import.meta.env.VITE_TOKEN_URL) + token: import.meta.env.VITE_USER_TOKEN + ? import.meta.env.VITE_USER_TOKEN + : import.meta.env.VITE_TOKEN_URL ? () => fetch(`${import.meta.env.VITE_TOKEN_URL}&user_id=${USER_ID}`) .then((res) => res.json())