Search code examples
getstream-io

Stream.io Chat API - Member state omitted from queryChannels response


When I do the following:

      const queryChannelsResponse = await this.client.queryChannels(
        { id: channelId },
        { last_updated_at: -1 },
        { state: true },

This does not include the members. How am I able to get the member information?

I am writing a webhook and I want it to send a push notification (I am currently sending these myself via expo) to all offline users.

I am migrating from pusher chatkit which is now being discontinued. They had a new_message_users_offline hook for this purpose.

In the message.new webhook payload in the documentation the members are present but they are not present in the request body:

{
    "type": "message.new",
    "cid": "messaging:394f36fd-d512-4f2b-a785-ab8dfe82af49",
    "message": {
        "id": "f73ee1a8-f6fd-450b-bc64-0840b4df8fd9-2b4908ad-e267-4c48-8f41-8c26c8f769ce",
        "text": "Ffddf",
        "html": "<p>Ffddf</p>\n",
        "type": "regular",
        "user": {
            "id": "f73ee1a8-f6fd-450b-bc64-0840b4df8fd9",
            "role": "user",
            "created_at": "2020-04-06T14:06:37.979584Z",
            "updated_at": "2020-04-06T19:45:39.556842Z",
            "last_active": "2020-04-06T19:45:39.54939Z",
            "banned": false,
            "online": true,
            "name": "Mark Everett",
            "image": "https://8dc-user-files-dev.s3.eu-west-1.amazonaws.com/MEMBER_PROFILE_IMAGE-f73ee1a8-f6fd-450b-bc64-0840b4df8fd9.png?v=6"
        },
        "attachments": [],
        "latest_reactions": [],
        "own_reactions": [],
        "reaction_counts": null,
        "reaction_scores": {},
        "reply_count": 0,
        "created_at": "2020-04-06T19:51:14.114803Z",
        "updated_at": "2020-04-06T19:51:14.114803Z",
        "mentioned_users": []
    },
    "user": {
        "id": "f73ee1a8-f6fd-450b-bc64-0840b4df8fd9",
        "role": "user",
        "created_at": "2020-04-06T14:06:37.979584Z",
        "updated_at": "2020-04-06T19:45:39.556842Z",
        "last_active": "2020-04-06T19:45:39.54939Z",
        "banned": false,
        "online": true,
        "channel_unread_count": 0,
        "channel_last_read_at": "1970-01-01T00:00:00Z",
        "total_unread_count": 0,
        "unread_channels": 0,
        "unread_count": 0,
        "image": "https://8dc-user-files-dev.s3.eu-west-1.amazonaws.com/MEMBER_PROFILE_IMAGE-f73ee1a8-f6fd-450b-bc64-0840b4df8fd9.png?v=6",
        "name": "Mark Everett"
    },
    "watcher_count": 1,
    "created_at": "2020-04-06T19:51:14.121213459Z",
    "channel_type": "messaging",
    "channel_id": "394f36fd-d512-4f2b-a785-ab8dfe82af49"
}

My plan is do do something like this:

  public async getOfflineUserIds(channelId: string): Promise<string[]> {
    try {
      // Get the channel
      const queryChannelsResponse = await this.client.queryChannels(
        { id: channelId },
        { last_updated_at: -1 },
        { message_limit: 0, limit: 1, state: true},
      )
      const channel = queryChannelsResponse[0]

      console.log('channel: ', channel)

      // Get the channels members
      const userIds: string[] = []
      // tslint:disable-next-line: forin
      for (const index in channel.state.members) {
        userIds.push(channel.state.members[index].user_id)
      }
      console.log('userIds:', userIds)

      const queryUsersResponse = await this.client.queryUsers(
        { id: { $in: userIds } },
        { last_active: -1 },
        {},
      )

      console.log('queryUsersResponse:', queryUsersResponse)

      // Work out who is offline/online
      const offlineUserIds = queryUsersResponse.users
        .filter(u => !u.online)
        .map(u => u.id)

      return offlineUserIds
    } catch (err) {
      throw new InternalServerErrorException(
        'Error getting offline users for channel.',
        err,
      )
    }
  }

Solution

  • This is now resolved.

    I did not add the members to the channel with channel.addMembers. I create and add members on the server as this works perfectly for my use case.

    If it helps anyone I ended up with these two methods:

      public async getChannelUserIds(channelId: string): Promise<string[]> {
        try {
          const queryChannelsResponse = await this.client.queryChannels(
            { id: channelId },
            { last_updated_at: -1 },
            { message_limit: 0, limit: 1, state: true },
          )
          const channel = queryChannelsResponse[0]
    
          const userIds = Object.keys(channel.state.members)
          console.log('userIds:', userIds)
    
          return userIds
        } catch (err) {
          throw new InternalServerErrorException(
            `Error getting user ids for channel ('${channelId}').`,
            err,
          )
        }
      }
    
      public async getOfflineUserIds(userIds: string[]): Promise<string[]> {
        try {
          const queryUsersResponse = await this.client.queryUsers(
            { id: { $in: userIds } },
            { last_active: -1 },
            {},
          )
    
          console.log('queryUsersResponse:', queryUsersResponse)
    
          const offlineUserIds = queryUsersResponse.users
            .filter(u => !u.online)
            .map(u => u.user_id)
    
          return offlineUserIds
        } catch (err) {
          throw new InternalServerErrorException(
            `Error getting offline user ids from ('${JSON.stringify(
              userIds,
              null,
              2,
            )}').`,
            err,
          )
        }
      }
    

    And then in my webhook I:

    @Post('stream/messages')
      public async onReceive(
        @Req() req: Request,
        @Headers('x-signature') signature: string,
        @Body() body: any,
      ) {
        try {
          console.debug('webhooks-stream.messages.onReceive')
          this.chatService.verifyWebhook((req as any).rawBody, signature)
    
          console.log('DEBUG WEBHOOK BODY', JSON.stringify(body, null, 2))
    
          switch (body.type) {
            case 'message.new': {
              const offlineMemberIds = await this.chatService.getOfflineUserIds(
                body.members.map(member => member.user_id),
              )
    
          ...