{"id":13724011,"url":"https://github.com/rajveermalviya/unifrost","last_synced_at":"2025-05-07T17:32:24.661Z","repository":{"id":57503259,"uuid":"201133101","full_name":"rajveermalviya/unifrost","owner":"rajveermalviya","description":"Making it easier to push pubsub events directly to the browser.","archived":true,"fork":false,"pushed_at":"2020-08-02T07:38:00.000Z","size":152,"stargazers_count":168,"open_issues_count":1,"forks_count":8,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-05-01T02:02:00.836Z","etag":null,"topics":["eventsource","google-cloud-pubsub","in-memory","nats","sse","stream-events"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rajveermalviya.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-08-07T21:52:13.000Z","updated_at":"2025-04-08T16:58:29.000Z","dependencies_parsed_at":"2022-09-13T08:22:03.391Z","dependency_job_id":null,"html_url":"https://github.com/rajveermalviya/unifrost","commit_stats":null,"previous_names":["rajveermalviya/unifrost","unifrost/unifrost"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rajveermalviya%2Funifrost","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rajveermalviya%2Funifrost/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rajveermalviya%2Funifrost/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rajveermalviya%2Funifrost/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rajveermalviya","download_url":"https://codeload.github.com/rajveermalviya/unifrost/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252926758,"owners_count":21826361,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["eventsource","google-cloud-pubsub","in-memory","nats","sse","stream-events"],"created_at":"2024-08-03T01:01:48.453Z","updated_at":"2025-05-07T17:32:24.258Z","avatar_url":"https://github.com/rajveermalviya.png","language":"Go","funding_links":["https://paypal.me/rajveermalviya"],"categories":["Go"],"sub_categories":[],"readme":"# unifrost: A go module that makes it easier to stream pubsub events to the web\n\n[![GoDoc](https://godoc.org/github.com/unifrost/unifrost?status.svg)](https://godoc.org/github.com/unifrost/unifrost)\n[![Go Report Card](https://goreportcard.com/badge/github.com/unifrost/unifrost)](https://goreportcard.com/report/unifrost/unifrost)\n[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3298/badge)](https://bestpractices.coreinfrastructure.org/projects/3298)\n\n⚠ This project is on early stage, it's not ready for production yet ⚠\n\nPreviously named gochan\n\nunifrost is a go module for relaying pubsub messages to the web via [SSE(Eventsource)](https://en.wikipedia.org/wiki/Server-sent_events).\nIt is based on Twitter's implementation for real-time event-streaming in their\nnew web app.\n\nunifrost is named after bifrost, the rainbow bridge that connects Asgard with\nMidgard (Earth), that is MCU reference which is able to transport people both\nways. But because unifrost sends messages from server to client (only one way),\nhence unifrost. 😎\n\nIt uses the [Go CDK](https://gocloud.dev) as broker neutral pubsub driver that\nsupports multiple pubsub brokers:\n\n- Google Cloud Pub/Sub\n- Amazon Simple Queueing Service\n- Azure Service Bus (Pending)\n- RabbitMQ\n- NATS\n- Kafka\n- In-Memory (Only for testing)\n\n## Installation\n\nunifrost supports Go modules and built against go version 1.13\n\n```sh\ngo get github.com/unifrost/unifrost\n```\n\n## Documentation\n\nFor documentation check [godoc](https://godoc.org/github.com/unifrost/unifrost).\n\n## Usage\n\nunifrost uses Server-Sent-Events, because of this it doesn't require to run a\nstandalone server, unlike websockets it can be embedded in your api server.\nunifrost's stream handler has a ServeHTTP method i.e it implements http.Handler interface\nso that it can be used directly or can be wrapped with middlewares like\nAuthentication easily.\n\n```go\n// Golang (psuedo-code)\n\n// Using stream handler directly\nstreamHandler, err := unifrost.NewStreamHandler(\n  ctx,\n  \u0026memdriver.Client{},\n  unifrost.ConsumerTTL(2*time.Second),\n)\nlog.Fatal(\"HTTP server error: \", http.ListenAndServe(\"localhost:3000\", streamHandler))\n```\n\n```go\n// Golang (psuedo-code)\n\n// Using stream handler by wrapping it in auth middleware\nstreamHandler, err := unifrost.NewStreamHandler(\n  ctx,\n  \u0026memdriver.Client{},\n  unifrost.ConsumerTTL(2*time.Second),\n)\n\nmux := http.NewServeMux()\nmux.HandleFunc(\"/events\", func (w http.ResponseWriter, r *http.Request) {\n    err := Auth(r)\n    if err != nil {\n      http.Error(w, \"unauthorized\", http.StatusUnauthorized)\n      return\n    }\n\n    streamHandler.ServeHTTP(w,r)\n})\nlog.Fatal(\"HTTP server error: \", http.ListenAndServe(\"localhost:3000\", mux))\n```\n\n# Message Protocol\n\nEvery message sent by the server is encoded in plaintext in JSON,\nit contains topic and the payload.\n\nEvery message will be an array of length 2, first index will be the topic string,\nsecond index will be the payload in string type.\n\nWhen consumer connects to the server, server sends a preflight message that contains\nthe initial server configuration and list of topics the consumer has already been subscribed.\n\n1. Configuration: it contains the consumer_id and consumer_ttl set by the\n   stream handler config\n2. Subscriptions associated with the specified consumer id.\n\nExample first message:\n\n```json\n[\n  \"/unifrost/info\",\n\n  \"{\\\"config\\\":{\\\"consumer_id\\\":\\\"unique-id\\\",\\\"consumer_ttl_millis\\\":2000},\\\"subscriptions\\\":[]}\"\n]\n```\n\nExample error message:\n\n```json\n[\n  \"/unifrost/error\",\n\n  \"{\\\"error\\\":{\\\"code\\\":\\\"subscription-failure\\\",\\\"message\\\":\\\"Cannot receive message from subscription, closing subscription\\\",\\\"topic\\\":\\\"topic3\\\"}}\"\n]\n```\n\nAll the messages are streamed over single channel, i.e using EventSource JS API\n`new EventSource().onmessage` or `new EventSource().addEventListener('message', (e) =\u003e{})`\nmethods will listen to them.\n\nAll the info events are streamed over message channel i.e using the EventSource JS API,\n`onmessage` or `addEventListener('message', () =\u003e {})` method will listen to them.\nAll the subscription events have event name same as their topic name, so to listen to\ntopic events you need to add an event-listener on the EventSource object.\n\n# Example\n\nClient example:\n\n```ts\n// Typescript (psuedo-code)\nconst consumerID = 'unique-id';\n\nconst sse = new EventSource(`/events?id=${consumerID}`);\n// for info events like first-message and errors\nsse.addEventListener('message', (e) =\u003e {\n  const message = JSON.parse(e.data);\n\n  const topic = message[0] as String;\n  const payload = message[1] as String;\n\n  // Payload is the exact message from Pub Sub broker, probably JSON.\n  // Decode payload\n  const data = JSON.parse(payload);\n});\n```\n\nNew consumer is registered explicitly using the `streamHandler.NewConsumer()`\nwith an auto generated id.\nTo register a consumer with custom id use `streamHandler.NewCustomConsumer(id)`\n\nThis makes it easy to integrate authentication with `unifrost.StreamHandler`.\nOne possible auth workflow can be, create a new unifrost consumer after login\nand return the consumer id to the client to store it in the local storage of the\nbrowser. Further using the consumer id to connect to the stream handler.\n\nIf you don't care about authentication, you can also generate a new consumer\nautomatically everytime a new consumer connects without the `id` parameter\nuse the following middleware with the streamer.\nAnd handle registering the `id` to your backend from your client.\n\n```go\n// Golang (psuedo-code)\n\nmux.HandleFunc(\"/events\", func(w http.ResponseWriter, r *http.Request) {\n    // Auto generate new consumer_id, when new consumer connects.\n    q := r.URL.Query()\n    if q.Get(\"id\") == \"\" {\n        consumer, _ := streamHandler.NewConsumer(ctx)\n        q.Set(\"id\", consumer.ID)\n        r.URL.RawQuery = q.Encode()\n    }\n\n    streamer.ServeHTTP(w, r)\n})\n```\n\nWhen a consumer gets disconnected it has a time window to connect to the server\nagain with the state unchanged. If consumer ttl is not specified in the\nstreamer config then default ttl is set to one.\n\n# Managing subscriptions\n\n`unifrost.StreamHandler` provides simple API for subscribing and unsubscribing to topics.\n\n```go\nfunc (s *StreamHandler) Subscribe(ctx context.Context, consumerID string, topic string) error\n\n\nfunc (s *StreamHandler) Unsubscribe(ctx context.Context, consumerID string, topic string) error\n```\n\nThese methods can be used to add or remove subscriptions for a consumer.\n\nIf you want to give subscription control to the client look at\n[the implementation](examples/nats_example/main.go#L95) in the example.\n\nTo know more, check out the [example](examples/nats_example)\n\n## Why Server Sent Events (SSE) ?\n\nWhy would you choose SSE over WebSockets?\n\nOne reason SSEs have been kept in the shadow is because later APIs like\nWebSockets provide a richer protocol to perform bi-directional, full-duplex\ncommunication. However, in some scenarios data doesn't need to be sent from the\nclient. You simply need updates from some server action. A few examples would\nbe status updates, tweet likes, tweet retweets, tickers, news feeds, or other\nautomated data push mechanisms (e.g. updating a client-side Web SQL Database or\nIndexedDB object store). If you'll need to send data to a server, Fetch API is\nalways a friend.\n\nSSEs are sent over traditional HTTP. That means they do not require a special\nprotocol or server implementation to get working. WebSockets on the other hand,\nrequire full-duplex connections and new Web Socket servers to handle the\nprotocol. In addition, Server-Sent Events have a variety of features that\nWebSockets lack by design such as automatic reconnection, event IDs, and the\nability to send arbitrary events.\n\nBecause SSE works on top of HTTP, HTTP protocol improvements can also benefit SSE.\nFor example, the in-development HTTP/3 protocol, built on top of QUIC, could\noffer additional performance improvements in the presence of packet loss due to\nlack of head-of-line blocking.\n\n## Community:\n\nJoin the #unifrost channel on [gophers](https://gophers.slack.com/messages/unifrost)\nSlack Workspace for questions and discussions.\n\n## Future Goals:\n\n- Standalone server that can be configured by yaml, while also staying modular.\n- Creating a website for documentation \u0026 overview, and some examples.\n\n## Users\n\nIf you are using unifrost in production please let me know by sending an\n[email](mailto:rajveer0malviya@gmail.com) or file an issue.\n\n## Show some love\n\nThe best way to show some love towards the project, is to contribute and file\nissues.\n\nIf you **love** unifrost, you can support by sharing the project on Twitter.\n\nYou can also support by sponsoring the project via [PayPal](https://paypal.me/rajveermalviya).\n\n## License\n\n[APACHE v2](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frajveermalviya%2Funifrost","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frajveermalviya%2Funifrost","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frajveermalviya%2Funifrost/lists"}