Giter Site home page Giter Site logo

stream-chat-ruby's Introduction

Official Ruby SDK for Stream Chat

build Gem Version

Official Ruby API client for Stream Chat, a service for building chat applications.
Explore the docs »

Code Samples · Report Bug · Request Feature

📝 About Stream

You can sign up for a Stream account at our Get Started page.

You can use this library to access chat API endpoints server-side.

For the client-side integrations (web and mobile) have a look at the JavaScript, iOS and Android SDK libraries (docs).

⚙️ Installation

stream-chat-ruby supports:

  • Ruby (2.7, 3.0, 3.1)
$ gem install stream-chat-ruby

✨ Getting started

require 'stream-chat'

client = StreamChat::Client.new('STREAM_KEY', 'STREAM_SECRET')

💡 Note: since v2.21.0 we implemented Sorbet type checker. As of v2.x.x we only use it for static type checks and you won't notice any difference, but from v3.0.0 we will enable runtime checks 🚨 🚨 🚨.

What this means, is that you'll receive an error during runtime if you pass an invalid type to our methods. To prepare for that, just make sure whatever you pass in, matches the method signature (sig { ... }).

Update (2022-May-24): we have relased v3.0.0 with enabled runtime checks.


Additionally, in a future major version, we would like to enforce symbol hash keys during runtime to conform to Ruby best practises. It's a good idea to prepare your application for that.

# Wrong:
user = { "user" => { "id" => "bob-1"}}
# Correct:
user = { user: { id: "bob-1" }}

Generate a token for client-side usage:

client.create_token('bob-1')

Create/Update users

client.upsert_user({
    id: 'bob-1',
    role: 'admin',
    name: 'Robert Tables'
})

# Batch update is also supported
jane = {id: 'jane-1'}
june = {id: 'june-1'}
client.upsert_users([jane, june])

Channel types

client.create_channel_type({
    name: 'livechat',
    automod: 'disabled',
    commands: ['ban'],
    mutes: true
})

channel_types = client.list_channel_types()

Channels

# Create a channel with members from the start
chan = client.channel("messaging", channel_id: "bob-and-jane", data: {:members => ['bob-1', 'jane-77']})
chan.create('bob-1')

# Create a channel and then add members
chan = client.channel("messaging", channel_id: "bob-and-jane")
chan.create('bob-1')
chan.add_members(['bob-1', 'jane-77'])

Reactions

chan.send_reaction(m1['id'], {type: 'like'}, 'bob-1')

Moderation

chan.add_moderators(['jane-77'])
chan.demote_moderators(['bob-1'])

chan.ban_user('bob-1', timeout: 30)

chan.unban_user('bob-1')

Messages

m1 = chan.send_message({text: 'Hi Jane!'}, 'bob-1')

deleted_message = client.delete_message(m1['message']['id'])

Devices

jane_phone = client.add_device({:id => 'iOS Device Token', :push_provider => push_provider.apn, :user_id => 'jane-77'})

client.get_devices('jane-77')

client.remove_device(jane_phone['id'], jane_phone['user_id'])

Blocklists

client.create_blocklist('my_blocker', %w[fudge cream sugar])

# Enable it on 'messaging' channel type
client.update_channel_type('messaging', blocklist: 'my_blocker', blocklist_behavior: 'block')

client.get_blocklist('my_blocker')

client.delete_blocklist('my_blocker')

Export Channels

# Register an export
response = client.export_channels({type: 'messaging', id: 'jane'})

# Check completion
status_response = client.get_export_channel_status(response['task_id'])
# status_response['status'] == 'pending', 'completed'

Campaigns

# Create a user or channel segment
client.create_segment({ name: 'test', type: 'user', filter: { uniq: 'a flag on users' } })

# Create a campaign that uses the segment
client.create_campaign({
    name: 'test',
    text: 'Hi',
    sender_id: campaign_sender,
    segment_id: segment_id,
    channel_type: 'messaging'
})

# Schedule the campaign
client.schedule_campaign(campaign_id, Time.now.to_i)

# Query the campaign to check the status
response = client.query_campaigns(filter_conditions: { id: campaign_id })
response['campaigns'][0]['status'] == 'completed'

# Read sent information
client.query_recipients(filter_conditions: { campaign_id: campaign_id })

Rate limits

# Get all rate limits
limits = client.get_rate_limits

# Get rate limits for specific platform(s)
limits = client.get_rate_limits(server_side: true)

# Get rate limits for specific platforms and endpoints
limits = client.get_rate_limits(android: true, ios: true, endpoints: ['QueryChannels', 'SendMessage'])

✍️ Contributing

We welcome code changes that improve this library or fix a problem, please make sure to follow all best practices and add tests if applicable before submitting a Pull Request on Github. We are very happy to merge your code in the official repository. Make sure to sign our Contributor License Agreement (CLA) first. See our license file for more details.

Head over to CONTRIBUTING.md for some development tips.

🧑‍💻 We are hiring!

We've recently closed a $38 million Series B funding round and we keep actively growing. Our APIs are used by more than a billion end-users, and you'll have a chance to make a huge impact on the product within a team of the strongest engineers all over the world.

Check out our current openings and apply via Stream's website.

stream-chat-ruby's People

Contributors

bdandy avatar betterwithranch avatar bogdan-d avatar bogdan-getstream avatar dependabot[bot] avatar ferhatelmas avatar ffenix113 avatar ghiculescu avatar github-actions[bot] avatar gumuz avatar jpalumickas avatar keyneston avatar krainboltgreene avatar kyohah avatar malbert avatar marco-ulge avatar miagilepner avatar peterdeme avatar reachire-smendola avatar rickypanzer avatar robindaugherty avatar ruggi avatar tbarbugli avatar thesyncim avatar totalimmersion avatar vishalnarkhede avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

stream-chat-ruby's Issues

update_user example fails

Running

client.update_user({
  'id' => 'bob-1',
  'role' => 'admin',
  'name' => 'Robert Tables'
})

, as seen in the README, fails with error users[] is a required field.

Details of what I did:

% docker run -it --rm ruby:2.5 bash
root@2d2208282bd1:/# gem install stream-chat-ruby
Fetching stream-chat-ruby-1.1.0.gem
Fetching faraday-1.0.0.gem
Fetching jwt-2.2.1.gem
Fetching multipart-post-2.1.1.gem
Successfully installed multipart-post-2.1.1
Successfully installed faraday-1.0.0
Successfully installed jwt-2.2.1
Successfully installed stream-chat-ruby-1.1.0
4 gems installed
root@2d2208282bd1:/# irb
irb(main):001:0> require 'stream-chat'
=> true
irb(main):002:0> client = StreamChat::Client.new(api_key="abcdefg", api_secret="12346789jeie")
=> #<StreamChat::Client:0x000055b47099a528 @api_key="abcdefg", @api_secret="12346789jeie", @timeout=6.0, @options={}, @auth_token="mklelkmqlkfmql", @base_url="https://chat-us-east-1.stream-io-api.com", @conn=#<Faraday::Connection:0x000055b470999808 @parallel_manager=nil, @headers={"User-Agent"=>"Faraday v1.0.0"}, @params={}, @options=#<Faraday::RequestOptions timeout=6.0, open_timeout=6.0>, @ssl=#<Faraday::SSLOptions (empty)>, @default_parallel_manager=nil, @builder=#<Faraday::RackBuilder:0x000055b470999358 @adapter=Faraday::Adapter::NetHttp, @handlers=[Faraday::Request::Multipart]>, @url_prefix=#<URI::HTTPS https://chat-us-east-1.stream-io-api.com/>, @manual_proxy=false, @proxy=nil>>
irb(main):003:0> client.update_user({
irb(main):004:2*     'id' => 'bob-1',
irb(main):005:2*     'role' => 'admin',
irb(main):006:2*     'name' => 'Robert Tables'
irb(main):007:2> })
#<Faraday::Response:0x000055b470ad6220 @on_complete_callbacks=[], @env=#<Faraday::Env @method=:post @request_body="{\"users\":{\"\":{\"id\":\"bob-1\",\"role\":\"admin\",\"name\":\"Robert Tables\"}}}" @url=#<URI::HTTPS https://chat-us-east-1.stream-io-api.com/users?api_key=abcdefg> @request=#<Faraday::RequestOptions timeout=6.0, open_timeout=6.0> @request_headers={"User-Agent"=>"Faraday v1.0.0", "Content-type"=>"application/json", "X-stream-client"=>"stream-ruby-client-1.1.0", "Authorization"=>"mklelkmqlkfmql", "stream-auth-type"=>"jwt"} @ssl=#<Faraday::SSLOptions verify=true> @response=#<Faraday::Response:0x000055b470ad6220 ...> @response_headers={"server"=>"fasthttp", "date"=>"Wed, 25 Mar 2020 10:07:52 GMT", "content-type"=>"application/json;charset=utf-8", "content-length"=>"183", "x-ratelimit-limit"=>"300", "access-control-allow-origin"=>"*", "access-control-max-age"=>"86400", "cache-control"=>"no-cache", "access-control-allow-headers"=>"x-requested-with, content-type, accept, origin, authorization, x-csrftoken, x-stream-client, stream-auth-type", "access-control-allow-methods"=>"GET, POST, PUT, PATCH, DELETE, OPTIONS", "x-ratelimit-remaining"=>"299", "x-ratelimit-reset"=>"1585130880", "connection"=>"close"} @status=400 @reason_phrase="Bad Request" @response_body="{\"code\":4,\"message\":\"UpdateUsers failed with error: \\\"users[] is a required field\\\"\",\"exception_fields\":{\"users[]\":\"users[] is a required field\"},\"StatusCode\":400,\"duration\":\"0.00ms\"}">>
Traceback (most recent call last):
        7: from /usr/local/bin/irb:11:in `<main>'
        6: from (irb):3
        5: from /usr/local/bundle/gems/stream-chat-ruby-1.1.0/lib/stream-chat/client.rb:101:in `update_user'
        4: from /usr/local/bundle/gems/stream-chat-ruby-1.1.0/lib/stream-chat/client.rb:97:in `update_users'
        3: from /usr/local/bundle/gems/stream-chat-ruby-1.1.0/lib/stream-chat/client.rb:254:in `post'
        2: from /usr/local/bundle/gems/stream-chat-ruby-1.1.0/lib/stream-chat/client.rb:336:in `make_http_request'
        1: from /usr/local/bundle/gems/stream-chat-ruby-1.1.0/lib/stream-chat/client.rb:312:in `parse_response'
StreamChat::StreamAPIException (StreamChat::StreamAPIException)

Relax dependency versions

Hello,

In 3.1.0 all dependency versions was strictly set, but other gems using also these dependencies, so we cannot normally upgrade stream-chat-ruby. It's better to relax to minor version, like this faraday ~> 1.10 so other gems can be upgraded also

Generated Token not accepted through Swift SDK?

Hi there,

we recently tried to switch from development tokens to actual real tokens for final testing and found out, that the token I generate through this method:

def create_token(user_id, exp = nil)
payload = {user_id: user_id}
if exp != nil
payload['exp'] = exp
end
JWT.encode(payload, @api_secret, 'HS256')
end

Unfortunately if we use this token via the Swift SDK the requests will be rejected with a 401 and the message that the signature of this token is not valid. Validating this ourselves by using https://jwt.io/#debugger-io is looking good.

Maybe we are doing something wrong but as I am only forwarding the generated token to the client, and the token is not even set-up to expire in the future, we are kind of clueless now.

JWT format error on create_token method

Hi there,

I was comparing the JWT token generated by the create_token method, and it doesn't match with the one generated by this test site: https://getstream.io/chat/docs/javascript/token_generator/

if you decode both tokens using https://jwt.io/, there is a small difference on the decoded value.
The header from the getstream.io token has "typ": "JWT", while the generated on this library doesn't.

This difference makes the token different and it fails when you try to use it on the frontend.
I've tried generating the token using this code, by adding the typ: 'JWT' and now it works:

JWT.encode(
      {
        user_id: 'my-user-id'
      },
      @client.api_secret,
      'HS256',
      {
        typ: 'JWT'
      }
    )

`StreamChannelException not defined`

https://github.com/GetStream/stream-chat-ruby/blob/master/lib/stream-chat/channel.rb#L22

    def url
      raise StreamChannelException 'channel does not have an id' if @id.nil?

      "channels/#{@channel_type}/#{@id}"
    end

When that line gets run, it raises:

undefined method `StreamChannelException' for #StreamChat::Channel:0x00007ffd46faff50

instead of the intended exception.

I only came across it because I'm working on a Rails project that uses your gem and am using pretty imprecise WebMock.stub_request calls in my test suite.

So, I might be hitting state that you would be incredibly unlikely to hit in the real world and maybe you don't have covered in your test suite. :)

Sorbet issue with setting T::Configuration.default_checked_level

In #83, sorbet was added to this gem. As part of that work T::Configuration.default_checked_level was being set to :never.

But setting that config in a gem causes issues for an application the uses it and also uses sorbet. Before stream-chat-ruby is loaded, the default_checked_level is already read by the application's code. Then when stream-chat-ruby is loaded and tries to set this config, an exception is raised
https://github.com/sorbet/sorbet/blob/0c24167b7a409a64b7f6c3a410d647ff7e1cb52a/gems/sorbet-runtime/lib/types/private/runtime_levels.rb#L53-L55

Gem is misnamed causing additional work during implementation

The main file in this gem is lib/stream-chat.rb. The gemspec is named stream-chat.gemspec.

The "-ruby" part of the name is redundant in a Ruby gem name. With all that in mind, the gem should have been published under the name stream-chat. That name is not in use in Rubygems, so that is currently possible.

By not having the name of the gem not matching the name of the main file, the additional step of calling `require "stream-chat" is necessary. That's not standard or expected in the Ruby world, it would only be expected if the gem has files that should only be included under limited and specific conditions, like test harnesses.

So here are two possible paths to improve this:

Change the name of the gem:

  1. Change the name in the gemspec file to stream-chat
  2. Update the README to use the new name in the Installation section, and remove the require call in the Getting Started section
  3. Publish new versions to rubygems under the new name (and potentially also the old name)

Downside of simply renaming the gem going forward is that those using the gem under the old name would no longer receive new versions of the gem, unless it's published under both names going forward.

Remove the need for an additional require call:

  1. Add a file named lib/stream-chat-ruby.rb which calls require "stream-chat"
  2. Update the README to remove the require call in the Getting Started section

Get unread messages from an user in a channel

Hi guys,

I don't know if here is the best place to get some help with that, but I couldn't find anywhere something related to that. Is there a way to get the unread_messages from one specific user in a channel?

Response headers are not available on successful requests

Currently, if an api request is succesful, the response body is parsed and returned, but the response headers are discarded. But, if an exception is returned, the response object is returned, making the response headers available.

Since the current api rate limit status is included in the response headers, there is no way to proactively monitor the api rate limits prior to getting an exception.

Obviously, we couldn't change the return values without breaking every method's interface, but maybe we could store it in the client so it could be accessed by a separate method?

Something like this, maybe?

client = StreamChat::Client.new('key', 'secret')
client.query_channels({id: 'channelid'})
client.rate_limits  # => { "query_channels": { limit: 10000, remaining: 9996, reset: 1587587580 })

bug: client.create_token returning a token but silently failing to create a user

Describe the bug

I've got code which, on visiting the "messenger" page for the first time, attempts to create a user and add them to a default list of channels.
I create a token using client.create_token, then attempt to add the user to a few channels.

However, I'm getting the exception

StreamChat::StreamAPIException (StreamChat error code 4: UpdateChannel failed with error: "The following users are involved in channel update operation, but don't exist: [94461a35e0a73734438dba5ba10bbb74]. Please create the user objects before setting up the channel.")

client.create_token returns a 200 success and also returns a token.
The only exception I get is when I consequently try to add the user, using my manually generated user_id, to channels.

This was working yesterday - my code around user creation doesn't seem to have changed.

To Reproduce

  1. Create a user using client.create_token(id) (I'm using id = SecureRandom.hex(16))
  2. Attempt to add user using channel.add_members([id])

Expected behavior

The create_token endpoint should:

  1. create a user if params are valid (which in this case they are); AND
  2. return an error if params are invalid OR the server is experience issues, so error handling can be handled

Package version

stream-chat-react: 10.14.0
stream-chat-css: 3.13.0

Desktop (please complete the following information):

OS: MacOS Ventura 13.2
Browser: Chrome
Version: 118.0.5993.70

Additional context

require "securerandom"

class ChatsController < ApplicationController
  # ...

  def show
    # NOTE: I've checked this ENV vars are set correctly
    if (ENV["STREAM_API_KEY"] && ENV["STREAM_API_SECRET"]).present?
      if current_user.stream_user_id.nil?
        @stream_user_id = SecureRandom.hex(16)
        # NOTE: the line below fails silently
        @stream_user_token = StreamChatClient.create_stream_user(
          id: @stream_user_id
        )

        current_user.update!(
          stream_user_id: @stream_user_id,
          stream_user_token: @stream_user_token
        )
      else
        @stream_user_id = current_user.stream_user_id
        @stream_user_token = current_user.stream_user_token
      end

      add_current_user_to_channels
    else
      # ...
    end
  end

  private

  def add_current_user_to_channels
    [
      { type: "messaging", id: "general" },
      { type: "messaging", id: "feedback-and-requests" },
      { type: "messaging", id: "bugs" },
    ].each do |channel|
      channel = StreamChatClient.get_channel(
        type: channel[:type],
        channel_id: channel[:id],
      )
      unless StreamChatClient.channel_include?(
          channel: channel,
          user_id: current_user.stream_user_id
      )
        StreamChatClient.add_member(
          channel: channel,
          user_id: current_user.stream_user_id
        )
      end
    end
  end
end

My StreamChatClient class:

require "stream-chat"

class StreamChatClient
  def self.add_member(channel:, user_id:)
    channel.add_members([user_id])
  end

  def self.channel_include?(channel:, user_id:)
    members = channel.query_members()["members"]
    members.map{ |member| member["user_id"] }.include?(user_id)
  end

  def self.get_channel(type:, channel_id:)
    client.channel(type, channel_id: channel_id)
  end

  def self.create_stream_user(id:)
    client.create_token(id)
  end

  private

  def self.client
    StreamChat::Client.new(
      api_key=ENV["STREAM_API_KEY"],
      api_secret=ENV["STREAM_API_SECRET"]
    )
  end
end

Creating channel without ID for Team

Hello,

When creating channels with a multi-tenant team parameter, I noticed that the team attribute doesn't seem to save the associated if the Channel ID given is empty?

Works:

chan = @client.channel('messaging', channel_id: 'kevin-nikki', data: {'members'=> ['123', '456'], 'team' => '789'})

Does Not Work:

chan = @client.channel('messaging', channel_id: nil, data: {'members'=> ['123','456'], 'team' => '789'})

Gem completely breaks with latest version of Faraday.

This gems gemspec needs to specify a stricter version for Faraday because it completely breaks with the latest 2.X version.

no implicit conversion of nil into String
/Users/miketaylor/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/json-2.6.2/lib/json/common.rb:216:in `initialize'
/Users/miketaylor/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/json-2.6.2/lib/json/common.rb:216:in `new'
/Users/miketaylor/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/json-2.6.2/lib/json/common.rb:216:in `parse'
/Users/miketaylor/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/stream-chat-ruby-3.0.0/lib/stream-chat/client.rb:973:in `parse_response'
/Users/miketaylor/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.10148/lib/types/private/methods/call_validation.rb:161:in `bind_call'

Screen Shot 2022-08-12 at 10 29 03 AM

Error handling format does not match actual api format

StreamChat::StreamAPIException constructor parses incorrect response format. The code is expecting a format like this

 {
   "data": {
      "code": 4,
      "message": "UpdateChannel failed ...",
   }
 }

but the actual response is

{
   "code": 4,
   "message": "UpdateChannel failed ..."
}

As a result, these values are set to the defaults of unknown in all exceptions and no details are provided for logging or exception notifications.

Support for multi-tenant mode

Hello,

Do you know if there is a way to support multi-tenant mode when creating channels to pass in the array of teams: [] to the request?

Thanks,

Querying members with a filter doesn't work

Describe the bug

channel.query_members(
  filter_conditions: { id: { "$in" => [user_id] } }
)

always returns all members when using the Ruby client.
In this case user_id = SecureRandom.hex.

I've used byebug to double check a valid user_id was sent through in the filter_conditions yet I kept seeing a list of all users back.

To Reproduce

  1. Create 2 users in Stream (I used the API to create users)
  2. Use the filter_conditions: { id: { "$in" => [user_id] } } in query_members
  3. Inspect the response

Expected behavior

Only the user which matches user_id to be returned

Package version

stream-chat-ruby (3.0.0)

Desktop (please complete the following information):

  • OS: MacOS Ventura 13.2
  • Browser: Chrome
  • Version: 118.0.5993.70

Additional context

N/A

Improve release workflow

  • We currently don't include chore commits in changelog, but they are also useful to understand the changes.
  • Also we should replace our release handling to release-please, as mentioned on the page of the package that we currently use to generate changelog.
  • We now include Gemfile.lock in git, and it also must be updated when creating a release.

File upload not working

This is send_file method:

def send_file(relative_url, file_url, user, content_type = 'application/octet-stream')
  url = [@base_url, relative_url].join('/')

  file = open(file_url)
  body = {user: user.to_json}

  body[:file] = Faraday::UploadIO.new(file, content_type)

  response = @conn.post url do |req|
    req.headers["X-Stream-Client"] = get_user_agent
    req.headers['Authorization'] = @auth_token
    req.headers["stream-auth-type"] = "jwt"
    req.params = get_default_params
    req.body = body
  end

  parse_response(response)
end

file_url is the absolute (and public) path to the asset I want to upload.
But, where am I supposed to point the relative_url ?
It is used to create the POST /endpoint for Faraday request...

Also the documentation is not clear about this.

Identify Failures From Rate Limiting

Hi!

We have a cron job that runs every evening syncing the chat configuration for all of our users. We saw that some of these requests would fail because of 429, according to the log available in the Stream dashboard
Screenshot 2024-03-05 at 2 13 41 PM

I tried to repeatedly calling the following code to reproduce the rate limiting

 response = channel.update_partial({
    'name' => name,
    'image' => image,
 })
puts response

I was able to get the 429s to show up in the dashboard, but the responses being printed out don't seem to be any different from the normal ones. There was no exception being thrown by the SDK either, which I think would have been the better way for handling failed requests.

What's the recommended way to check if individual requests have failed because of rate limiting based on the response?

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.