GithubHelp home page GithubHelp logo

getstream / stream-net Goto Github PK

View Code? Open in Web Editor NEW
34.0 35.0 8.0 770 KB

NET Client - Build Activity Feeds & Streams with GetStream.io

Home Page: https://getstream.io

License: BSD 3-Clause "New" or "Revised" License

C# 99.46% Shell 0.30% JavaScript 0.24%
dotnet stream-net c-sharp activity-feed getstream-io notification-feed timeline feed news-feed

stream-net's Introduction

Official .NET SDK for Stream

Build status NuGet Badge

Official .NET API client for Stream, a service for building scalable newsfeeds and activity streams.
Explore the docs »

Code Samples · Report Bug · Request Feature

🚨 Breaking changes in v5.0 <

In v5.0.0, we have refactored the library to be more maintainable in the future. Most importantly, we got rid of some complex internal logic (such as tricky json serialization and deserialization, code organization improvements etc.). Also, we made the library more modern such as adding Async postfix to async methods. All public methods have documentation now and a link to the official docs now. This README file's code snippets are updated to reflect the new changes.

Breaking changes:

  • All async methods have Async postfix.
  • Model classes have been moved into Stream.Models namespace.
  • All client classes have interfaces now, and Ref() methods are not static anymore. This will make it easier to the consumers of this library to unit test them.

📝 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).

💡 Note: this is a library for the Feeds product. The Chat SDKs can be found here.

⚙️ Installation

$ nuget install stream-net

✨ Getting started

// Create a client, find your API keys here https://getstream.io/dashboard/
var client = new StreamClient("YOUR_API_KEY", "API_KEY_SECRET");
// From this IStreamClient instance you can access a variety of API endpoints,
// and it is also a factory class for other classes, such as IBatch, ICollections
// IReactions, IStreamFeed etc. Note: all of these clients can be used as a
// singleton as they do not store state.
// Let's take a look at IStreamFeed now.

// Reference a feed
var userFeed1 = client.Feed("user", "1");

// Get 20 activities starting from activity with id last_id (fast id offset pagination)
var results = await userFeed1.GetActivitiesAsync(0, 20, FeedFilter.Where().IdLessThan(last_id));

// Get 10 activities starting from the 5th (slow offset pagination)
var results = await userFeed1.GetActivitiesAsync(5, 10);

// Create a new activity
var activity = new Activity("1", "like", "3")
{
    ForeignId = "post:42"
};

await userFeed1.AddActivityAsync(activity);

// Create a complex activity
var activity = new Activity("1", "run", "1")
{
    ForeignId = "run:1"
};
var course = new Dictionary<string, object>();
course["name"] = "Shevlin Park";
course["distance"] = 10;
activity.SetData("course", course);
await userFeed1.AddActivityAsync(activity);

// Remove an activity by its id
await userFeed1.RemoveActivityAsync("e561de8f-00f1-11e4-b400-0cc47a024be0");

// Remove activities by their foreign_id
await userFeed1.RemoveActivityAsync("post:42", true);

// Let user 1 start following user 42's flat feed
await userFeed1.FollowFeedAsync("flat", "42");

// Let user 1 stop following user 42's flat feed
await userFeed1.UnfollowFeedAsync("flat", "42");

// Retrieve first 10 followers of a feed
await userFeed1.FollowersAsync(0, 10);

// Retrieve 2 to 10 followers
await userFeed1.FollowersAsync(2, 10);

// Retrieve 10 feeds followed by $user_feed_1
await userFeed1.FollowingAsync(0, 10);

// Retrieve 10 feeds followed by $user_feed_1 starting from the 10th (2nd page)
await userFeed1.FollowingAsync(10, 20);

// Check if $user_feed_1 follows specific feeds
await userFeed1.FollowingAsync(0, 2, new[] { "user:42", "user:43" });

// Follow stats
// Count the number of users following this feed
// Count the number of tags are followed by this feed
var stats = await userFeed1.FollowStatsAsync(new[] { "user" }, new[] { "tags" });
Console.WriteLine(stats.Results.Followers.Count);
Console.WriteLine(stats.Results.Following.Count);

// Retrieve activities by their ids
var ids = new[] { "e561de8f-00f1-11e4-b400-0cc47a024be0", "a34ndjsh-00f1-11e4-b400-0c9jdnbn0eb0" };
var activities = await client.Batch.GetActivitiesByIdAsync(ids);

// Retrieve activities by their ForeignID/Time
var foreignIDTimes = new[] { new ForeignIdTime("fid-1", DateTime.Parse("2000-08-19T16:32:32")), new ForeignIdTime("fid-2", DateTime.Parse("2000-08-21T16:32:32")) };
var activities = await client.Batch.GetActivitiesByForeignIdAsync(foreignIDTimes);

// Partially update an activity
var set = new Dictionary<string, object>();
set.Add("custom_field", "new value");
var unset = new[] { "field to remove" };

// By id
await client.ActivityPartialUpdateAsync("e561de8f-00f1-11e4-b400-0cc47a024be0", null, set, unset);

// By foreign id and time
var fidTime = new ForeignIdTime("fid-1", DateTime.Parse("2000-08-19T16:32:32"));
await client.ActivityPartialUpdateAsync(null, fidTime, set, unset);

// Add a reaction to an activity
var activityData = new Activity("bob", "cook", "burger")
{
    ForeignId = "post:42"
};
var activity = await userFeed1.AddActivityAsync(activity);
var r = await client.Reactions.AddAsync("comment", activity.Id, "john");

// Add a reaction to a reaction
var child = await client.Reactions.AddChildAsync(r, "upvote", "john");

// Enrich feed results
var userData = new Dictionary<string, object>()
{
    {"is_admin", true},
    {"nickname","bobby"}
};
var u = await client.Users.AddAsync("timmy", userData);
var userRef = u.Ref();
var a = new Activity(userRef, "add", "post");
var plainActivity = await userFeed1.AddActivityAsync(a);
// Here plainActivity.Actor is just a plain string containing the user ref
var enriched = await userFeed1.GetEnrichedFlatActivitiesAsync();
var actor = enriched.Results.First();
var userID = actor.GetData<string>("id");
var data = actor.GetData<Dictionary<string, object>>("data"); //this is `userData`

// Enrich feed results with reactions
var activityData = new Activity("bob", "cook", "burger")
{
    ForeignId = "post:42"
};
var activity = await userFeed1.AddActivityAsync(activity);
var com = await client.Reactions.AddAsync("comment", activity.Id, "john");
var like = await client.Reactions.AddAsync("like", activity.Id, "maria");

// Fetch reaction counts grouped by reaction kind
var enriched = await userFeed1.GetEnrichedFlatActivitiesAsync(GetOptions.Default.WithReaction(ReactionOption.With().Counts()));
var enrichedActivity = enriched.Results.First();
Console.WriteLine(enrichedActivity.ReactionCounts["like"]); // 1
Console.WriteLine(enrichedActivity.ReactionCounts["comment"]); // 1

// Fetch reactions grouped by reaction kind
var enriched = await userFeed1.GetEnrichedFlatActivitiesAsync(GetOptions.Default.WithReaction(ReactionOption.With().Own()));
var enrichedActivity = enriched.Results.First();
Console.WriteLine(enrichedActivity.OwnReactions["like"]); // is the $like reaction
Console.WriteLine(enrichedActivity.OwnReactions["comment"]); // is the $comment reaction

// All reactions enrichment can be selected
var enriched = await userFeed1.GetEnrichedFlatActivitiesAsync(GetOptions.Default.WithReaction(ReactionOption.With().Counts().Own().Recent()));

// Personalization
var input = new Dictionary<string, object>
{
    {"feed_slug", "my_personalized_feed"},
    {"user_id", "john"},
    {"limit", 20},
    {"ranking", "my_ranking"}
};
var response = await client.Personalization.GetAsync("my_endpoint", input);

// File & Image Upload
var fileUpload = await client.Files.UploadAsync(stream, name, contentType);
var imageupload = await client.Images.UploadAsync(stream, name, contentType);

// Use fileUpload.File and imageUpload.File afterwards
// Open graph
var og = await client.OgAsync("https://google.com");

✍️ 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-net's People

Contributors

amila17 avatar christopherjl avatar dwightgunning avatar ferhatelmas avatar ffenix113 avatar github-actions[bot] avatar hoelter avatar ibebbs avatar kgamecarter avatar link512 avatar marco-ulge avatar murugaratham avatar peterdeme avatar plank-tools avatar prashantchoudhary avatar shawnspeak avatar solstice1557 avatar tbarbugli avatar vonbv avatar xernobyl 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  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  avatar  avatar  avatar  avatar  avatar  avatar

stream-net's Issues

"signature is invalid" when using CreateUserSessionToken

Hi,

I'm using version 3.0.1 of the 'stream-net' package and hitting a "signature is invalid" exception when trying to retrieve a feed using a user session token. I've seen lots of people encounter this issue in lots of language SDKs but the reported issues are either unanswered or closed without explanation. Perhaps there's an issue that has been addressed in other SDK's but not in the 'stream-net' package?

Anyway, here is a simple reproduction:

// Executed on the server
var serverConnection = new StreamClient(apiKey, apiSecret);
var clientToken = serverConnection.CreateUserSessionToken(userId);

// Just to verify that the 'server' can retrieve the users feed
var serverResponse = await serverConnection.Feed("users", userId).GetActivities(); // executes correctly
display($"Activites retrieved on server: {serverResponse.Count()}"); // Expect 0

// Executed on the client
var clientConnection = new StreamClient(apiKey, clientToken);
var clientResponse = await clientConnection.Feed("users", userId).GetActivities(); // fails with "signature is invalid"
display($"Activites retrieved on client: {clientResponse.Count()}");

Which results in this output:

Activites retrieved on server: 0
Error: Stream.StreamException: signature is invalid
at Stream.StreamException.FromResponse(RestResponse response)
at Stream.StreamFeed.GetActivities(Int32 offset, Int32 limit, FeedFilter filter, ActivityMarker marker)
at Submission#16.<<Initialize>>d__0.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.CodeAnalysis.Scripting.ScriptExecutionState.RunSubmissionsAsync[TResult](ImmutableArray`1 precedingExecutors, Func`2 currentExecutor, StrongBox`1 exceptionHolderOpt, Func`2 catchExceptionOpt, CancellationToken cancellationToken)

The code seems to be correct according to the documentation so any suggestions on how to resolve this issue?

Thanks in advance.

gz#9002

StreamResponse does not include the Next property

The REST API includes the "next" property on the JSON, which indicates that another page of activities is available for fetching:

{ results: [], next: '/api/v1.0/feed/<< feed_group >>/<< feed_id >>/?api_key=<< api_key >>&id_lt=<< next_activity_id >>&limit=<< activity_limit >>' }

This is not being exposed on the StreamResponse object. It would be very helpful to have this property, as it could allow having an indicator that there is at least one more page available, since there is not a way to get the total count of activities on a feed for a specific user.

gz#10110

Unit testing with StreamClient

StreamClient is currently a bottleneck in unit testing. Can you consider making the public methods of the following classes virtual, or Extract interfaces for all public methods?

  • StreamClient
  • StreamFeed
  • BatchOperations

Change Stream namespace to GetStream

System.IO.Stream is a widely used class. I know this has already came up before (#29 ) and decisions were made, but I STRONGLY recommend you reconsider. Very bad practice to have such an intrusive namespace.

Serializing object properties to camelcase in SetData

We're calling Activity.SetData where data is an instance of a class with Pascal case property names, but we'd prefer the properties to appear in Stream as camel case. Do you have any advice for this, e.g. does the client support us converting Pascal case property names to camel case? Or would you consider making a change to add that?

I'd also be happy to try a PR if you'd prefer - my guess is that SetData could take an optional JsonSerializer parameter that we could set up with a CamelCasePropertyNamesContractResolver, and then SetData passes that serializer to FromObject.

Either way thanks for the client, it's been great to use so far.

Allow C# client to connect with a session token

According to this issue the C# client cannot connect using a session token. Unfortunately we are looking to write a native client app based on C# (Xamarin/Uno) and not being able to use the C# SDK in this way is a major impediment.

Is there any reason the C# client cannot be extended to use a session token? If it is just a case of requiring the appropriate code to be implemented, I'd be happy to port the JS to C# and provide a PR.

Thanks

gz#9144

Add client function to create user session token

Something like (with added support for custom k/v to the payload):

public string SessionToken(string userId, string apiSecret)
{
    var segments = new List<string>();
    var header = new
    {
        typ = "JWT",
        alg = "HS256"
    };

    var payload = new
    {
        user_id = userId
    };

    byte[] headerBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header));
    byte[] payloadBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload));

    segments.Add(Base64UrlEncode(headerBytes));
    segments.Add(Base64UrlEncode(payloadBytes));

    var stringToSign = string.Join(".", segments.ToArray());
    var bytesToSign = Encoding.UTF8.GetBytes(stringToSign);

    using (var sha = new HMACSHA256(Encoding.UTF8.GetBytes(apiSecret)))
    {
        byte[] signature = sha.ComputeHash(bytesToSign);
        segments.Add(Base64UrlEncode(signature));
    }
    return string.Join(".", segments.ToArray());
}

Can the Collection Upsert method take a GenericData?

Hi,

We add/edit collections a lot and so far we've been using the Add and Update methods which both take GenericData objects. We're starting to get race conditions around that so we'd like to move to using Upsert, but that takes a CollectionObject instead of GenericData and I'm having trouble building the collection object. Is it possible to pass generic data to Upsert?

I'm trying to make the collection object like this:

var collectionObject = new CollectionObject(collectionEntryId);

var dataDict = new Dictionary<string, object>
{
	{ PropertyName1, PropertyValue1 },
	... etc
};

collectionObject.SetData(dataDict);

And the error I'm getting is

Internal Server Error, Can not add property id to Newtonsoft.Json.Linq.JObject. Property with the same name already exists on object.   at Newtonsoft.Json.Linq.JObject.ValidateToken(JToken o, JToken existing)
   at Newtonsoft.Json.Linq.JContainer.InsertItem(Int32 index, JToken item, Boolean skipParentCheck)
   at Newtonsoft.Json.Linq.JObject.InsertItem(Int32 index, JToken item, Boolean skipParentCheck)
   at Newtonsoft.Json.Linq.JContainer.Add(Object content)
   at Newtonsoft.Json.Linq.JObject.Add(String propertyName, JToken value)
   at Stream.GenericData.<>c__DisplayClass7_0.<AddToJObject>b__0(KeyValuePair`2 x)
   at Stream.Extensions.ForEach[T](IEnumerable`1 items, Action`1 action)
   at Stream.GenericData.AddToJObject(JObject& root)
   at Stream.CollectionObject.ToJObject()
   at Stream.Collections.<>c.<UpsertMany>b__3_0(CollectionObject x)
   at System.Linq.Enumerable.SelectArrayIterator`2.MoveNext()
   at Newtonsoft.Json.Linq.JContainer.TryAddInternal(Int32 index, Object content, Boolean skipParentCheck)
   at Newtonsoft.Json.Linq.JContainer.Add(Object content)
   at Newtonsoft.Json.Linq.JArray..ctor(Object content)
   at Newtonsoft.Json.Linq.JProperty..ctor(String name, Object content)
   at Stream.Collections.UpsertMany(String collectionName, IEnumerable`1 data)
   at Stream.Collections.Upsert(String collectionName, CollectionObject data)
   at [our code from here]

It's true that one of the property names is id but that was never a problem using the Add method so I'm not sure why it should affect Upsert specifically?

Thanks very much.

Setting collection data to an object

Hiya, we're using GenericData.SetData in both this client and the javascript client. In the javascript client, the docs example allows for passing in an object with multiple properties to be serialized.

In this client I can't see a way to do that except setting multiple name/value pairs? Is there a way to pass in an object instead? If not, would you be able to add an overload for that?

Thanks very much!


In the Stream docs on collections here:

  1. The javascript docs passing in an object with two properties
    image

  2. The C# docs calling SetData once for each property
    image

  3. What we'd be keen for

var collectionData = new GenericData();
collectionData.SetData(new Food("cheese burger", "4 stars"));
await client.Collections.Add("food", collectionData, "cheese-burger");

Batch Read Activities By ForeignIdTimes does not return activities that actually exist

I'm using .net 6, and the stream-net client to connect with getstream.

When I add an activity to a feed, I use the foreignIdTime approach. So I add a foreignId as string, and a dateTime.

However, when I create an activity, and then use the batch read by foreignIdTime (_streamClient.Batch.GetActivitiesByForeignIdAsync(foreignIdTimesList);), it does not return the created activity even though, the activity was created with the same foreignId and time

example code

public async Task<bool> CheckPostExistsInGetstream(int? postId, DateTime? createdDate)
        {
            // postId = 27410
            // createdDate =  {8/31/2023 4:55:46 AM}
            if (createdDate == null)
            {
                return false;
            }

            ForeignIdTime foreignIdTime = new($"post:{postId}", createdDate.Value);

            GenericGetResponse<Activity> recievedActivitiesResponse = await _streamClient.Batch.GetActivitiesByForeignIdAsync(new[] { foreignIdTime });

            if (recievedActivitiesResponse.Results.Count > 0) 
            {
                return true;
            }

            return false;
        }

The above code for the PostId = 27410 and createdDate = {8/31/2023 4:55:46 AM} returns false

and as you can see the activity does exist.
image

Unable to filter with GetEnrichedFlatActivitiesAsync

I am retrieving a list of activities and I have the need to filter by ActorId or by a custom field. Neither one works. Here's my code, it's super simple but doesn't seem to produce the desired result and I am always getting the same list back.

var options = GetOptions.Default.WithOffset(pagesLoaded * PAGE_SIZE).WithLimit(PAGE_SIZE);
if (selectedActorId.HasValue)
{
options = options.WithUserId(selectedActorId.ToString());
}
if(SelectedProjectId.HasValue)
{
options = options.WithCustom("project_id", SelectedProjectId.Value.ToString());
}
options = options.WithReaction(ReactionOption.With().Counts().Own());

var activities = await feed.GetEnrichedFlatActivitiesAsync(options);

Please help!

StreamChatException.FromResponse crashes in case of Http timeout

When StreamChatException.FromResponse() is called in case of a Http timeout, JsonConvert.DeserializeObject<StreamChatException.ExceptionState>(response.Content) causes a Newtonsoft.Json.JsonReaderException.
The content of response.Content is in that case "Request Timeout".

gz#9477

Calling `.Own()` retrieves other users' reactions

var results = await feed.GetEnrichedFlatActivities(GetOptions.Default.WithReaction(ReactionOption.With().Counts().Own()));

CMIIW but the OwnReactions IEnumerable i get from results, includes reactions from UserIds that are not the same as the owner of the FeedGroup.

Subscribe in C#

Are there any plans to add the ability to subscribe to a feed, i.e. to receive callbacks when a new activity is added to a feed?

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.