{"version":3,"file":"feed.mjs","sources":["../../../../src/clients/feed/feed.ts"],"sourcesContent":["import { GenericData } from \"@knocklabs/types\";\nimport EventEmitter from \"eventemitter2\";\nimport { nanoid } from \"nanoid/non-secure\";\n\nimport { isValidUuid } from \"../../helpers\";\nimport Knock from \"../../knock\";\nimport { NetworkStatus, isRequestInFlight } from \"../../networkStatus\";\nimport {\n  BulkUpdateMessagesInChannelProperties,\n  MessageEngagementStatus,\n} from \"../messages/interfaces\";\n\nimport {\n  FeedClientOptions,\n  FeedItem,\n  FeedMetadata,\n  FeedResponse,\n  FetchFeedOptions,\n  FetchFeedOptionsForRequest,\n} from \"./interfaces\";\nimport {\n  FeedSocketManager,\n  SocketEventPayload,\n  SocketEventType,\n} from \"./socket-manager\";\nimport createStore, { FeedStore } from \"./store\";\nimport {\n  BindableFeedEvent,\n  FeedEvent,\n  FeedEventCallback,\n  FeedEventPayload,\n  FeedItemOrItems,\n  FeedMessagesReceivedPayload,\n  FeedRealTimeCallback,\n} from \"./types\";\nimport { getFormattedTriggerData, mergeDateRangeParams } from \"./utils\";\n\n// Default options to apply\nconst feedClientDefaults: Pick<FeedClientOptions, \"archived\" | \"mode\"> = {\n  archived: \"exclude\",\n  mode: \"compact\",\n};\n\nconst CLIENT_REF_ID_PREFIX = \"client_\";\n\nclass Feed {\n  public readonly defaultOptions: FeedClientOptions;\n  public readonly referenceId: string;\n  public unsubscribeFromSocketEvents: (() => void) | undefined = undefined;\n  private socketManager: FeedSocketManager | undefined;\n  private userFeedId: string;\n  private broadcaster: EventEmitter;\n  private broadcastChannel!: BroadcastChannel | null;\n  private hasSubscribedToRealTimeUpdates: boolean = false;\n\n  // The raw store instance, used for binding in React and other environments\n  public store: FeedStore;\n\n  constructor(\n    readonly knock: Knock,\n    readonly feedId: string,\n    options: FeedClientOptions,\n    socketManager: FeedSocketManager | undefined,\n  ) {\n    if (!feedId || !isValidUuid(feedId)) {\n      this.knock.log(\n        \"[Feed] Invalid or missing feedId provided to the Feed constructor. The feed should be a UUID of an in-app feed channel (`in_app_feed`) found in the Knock dashboard. Please provide a valid feedId to the Feed constructor.\",\n        true,\n      );\n    }\n\n    this.feedId = feedId;\n    this.userFeedId = this.buildUserFeedId();\n    this.referenceId = CLIENT_REF_ID_PREFIX + nanoid();\n    this.socketManager = socketManager;\n    this.store = createStore();\n    this.broadcaster = new EventEmitter({ wildcard: true, delimiter: \".\" });\n    this.defaultOptions = {\n      ...feedClientDefaults,\n      ...mergeDateRangeParams(options),\n    };\n    this.knock.log(`[Feed] Initialized a feed on channel ${feedId}`);\n\n    // Attempt to setup a realtime connection (does not join)\n    this.initializeRealtimeConnection();\n\n    this.setupBroadcastChannel();\n  }\n\n  /**\n   * Used to reinitialize a current feed instance, which is useful when reauthenticating users\n   */\n  reinitialize(socketManager?: FeedSocketManager) {\n    this.socketManager = socketManager;\n\n    // Reinitialize the user feed id incase the userId changed\n    this.userFeedId = this.buildUserFeedId();\n\n    // Reinitialize the real-time connection\n    this.initializeRealtimeConnection();\n\n    // Reinitialize our broadcast channel\n    this.setupBroadcastChannel();\n  }\n\n  /**\n   * Cleans up a feed instance by destroying the store and disconnecting\n   * an open socket connection.\n   */\n  teardown() {\n    this.knock.log(\"[Feed] Tearing down feed instance\");\n\n    this.socketManager?.leave(this);\n\n    if (this.broadcastChannel) {\n      this.broadcastChannel.close();\n    }\n  }\n\n  /** Tears down an instance and removes it entirely from the feed manager */\n  dispose() {\n    this.knock.log(\"[Feed] Disposing of feed instance\");\n    this.teardown();\n    this.broadcaster.removeAllListeners();\n    this.knock.feeds.removeInstance(this);\n  }\n\n  /*\n    Initializes a real-time connection to Knock, connecting the websocket for the\n    current ApiClient instance if the socket is not already connected.\n  */\n  listenForUpdates() {\n    this.knock.log(\"[Feed] Connecting to real-time service\");\n\n    this.hasSubscribedToRealTimeUpdates = true;\n\n    // If the user is not authenticated, then do nothing\n    if (!this.knock.isAuthenticated()) {\n      this.knock.log(\n        \"[Feed] User is not authenticated, skipping listening for updates\",\n      );\n      return;\n    }\n\n    this.unsubscribeFromSocketEvents = this.socketManager?.join(this);\n  }\n\n  /* Binds a handler to be invoked when event occurs */\n  on(\n    eventName: BindableFeedEvent,\n    callback: FeedEventCallback | FeedRealTimeCallback,\n  ) {\n    this.broadcaster.on(eventName, callback);\n  }\n\n  off(\n    eventName: BindableFeedEvent,\n    callback: FeedEventCallback | FeedRealTimeCallback,\n  ) {\n    this.broadcaster.off(eventName, callback);\n  }\n\n  getState() {\n    return this.store.getState();\n  }\n\n  async markAsSeen(itemOrItems: FeedItemOrItems) {\n    const now = new Date().toISOString();\n    this.optimisticallyPerformStatusUpdate(\n      itemOrItems,\n      \"seen\",\n      { seen_at: now },\n      \"unseen_count\",\n    );\n\n    return this.makeStatusUpdate(itemOrItems, \"seen\");\n  }\n\n  async markAllAsSeen() {\n    // To mark all of the messages as seen we:\n    // 1. Optimistically update *everything* we have in the store\n    // 2. We decrement the `unseen_count` to zero optimistically\n    // 3. We issue the API call to the endpoint\n    //\n    // Note: there is the potential for a race condition here because the bulk\n    // update is an async method, so if a new message comes in during this window before\n    // the update has been processed we'll effectively reset the `unseen_count` to be what it was.\n    //\n    // Note: here we optimistically handle the case whereby the feed is scoped to show only `unseen`\n    // items by removing everything from view.\n    const { metadata, items, ...state } = this.store.getState();\n\n    const isViewingOnlyUnseen = this.defaultOptions.status === \"unseen\";\n\n    // If we're looking at the unseen view, then we want to remove all of the items optimistically\n    // from the store given that nothing should be visible. We do this by resetting the store state\n    // and setting the current metadata counts to 0\n    if (isViewingOnlyUnseen) {\n      state.resetStore({\n        ...metadata,\n        total_count: 0,\n        unseen_count: 0,\n      });\n    } else {\n      // Otherwise we want to update the metadata and mark all of the items in the store as seen\n      state.setMetadata({ ...metadata, unseen_count: 0 });\n\n      const attrs = { seen_at: new Date().toISOString() };\n      const itemIds = items.map((item) => item.id);\n\n      state.setItemAttrs(itemIds, attrs);\n    }\n\n    // Issue the API request to the bulk status change API\n    const result = await this.makeBulkStatusUpdate(\"seen\");\n    this.emitEvent(\"all_seen\", items);\n\n    return result;\n  }\n\n  async markAsUnseen(itemOrItems: FeedItemOrItems) {\n    this.optimisticallyPerformStatusUpdate(\n      itemOrItems,\n      \"unseen\",\n      { seen_at: null },\n      \"unseen_count\",\n    );\n\n    return this.makeStatusUpdate(itemOrItems, \"unseen\");\n  }\n\n  async markAsRead(itemOrItems: FeedItemOrItems) {\n    const now = new Date().toISOString();\n    this.optimisticallyPerformStatusUpdate(\n      itemOrItems,\n      \"read\",\n      { read_at: now },\n      \"unread_count\",\n    );\n\n    return this.makeStatusUpdate(itemOrItems, \"read\");\n  }\n\n  async markAllAsRead() {\n    // To mark all of the messages as read we:\n    // 1. Optimistically update *everything* we have in the store\n    // 2. We decrement the `unread_count` to zero optimistically\n    // 3. We issue the API call to the endpoint\n    //\n    // Note: there is the potential for a race condition here because the bulk\n    // update is an async method, so if a new message comes in during this window before\n    // the update has been processed we'll effectively reset the `unread_count` to be what it was.\n    //\n    // Note: here we optimistically handle the case whereby the feed is scoped to show only `unread`\n    // items by removing everything from view.\n    const { metadata, items, ...state } = this.store.getState();\n\n    const isViewingOnlyUnread = this.defaultOptions.status === \"unread\";\n\n    // If we're looking at the unread view, then we want to remove all of the items optimistically\n    // from the store given that nothing should be visible. We do this by resetting the store state\n    // and setting the current metadata counts to 0\n    if (isViewingOnlyUnread) {\n      state.resetStore({\n        ...metadata,\n        total_count: 0,\n        unread_count: 0,\n      });\n    } else {\n      // Otherwise we want to update the metadata and mark all of the items in the store as seen\n      state.setMetadata({ ...metadata, unread_count: 0 });\n\n      const attrs = { read_at: new Date().toISOString() };\n      const itemIds = items.map((item) => item.id);\n\n      state.setItemAttrs(itemIds, attrs);\n    }\n\n    // Issue the API request to the bulk status change API\n    const result = await this.makeBulkStatusUpdate(\"read\");\n    this.emitEvent(\"all_read\", items);\n\n    return result;\n  }\n\n  async markAsUnread(itemOrItems: FeedItemOrItems) {\n    this.optimisticallyPerformStatusUpdate(\n      itemOrItems,\n      \"unread\",\n      { read_at: null },\n      \"unread_count\",\n    );\n\n    return this.makeStatusUpdate(itemOrItems, \"unread\");\n  }\n\n  async markAsInteracted(\n    itemOrItems: FeedItemOrItems,\n    metadata?: Record<string, string>,\n  ) {\n    const now = new Date().toISOString();\n    this.optimisticallyPerformStatusUpdate(\n      itemOrItems,\n      \"interacted\",\n      {\n        read_at: now,\n        interacted_at: now,\n      },\n      \"unread_count\",\n    );\n\n    return this.makeStatusUpdate(itemOrItems, \"interacted\", metadata);\n  }\n\n  /*\n  Marking one or more items as archived should:\n\n  - Decrement the badge count for any unread / unseen items\n  - Remove the item from the feed list when the `archived` flag is \"exclude\" (default)\n\n  TODO: how do we handle rollbacks?\n  */\n  async markAsArchived(itemOrItems: FeedItemOrItems) {\n    const state = this.store.getState();\n\n    const shouldOptimisticallyRemoveItems =\n      this.defaultOptions.archived === \"exclude\";\n\n    const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];\n\n    const itemIds: string[] = items.map((item) => item.id);\n\n    /*\n      In the code here we want to optimistically update counts and items\n      that are persisted such that we can display updates immediately on the feed\n      without needing to make a network request.\n\n      Note: right now this does *not* take into account offline handling or any extensive retry\n      logic, so rollbacks aren't considered. That probably needs to be a future consideration for\n      this library.\n\n      Scenarios to consider:\n\n      ## Feed scope to archived *only*\n\n      - Counts should not be decremented\n      - Items should not be removed\n\n      ## Feed scoped to exclude archived items (the default)\n\n      - Counts should be decremented\n      - Items should be removed\n\n      ## Feed scoped to include archived items as well\n\n      - Counts should not be decremented\n      - Items should not be removed\n    */\n\n    if (shouldOptimisticallyRemoveItems) {\n      // If any of the items are unseen or unread, then capture as we'll want to decrement\n      // the counts for these in the metadata we have\n      const unseenCount = items.filter((i) => !i.seen_at).length;\n      const unreadCount = items.filter((i) => !i.read_at).length;\n\n      // Build the new metadata\n      const updatedMetadata = {\n        ...state.metadata,\n        // Ensure that the counts don't ever go below 0 on archiving where the client state\n        // gets out of sync with the server state\n        total_count: Math.max(0, state.metadata.total_count - items.length),\n        unseen_count: Math.max(0, state.metadata.unseen_count - unseenCount),\n        unread_count: Math.max(0, state.metadata.unread_count - unreadCount),\n      };\n\n      // Remove the archiving entries\n      const entriesToSet = state.items.filter(\n        (item) => !itemIds.includes(item.id),\n      );\n\n      state.setResult({\n        entries: entriesToSet,\n        meta: updatedMetadata,\n        page_info: state.pageInfo,\n      });\n    } else {\n      // Mark all the entries being updated as archived either way so the state is correct\n      state.setItemAttrs(itemIds, { archived_at: new Date().toISOString() });\n    }\n\n    return this.makeStatusUpdate(itemOrItems, \"archived\");\n  }\n\n  async markAllAsArchived() {\n    // Note: there is the potential for a race condition here because the bulk\n    // update is an async method, so if a new message comes in during this window before\n    // the update has been processed we'll effectively reset the `unseen_count` to be what it was.\n    const { items, ...state } = this.store.getState();\n\n    // Here if we're looking at a feed that excludes all of the archived items by default then we\n    // will want to optimistically remove all of the items from the feed as they are now all excluded\n    const shouldOptimisticallyRemoveItems =\n      this.defaultOptions.archived === \"exclude\";\n\n    if (shouldOptimisticallyRemoveItems) {\n      // Reset the store to clear out all of items and reset the badge count\n      state.resetStore();\n    } else {\n      // Mark all the entries being updated as archived either way so the state is correct\n      const itemIds = items.map((i) => i.id);\n      state.setItemAttrs(itemIds, { archived_at: new Date().toISOString() });\n    }\n\n    // Issue the API request to the bulk status change API\n    const result = await this.makeBulkStatusUpdate(\"archive\");\n    this.emitEvent(\"all_archived\", items);\n\n    return result;\n  }\n\n  async markAllReadAsArchived() {\n    // Note: there is the potential for a race condition here because the bulk\n    // update is an async method, so if a new message comes in during this window before\n    // the update has been processed we'll effectively reset the `unseen_count` to be what it was.\n    const { items, ...state } = this.store.getState();\n    // Filter items to only include those that are unread\n    const unreadItems = items.filter((item) => item.read_at === null);\n    // Mark all the unread items as archived and read\n    const itemIds = unreadItems.map((i) => i.id);\n    state.setItemAttrs(itemIds, {\n      archived_at: new Date().toISOString(),\n    });\n\n    // Here if we're looking at a feed that excludes all of the archived items by default then we\n    // will want to optimistically remove all of the items from the feed as they are now all excluded\n    const shouldOptimisticallyRemoveItems =\n      this.defaultOptions.archived === \"exclude\";\n\n    if (shouldOptimisticallyRemoveItems) {\n      // Remove all the read items from the store and reset the badge count\n      const remainingItems = items.filter((item) => !itemIds.includes(item.id));\n      // Build the new metadata\n      const updatedMetadata = {\n        ...state.metadata,\n        total_count: remainingItems.length,\n        unread_count: 0,\n      };\n\n      state.setResult({\n        entries: remainingItems,\n        meta: updatedMetadata,\n        page_info: state.pageInfo,\n      });\n    }\n\n    // Issue the API request to the bulk status change API\n    const result = await this.makeBulkStatusUpdate(\"archive\");\n    // this.emitEvent(\"all_archived\", readItems);\n\n    return result;\n  }\n\n  async markAsUnarchived(itemOrItems: FeedItemOrItems) {\n    const state = this.store.getState();\n\n    const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];\n\n    const itemIds: string[] = items.map((item) => item.id);\n\n    const shouldOptimisticallyRemoveItems =\n      this.defaultOptions.archived === \"only\";\n\n    if (shouldOptimisticallyRemoveItems) {\n      // If any of the items are unseen or unread, then capture as we'll want to decrement\n      // the counts for these in the metadata we have\n      const unseenCount = items.filter((i) => !i.seen_at).length;\n      const unreadCount = items.filter((i) => !i.read_at).length;\n\n      // Build the new metadata\n      const updatedMetadata = {\n        ...state.metadata,\n        // Ensure that the counts don't ever go below 0 on unarchiving where the client state\n        // gets out of sync with the server state\n        total_count: Math.max(0, state.metadata.total_count - items.length),\n        unseen_count: Math.max(0, state.metadata.unseen_count - unseenCount),\n        unread_count: Math.max(0, state.metadata.unread_count - unreadCount),\n      };\n\n      // Remove the unarchived entries\n      const entriesToSet = state.items.filter(\n        (item) => !itemIds.includes(item.id),\n      );\n\n      state.setResult({\n        entries: entriesToSet,\n        meta: updatedMetadata,\n        page_info: state.pageInfo,\n      });\n    } else {\n      this.optimisticallyPerformStatusUpdate(itemOrItems, \"unarchived\", {\n        archived_at: null,\n      });\n    }\n\n    return this.makeStatusUpdate(itemOrItems, \"unarchived\");\n  }\n\n  /* Fetches the feed content, appending it to the store */\n  async fetch(options: FetchFeedOptions = {}) {\n    const { networkStatus, ...state } = this.store.getState();\n\n    // If the user is not authenticated, then do nothing\n    if (!this.knock.isAuthenticated()) {\n      this.knock.log(\"[Feed] User is not authenticated, skipping fetch\");\n      return;\n    }\n\n    // If there's an existing request in flight, then do nothing\n    if (isRequestInFlight(networkStatus)) {\n      this.knock.log(\"[Feed] Request is in flight, skipping fetch\");\n      return;\n    }\n\n    // Set the loading type based on the request type it is\n    state.setNetworkStatus(options.__loadingType ?? NetworkStatus.loading);\n\n    // trigger_data should be a JSON string for the API\n    // this function will format the trigger data if it's an object\n    // https://docs.knock.app/reference#get-feed\n    const formattedTriggerData = getFormattedTriggerData({\n      ...this.defaultOptions,\n      ...options,\n    });\n\n    // Always include the default params, if they have been set\n    const queryParams: FetchFeedOptionsForRequest = {\n      ...this.defaultOptions,\n      ...mergeDateRangeParams(options),\n      trigger_data: formattedTriggerData,\n      // Unset options that should not be sent to the API\n      __loadingType: undefined,\n      __fetchSource: undefined,\n      __experimentalCrossBrowserUpdates: undefined,\n    };\n\n    const result = await this.knock.client().makeRequest({\n      method: \"GET\",\n      url: `/v1/users/${this.knock.userId}/feeds/${this.feedId}`,\n      params: queryParams,\n    });\n\n    if (result.statusCode === \"error\" || !result.body) {\n      state.setNetworkStatus(NetworkStatus.error);\n\n      return {\n        status: result.statusCode,\n        data: result.error || result.body,\n      };\n    }\n\n    const response = {\n      entries: result.body.entries,\n      meta: result.body.meta,\n      page_info: result.body.page_info,\n    };\n\n    if (options.before) {\n      const opts = { shouldSetPage: false, shouldAppend: true };\n      state.setResult(response, opts);\n    } else if (options.after) {\n      const opts = { shouldSetPage: true, shouldAppend: true };\n      state.setResult(response, opts);\n    } else {\n      state.setResult(response);\n    }\n\n    // Legacy `messages.new` event, should be removed in a future version\n    this.broadcast(\"messages.new\", response);\n\n    // Broadcast the appropriate event type depending on the fetch source\n    const feedEventType: FeedEvent =\n      options.__fetchSource === \"socket\"\n        ? \"items.received.realtime\"\n        : \"items.received.page\";\n\n    const eventPayload = {\n      items: response.entries as FeedItem[],\n      metadata: response.meta as FeedMetadata,\n      event: feedEventType,\n    };\n\n    this.broadcast(eventPayload.event, eventPayload);\n\n    return { data: response, status: result.statusCode };\n  }\n\n  async fetchNextPage(options: FetchFeedOptions = {}) {\n    // Attempts to fetch the next page of results (if we have any)\n    const { pageInfo } = this.store.getState();\n\n    if (!pageInfo.after) {\n      // Nothing more to fetch\n      return;\n    }\n\n    this.fetch({\n      ...options,\n      after: pageInfo.after,\n      __loadingType: NetworkStatus.fetchMore,\n    });\n  }\n\n  get socketChannelTopic(): string {\n    return `feeds:${this.userFeedId}`;\n  }\n\n  private broadcast(\n    eventName: FeedEvent,\n    data: FeedResponse | FeedEventPayload,\n  ) {\n    this.broadcaster.emit(eventName, data);\n  }\n\n  // Invoked when a new real-time message comes in from the socket\n  private async onNewMessageReceived({ data }: FeedMessagesReceivedPayload) {\n    this.knock.log(\"[Feed] Received new real-time message\");\n\n    // Handle the new message coming in\n    const { items, ...state } = this.store.getState();\n    const currentHead: FeedItem | undefined = items[0];\n\n    // Optimistically set the badge counts\n    const metadata = data[this.referenceId]?.metadata;\n    if (metadata) {\n      state.setMetadata(metadata);\n    }\n\n    // Fetch the items before the current head (if it exists)\n    this.fetch({ before: currentHead?.__cursor, __fetchSource: \"socket\" });\n  }\n\n  private buildUserFeedId() {\n    return `${this.feedId}:${this.knock.userId}`;\n  }\n\n  private optimisticallyPerformStatusUpdate(\n    itemOrItems: FeedItemOrItems,\n    type: MessageEngagementStatus | \"unread\" | \"unseen\" | \"unarchived\",\n    attrs: object,\n    badgeCountAttr?: \"unread_count\" | \"unseen_count\",\n  ) {\n    const state = this.store.getState();\n    const normalizedItems = Array.isArray(itemOrItems)\n      ? itemOrItems\n      : [itemOrItems];\n    const itemIds = normalizedItems.map((item) => item.id);\n\n    if (badgeCountAttr) {\n      const { metadata } = state;\n\n      // We only want to update the counts of items that have not already been counted towards the\n      // badge count total to avoid updating the badge count unnecessarily.\n      const itemsToUpdate = normalizedItems.filter((item) => {\n        switch (type) {\n          case \"seen\":\n            return item.seen_at === null;\n          case \"unseen\":\n            return item.seen_at !== null;\n          case \"read\":\n          case \"interacted\":\n            return item.read_at === null;\n          case \"unread\":\n            return item.read_at !== null;\n          default:\n            return true;\n        }\n      });\n\n      // This is a hack to determine the direction of whether we're\n      // adding or removing from the badge count\n      const direction = type.startsWith(\"un\")\n        ? itemsToUpdate.length\n        : -itemsToUpdate.length;\n\n      state.setMetadata({\n        ...metadata,\n        [badgeCountAttr]: Math.max(0, metadata[badgeCountAttr] + direction),\n      });\n    }\n\n    // Update the items with the given attributes\n    state.setItemAttrs(itemIds, attrs);\n  }\n\n  private async makeStatusUpdate(\n    itemOrItems: FeedItemOrItems,\n    type: MessageEngagementStatus | \"unread\" | \"unseen\" | \"unarchived\",\n    metadata?: Record<string, string>,\n  ) {\n    // Always treat items as a batch to use the corresponding batch endpoint\n    const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];\n    const itemIds = items.map((item) => item.id);\n\n    const result = await this.knock.messages.batchUpdateStatuses(\n      itemIds,\n      type,\n      { metadata },\n    );\n\n    // Emit the event that these items had their statuses changed\n    // Note: we do this after the update to ensure that the server event actually completed\n    this.emitEvent(type, items);\n\n    return result;\n  }\n\n  private async makeBulkStatusUpdate(\n    status: BulkUpdateMessagesInChannelProperties[\"status\"],\n  ) {\n    // The base scope for the call should take into account all of the options currently\n    // set on the feed, as well as being scoped for the current user. We do this so that\n    // we ONLY make changes to the messages that are currently in view on this feed, and not\n    // all messages that exist.\n    const options = {\n      user_ids: [this.knock.userId!],\n      engagement_status:\n        this.defaultOptions.status !== \"all\"\n          ? this.defaultOptions.status\n          : undefined,\n      archived: this.defaultOptions.archived,\n      has_tenant: this.defaultOptions.has_tenant,\n      tenants: this.defaultOptions.tenant\n        ? [this.defaultOptions.tenant]\n        : undefined,\n    };\n\n    return await this.knock.messages.bulkUpdateAllStatusesInChannel({\n      channelId: this.feedId,\n      status,\n      options,\n    });\n  }\n\n  private setupBroadcastChannel() {\n    // Attempt to bind to listen to other events from this feed in different tabs\n    // Note: here we ensure `self` is available (it's not in server rendered envs)\n    this.broadcastChannel =\n      typeof self !== \"undefined\" && \"BroadcastChannel\" in self\n        ? new BroadcastChannel(`knock:feed:${this.userFeedId}`)\n        : null;\n\n    // Opt into receiving updates from _other tabs for the same user / feed_ via the broadcast\n    // channel (iff it's enabled and exists)\n    if (\n      this.broadcastChannel &&\n      this.defaultOptions.__experimentalCrossBrowserUpdates === true\n    ) {\n      this.broadcastChannel.onmessage = (e) => {\n        switch (e.data.type) {\n          case \"items:archived\":\n          case \"items:unarchived\":\n          case \"items:seen\":\n          case \"items:unseen\":\n          case \"items:read\":\n          case \"items:unread\":\n          case \"items:all_read\":\n          case \"items:all_seen\":\n          case \"items:all_archived\":\n            // When items are updated in any other tab, simply refetch to get the latest state\n            // to make sure that the state gets updated accordingly. In the future here we could\n            // maybe do this optimistically without the fetch.\n            return this.fetch();\n          default:\n            return null;\n        }\n      };\n    }\n  }\n\n  private broadcastOverChannel(type: string, payload: GenericData) {\n    // The broadcastChannel may not be available in non-browser environments\n    if (!this.broadcastChannel) {\n      return;\n    }\n\n    // Here we stringify our payload and try and send as JSON such that we\n    // don't get any `An object could not be cloned` errors when trying to broadcast\n    try {\n      const stringifiedPayload = JSON.parse(JSON.stringify(payload));\n\n      this.broadcastChannel.postMessage({\n        type,\n        payload: stringifiedPayload,\n      });\n    } catch (e) {\n      console.warn(`Could not broadcast ${type}, got error: ${e}`);\n    }\n  }\n\n  private initializeRealtimeConnection() {\n    // In server environments we might not have a socket connection\n    if (!this.socketManager) return;\n\n    // If we're initializing but they have previously opted to listen to real-time updates\n    // then we will automatically reconnect on their behalf\n    if (this.hasSubscribedToRealTimeUpdates && this.knock.isAuthenticated()) {\n      this.unsubscribeFromSocketEvents = this.socketManager?.join(this);\n    }\n  }\n\n  async handleSocketEvent(payload: SocketEventPayload) {\n    switch (payload.event) {\n      case SocketEventType.NewMessage:\n        this.onNewMessageReceived(payload);\n        return;\n      default: {\n        const _exhaustiveCheck: never = payload.event;\n        return;\n      }\n    }\n  }\n\n  private emitEvent(\n    type:\n      | MessageEngagementStatus\n      | \"all_read\"\n      | \"all_seen\"\n      | \"all_archived\"\n      | \"unread\"\n      | \"unseen\"\n      | \"unarchived\",\n    items: FeedItem[],\n  ) {\n    // Handle both `items.` and `items:` format for events for compatibility reasons\n    this.broadcaster.emit(`items.${type}`, { items });\n    this.broadcaster.emit(`items:${type}`, { items });\n    // Internal events only need `items:`\n    this.broadcastOverChannel(`items:${type}`, { items });\n  }\n}\n\nexport default Feed;\n"],"names":["feedClientDefaults","CLIENT_REF_ID_PREFIX","Feed","knock","feedId","options","socketManager","__publicField","isValidUuid","nanoid","createStore","EventEmitter","mergeDateRangeParams","_a","eventName","callback","itemOrItems","now","metadata","items","state","attrs","itemIds","item","result","shouldOptimisticallyRemoveItems","unseenCount","i","unreadCount","updatedMetadata","entriesToSet","remainingItems","networkStatus","isRequestInFlight","NetworkStatus","formattedTriggerData","getFormattedTriggerData","queryParams","response","opts","feedEventType","eventPayload","pageInfo","data","currentHead","type","badgeCountAttr","normalizedItems","itemsToUpdate","direction","status","e","payload","stringifiedPayload","SocketEventType"],"mappings":"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"}