From b808c4ce633c18fd0f788e22d002913bceb7cbc2 Mon Sep 17 00:00:00 2001 From: Nate Emerson Date: Thu, 12 Sep 2024 15:45:45 -0700 Subject: [PATCH 1/2] Add first draft script for video 21 --- 21-nats-overview/script.md | 462 +++++++++++++++++++++++++++++++++++++ 1 file changed, 462 insertions(+) create mode 100644 21-nats-overview/script.md diff --git a/21-nats-overview/script.md b/21-nats-overview/script.md new file mode 100644 index 0000000..34eda06 --- /dev/null +++ b/21-nats-overview/script.md @@ -0,0 +1,462 @@ +# Introduction + +Hey everyone, my name is Nate and I like NATS! I'm a software architect who's +been building web applications and infrastructure for about 20 years. And I got a new job! + +I recently joined Synadia as a DX engineer, where I'm excited to work on our web +properties and educational content. While I've built and maintained systems with +RabbitMQ, Sidekiq, Kafka, Redis, and plethora AWS infra components, you might be +surprised to learn I'd never heard of NATS prior to meeting Jeremy at what I +would come to learn was my first job interview. + +[NATS architecture diagram] + +I've come to love the simple and extensible architecture NATS provides for +handling so many of the requirements of distributed systems. Since I've been +learning the ropes so to speak, I thought I'd bring you along as we explore the +basics of what this NATS thing is all about. + +Welcome to our 2024 refresh on NATS! + +NATS is an open-source messaging system with one goal: connecting everything. +Think of it as the nervous system for your distributed applications. It's fast, +scalable, and designed for modern cloud-native and edge computing environments. + +[outline] + +In this video, we'll: + +1. Cover the basics of NATS architecture +2. Build a real-time chat app +3. Add persistence with JetStream +4. Spice it up with a Twitch integration + +We'll keep things snappy, using diagrams and pre-written code to get you up to speed quickly. +Let's dive in and see what NATS can do! + +# NATS Architectural Overview + +Alright, let's break down how NATS works. We'll cover three key aspects: core +NATS, clustering, and JetStream. +[pub-sub diagram] +First up, core NATS. At its heart, NATS uses a publish-subscribe model. Here's how it works: + +Publishers send messages on specific subjects. +The NATS server acts as a message broker, the courier of the system. +Subscribers listen for messages on subjects they're interested in. + +It's that simple. This model allows for flexible, decoupled communication +between components in your system. + +[cluster diagram] +Now, let's talk scalability. NATS shines in distributed environments through +clustering: + +1. Multiple NATS servers form a cluster. +2. Clients can connect to any server in the cluster. +3. Servers use a Gossip Protocol to share information. + +This setup gives you high availability and fault tolerance. If one server goes +down, clients can seamlessly connect to another. + +# NATS JetStream Overview +[jetstream diagram] +Finally, let's look at JetStream, NATS' persistence layer: + +JetStream adds message streaming and persistence to NATS. +It uses 'Streams' to store messages. +'Consumers' read messages from streams, allowing for things like replay and +filtered consumption. + +JetStream is perfect for event-driven architectures and situations where you +need guaranteed message delivery. +And there you have it – NATS in a nutshell. Simple, scalable, and now with +built-in persistence. Enough talk, let's see it in action! + + +# Project Setup + +We'll start by installing NATS and the NATS CLI locally with a package manager. +Now we can run a nats-server locally to show you how this works. Using nats CLI +in another terminal we can verify that NATS is running and we are connected. + +Now let's build our first chat client. + +```bash +mkdir nats-chat && cd "$_" +go mod init nats-chat +go get github.com/nats-io/nats.go +``` + +# Basic Chat Application + +```go +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/nats-io/nats.go" +) + +func main() { + nc, err := nats.Connect(nats.DefaultURL) + if err != nil { + fmt.Println(err) + return + } + defer nc.Close() + + nc.Subscribe("chat", func(m *nats.Msg) { + fmt.Printf("Received: %s\n", string(m.Data)) + }) + + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + msg := scanner.Text() + if strings.ToLower(msg) == "quit" { + break + } + nc.Publish("chat", []byte(msg)) + } +} +``` + +Let's walk through this extremely simple chat client: +1. First we connect to the NATS server using the default URL +2. We set up a subscriber that listens for messages on the "chat" subject, +printing anything received to the console. +3. Then we have a simple loop that publishes anything entered on the command +line to the "chat subject"...until receiving the command "quit" + +We can run this in two terminals and see the messages being sent and received. +However, if we start a new client, the chat history is empty. A client must be +connected when a message is sent to see it. + +# Adding Web Client + +First we'll add a simple backend with Go to serve a websocket endpoint and +static HTML. Every connected subscribes to the "chat" subject and is able to +publish messages to it. In this extremely simple front-end, we have a simple +chat interface in HTML that uses JS to manage the websocket connection. + +web backend: +```go +package main + +import ( + "log" + "net/http" + + "github.com/gorilla/websocket" + "github.com/nats-io/nats.go" +) + +var upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func main() { + nc, err := nats.Connect(nats.DefaultURL) + if err != nil { + log.Fatal(err) + } + defer nc.Close() + + http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + defer conn.Close() + + sub, err := nc.Subscribe("chat", func(msg *nats.Msg) { + conn.WriteMessage(websocket.TextMessage, msg.Data) + }) + if err != nil { + log.Println(err) + return + } + defer sub.Unsubscribe() + + for { + _, message, err := conn.ReadMessage() + if err != nil { + log.Println(err) + break + } + nc.Publish("chat", message) + } + }) + + http.Handle("/", http.FileServer(http.Dir("./static"))) + log.Fatal(http.ListenAndServe(":8080", nil)) +} +``` + +Web chat front-end: + +```html + + + + + + NATS Web Chat + + + +

NATS Web Chat

+
+ + + + + + +``` + +# Enhancing with JetStream + +Now the obvious issue with this application so far is that you will only see +messages sent when you have the chat open. Let's fix that by adding persistence +using JetStream. + +```go +package main + +import ( + "log" + "net/http" + "time" + + "github.com/gorilla/websocket" + "github.com/nats-io/nats.go" +) + +var upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func main() { + nc, err := nats.Connect(nats.DefaultURL) + if err != nil { + log.Fatal(err) + } + defer nc.Close() + + // Create JetStream context + js, err := nc.JetStream() + if err != nil { + log.Fatal(err) + } + + // Create a stream for chat messages + _, err = js.AddStream(&nats.StreamConfig{ + Name: "CHAT", + Subjects: []string{"chat.>"}, + }) + if err != nil { + log.Fatal(err) + } + + http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + defer conn.Close() + + // Subscribe to chat messages + sub, err := js.Subscribe("chat.messages", func(msg *nats.Msg) { + conn.WriteMessage(websocket.TextMessage, msg.Data) + }) + if err != nil { + log.Println(err) + return + } + defer sub.Unsubscribe() + + // Retrieve chat history + consumer, err := js.PullSubscribe("chat.messages", "") + if err != nil { + log.Println(err) + return + } + + msgs, err := consumer.Fetch(100) + if err != nil { + log.Println(err) + } else { + for _, msg := range msgs { + conn.WriteMessage(websocket.TextMessage, msg.Data) + } + } + + // Handle incoming messages + for { + _, message, err := conn.ReadMessage() + if err != nil { + log.Println(err) + break + } + _, err = js.Publish("chat.messages", message) + if err != nil { + log.Println(err) + } + } + }) + + http.Handle("/", http.FileServer(http.Dir("./static"))) + log.Fatal(http.ListenAndServe(":8080", nil)) +} +``` + +# Twitch Chat Integration + + +```go +package main + +import ( + "fmt" + "log" + "os" + "strings" + + "github.com/gempir/go-twitch-irc/v2" + "github.com/nats-io/nats.go" +) + +func main() { + // Connect to NATS + nc, err := nats.Connect(nats.DefaultURL) + if err != nil { + log.Fatal(err) + } + defer nc.Close() + + js, err := nc.JetStream() + if err != nil { + log.Fatal(err) + } + + // Twitch bot setup + client := twitch.NewClient("YourBotUsername", "oauth:YourOAuthToken") + + client.OnPrivateMessage(func(message twitch.PrivateMessage) { + chatMessage := fmt.Sprintf("%s: %s", message.User.DisplayName, message.Message) + + // Publish to NATS JetStream + _, err := js.Publish("chat.messages", []byte(chatMessage)) + if err != nil { + log.Println("Error publishing to NATS:", err) + } + }) + + client.Join("YourTwitchChannel") + + err = client.Connect() + if err != nil { + panic(err) + } +} +``` + +# Conclusion and further enhancements + +Great job, everyone! Let's recap what we've built and explore some exciting +ways to take this project even further. + +## What We've Accomplished + +1. Set up a local NATS environment +2. Created a basic chat application using core NATS +3. Added a web client for broader accessibility +4. Implemented persistence with NATS JetStream +5. Integrated Twitch chat, demonstrating NATS' flexibility + +Our chat application now showcases several key NATS features: real-time +messaging, persistence, and system integration. + +## Potential Enhancements + +1. Multi-room support: + - Use NATS subjects to create multiple chat rooms + - Example: "chat.room.general", "chat.room.tech", etc. + +2. User authentication: + - Implement a login system + - Use NATS authorization to control access to different chat rooms + +3. Message filtering: + - Leverage NATS subject hierarchies for message categorization + - Implement client-side filtering based on message types or user preferences + +4. Scalability demonstration: + - Set up a NATS cluster to show how the application scales + - Implement load balancing for web servers + +5. More integrations: + - Add connectors for other platforms (Discord, Slack, etc.) + - Demonstrate NATS' power in creating a unified chat ecosystem + +6. Rich media support: + - Extend the application to handle file uploads, images, or even video streams + - Use NATS streaming for larger data transfers + +7. Real-time analytics: + - Implement a separate service that consumes chat messages + - Generate real-time statistics on chat activity, popular topics, etc. + +These enhancements showcase NATS' versatility in building complex, distributed +systems. Each one demonstrates a different aspect of what NATS can do, from +fine-grained message routing to handling high-throughput data streams. + +Remember, NATS is all about connecting systems, services, and devices flexibly +and at scale. The possibilities are endless! + +Thanks for joining me on this journey through NATS. Happy coding, and I can't +wait to see what you build with NATS! + From 5f6604ee379e56753b918ce5c7492d9ced533dac Mon Sep 17 00:00:00 2001 From: NateEmerson Date: Thu, 26 Sep 2024 14:53:35 -0700 Subject: [PATCH 2/2] Add code --- .gitignore | 1 + 21-nats-overview/client/cli-durable.go | 65 ++++++++++++++++++++ 21-nats-overview/client/cli.go | 35 +++++++++++ 21-nats-overview/client/twitch.go | 39 ++++++++++++ 21-nats-overview/client/web-durable.go | 85 ++++++++++++++++++++++++++ 21-nats-overview/client/web.go | 53 ++++++++++++++++ 21-nats-overview/go.mod | 19 ++++++ 21-nats-overview/go.sum | 33 ++++++++++ 21-nats-overview/main.go | 38 ++++++++++++ 21-nats-overview/static/index.html | 49 +++++++++++++++ 10 files changed, 417 insertions(+) create mode 100644 21-nats-overview/client/cli-durable.go create mode 100644 21-nats-overview/client/cli.go create mode 100644 21-nats-overview/client/twitch.go create mode 100644 21-nats-overview/client/web-durable.go create mode 100644 21-nats-overview/client/web.go create mode 100644 21-nats-overview/go.mod create mode 100644 21-nats-overview/go.sum create mode 100644 21-nats-overview/main.go create mode 100644 21-nats-overview/static/index.html diff --git a/.gitignore b/.gitignore index 5fef264..df9ca73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .obsidian user.creds +.DS_STORE diff --git a/21-nats-overview/client/cli-durable.go b/21-nats-overview/client/cli-durable.go new file mode 100644 index 0000000..350e8d5 --- /dev/null +++ b/21-nats-overview/client/cli-durable.go @@ -0,0 +1,65 @@ +package client + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/nats-io/nats.go" +) + +func RunCLIDurable() { + // Connect to NATS + fmt.Println(nats.DefaultURL) + nc, err := nats.Connect(nats.DefaultURL) + if err != nil { + fmt.Println(err) + return + } + defer nc.Close() + + js, err := nc.JetStream() + if err != nil { + fmt.Println(err) + } + + _, err = js.AddStream(&nats.StreamConfig{ + Name: "CHAT", + Subjects: []string{"chat.>"}, + }) + if err != nil { + fmt.Println(err) + } + + // Subscribe to chat messages + nc.Subscribe("chat.messages", func(m *nats.Msg) { + fmt.Printf("Received: %s\n", string(m.Data)) + }) + + // Retrieve chat history + consumer, err := js.PullSubscribe("chat.messages", "") + if err != nil { + fmt.Println(err) + return + } + + msgs, err := consumer.Fetch(100) + if err != nil { + fmt.Println(err) + } else { + for _, msg := range msgs { + fmt.Printf("Received: %s\n", string(msg.Data)) + } + } + + // Publish chat messages + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + msg := scanner.Text() + if strings.ToLower(msg) == "quit" { + break + } + js.Publish("chat.messages", []byte(msg)) + } +} diff --git a/21-nats-overview/client/cli.go b/21-nats-overview/client/cli.go new file mode 100644 index 0000000..523cd0f --- /dev/null +++ b/21-nats-overview/client/cli.go @@ -0,0 +1,35 @@ +package client +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/nats-io/nats.go" +) + +func RunCLI() { + // Connect to NATS + fmt.Println(nats.DefaultURL) + nc, err := nats.Connect(nats.DefaultURL) + if err != nil { + fmt.Println(err) + return + } + defer nc.Close() + + // Subscribe to chat messages + nc.Subscribe("chat", func(m *nats.Msg) { + fmt.Printf("Received: %s\n", string(m.Data)) + }) + + // Publish chat messages + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + msg := scanner.Text() + if strings.ToLower(msg) == "quit" { + break + } + nc.Publish("chat", []byte(msg)) + } +} diff --git a/21-nats-overview/client/twitch.go b/21-nats-overview/client/twitch.go new file mode 100644 index 0000000..5945132 --- /dev/null +++ b/21-nats-overview/client/twitch.go @@ -0,0 +1,39 @@ +package client + +import ( + "fmt" + + "github.com/gempir/go-twitch-irc/v4" + "github.com/nats-io/nats.go" +) + +func RunTwitch(channel string) { + nc, err := nats.Connect(nats.DefaultURL) + if err != nil { + fmt.Println(err) + } + defer nc.Close() + + js, err := nc.JetStream() + if err != nil { + fmt.Println(err) + } + + client := twitch.NewAnonymousClient() + + client.OnPrivateMessage(func(message twitch.PrivateMessage) { + chatMessage := fmt.Sprintf("%s: %s (twitch)", message.User.DisplayName, message.Message) + + _, err := js.Publish("chat.messages", []byte(chatMessage)) + if err != nil { + fmt.Println("Error publishing to NATS:", err) + } + }) + + client.Join(channel) + + err = client.Connect() + if err != nil { + panic(err) + } +} diff --git a/21-nats-overview/client/web-durable.go b/21-nats-overview/client/web-durable.go new file mode 100644 index 0000000..f823a56 --- /dev/null +++ b/21-nats-overview/client/web-durable.go @@ -0,0 +1,85 @@ +package client + +import ( + "fmt" + "net/http" + + "github.com/gorilla/websocket" + "github.com/nats-io/nats.go" +) + +var upgraderJS = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func RunWebDurable() { + nc, err := nats.Connect(nats.DefaultURL) + if err != nil { + fmt.Println(err) + } + defer nc.Close() + + js, err := nc.JetStream() + if err != nil { + fmt.Println(err) + } + + _, err = js.AddStream(&nats.StreamConfig{ + Name: "CHAT", + Subjects: []string{"chat.>"}, + }) + if err != nil { + fmt.Println(err) + } + + http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { + conn, err := upgraderJS.Upgrade(w, r, nil) + if err != nil { + fmt.Println(err) + return + } + defer conn.Close() + + sub, err := nc.Subscribe("chat.messages", func(msg *nats.Msg) { + conn.WriteMessage(websocket.TextMessage, msg.Data) + }) + if err != nil { + fmt.Println(err) + return + } + defer sub.Unsubscribe() + + // Retrieve chat history + consumer, err := js.PullSubscribe("chat.messages", "") + if err != nil { + fmt.Println(err) + return + } + + msgs, err := consumer.Fetch(100) + if err != nil { + fmt.Println(err) + } else { + for _, msg := range msgs { + conn.WriteMessage(websocket.TextMessage, msg.Data) + } + } + + for { + _, msg, err := conn.ReadMessage() + if err != nil { + fmt.Println(err) + break + } + _, err = js.Publish("chat.messages", msg) + if err != nil { + fmt.Println(err) + } + } + }) + + http.Handle("/", http.FileServer(http.Dir("./static"))) + fmt.Println(http.ListenAndServe(":8080", nil)) +} diff --git a/21-nats-overview/client/web.go b/21-nats-overview/client/web.go new file mode 100644 index 0000000..a78ccf6 --- /dev/null +++ b/21-nats-overview/client/web.go @@ -0,0 +1,53 @@ +package client + +import ( + "log" + "net/http" + + "github.com/gorilla/websocket" + "github.com/nats-io/nats.go" +) + +var upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func RunWeb() { + nc, err := nats.Connect(nats.DefaultURL) + if err != nil { + log.Fatal(err) + } + defer nc.Close() + + http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + defer conn.Close() + + sub, err := nc.Subscribe("chat", func(msg *nats.Msg) { + conn.WriteMessage(websocket.TextMessage, msg.Data) + }) + if err != nil { + log.Println(err) + return + } + defer sub.Unsubscribe() + + for { + _, message, err := conn.ReadMessage() + if err != nil { + log.Println(err) + break + } + nc.Publish("chat", message) + } + }) + + http.Handle("/", http.FileServer(http.Dir("./static"))) + log.Fatal(http.ListenAndServe(":8080", nil)) +} diff --git a/21-nats-overview/go.mod b/21-nats-overview/go.mod new file mode 100644 index 0000000..6e43f51 --- /dev/null +++ b/21-nats-overview/go.mod @@ -0,0 +1,19 @@ +module nats-chat + +go 1.23.1 + +require ( + github.com/gempir/go-twitch-irc/v4 v4.0.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/minio/highwayhash v1.0.3 // indirect + github.com/nats-io/jwt/v2 v2.5.8 // indirect + github.com/nats-io/nats-server/v2 v2.10.21 // indirect + github.com/nats-io/nats.go v1.37.0 // indirect + github.com/nats-io/nkeys v0.4.7 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + go.uber.org/automaxprocs v1.5.3 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/time v0.6.0 // indirect +) diff --git a/21-nats-overview/go.sum b/21-nats-overview/go.sum new file mode 100644 index 0000000..74b3f29 --- /dev/null +++ b/21-nats-overview/go.sum @@ -0,0 +1,33 @@ +github.com/gempir/go-twitch-irc/v4 v4.0.0 h1:sHVIvbWOv9nHXGEErilclxASv0AaQEr/r/f9C0B9aO8= +github.com/gempir/go-twitch-irc/v4 v4.0.0/go.mod h1:QsOMMAk470uxQ7EYD9GJBGAVqM/jDrXBNbuePfTauzg= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= +github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= +github.com/nats-io/jwt/v2 v2.5.8 h1:uvdSzwWiEGWGXf+0Q+70qv6AQdvcvxrv9hPM0RiPamE= +github.com/nats-io/jwt/v2 v2.5.8/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A= +github.com/nats-io/nats-server/v2 v2.10.21 h1:gfG6T06wBdI25XyY2IsauarOc2srWoFxxfsOKjrzoRA= +github.com/nats-io/nats-server/v2 v2.10.21/go.mod h1:I1YxSAEWbXCfy0bthwvNb5X43WwIWMz7gx5ZVPDr5Rc= +github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= +github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= +github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= +github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= diff --git a/21-nats-overview/main.go b/21-nats-overview/main.go new file mode 100644 index 0000000..01b8291 --- /dev/null +++ b/21-nats-overview/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "os" + + "nats-chat/client" +) + +func main() { + if len(os.Args) < 2 { + fmt.Println("Usage: go run main.go [cli|web]") + return + } + + clientType := os.Args[1] + switch clientType { + case "cli": + client.RunCLI() + case "cli-durable": + client.RunCLIDurable() + case "web": + client.RunWeb() + case "web-durable": + client.RunWebDurable() + case "twitch": + if len(os.Args) < 3 { + fmt.Println("Please provide channel name for twitch") + return + } + channelName := os.Args[2] + client.RunTwitch(channelName) + fmt.Printf("Connecting to channel: %s\n", channelName) + default: + fmt.Printf("Unknown client type: %s\n", clientType) + fmt.Println("Usage: go run main.go [cli|web]") + } +} diff --git a/21-nats-overview/static/index.html b/21-nats-overview/static/index.html new file mode 100644 index 0000000..985b28c --- /dev/null +++ b/21-nats-overview/static/index.html @@ -0,0 +1,49 @@ + + + + + + NATS Web Chat + + + +

NATS Web Chat

+
+ + + + + +