Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- `WithToken(ctx, token)` context override for per-request authentication, enabling concurrent requests with different user tokens through a single client instance

### Changed

Expand Down
48 changes: 48 additions & 0 deletions docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,54 @@ func CustomHeadersMiddleware(headers map[string]string) Middleware {
}
```

## Per-Request Token Override

Override the client-level token for individual requests using `WithToken`. This enables concurrent requests with different user tokens through a single client, without creating multiple client instances.

```go
// Override token for a single request
ctx := helix.WithToken(ctx, &helix.Token{AccessToken: "user-token"})
followers, err := client.GetChannelFollowers(ctx, &helix.GetChannelFollowersParams{
BroadcasterID: "12345",
})
```

### Token Resolution Order

1. Per-request token from `WithToken(ctx, token)` (highest priority)
2. Client-level `AuthClient` token
3. Client-level `TokenProvider` (e.g., Extension JWT)

### Concurrent Multi-Token Requests

Fetch data for multiple channels where each requires its own user token:

```go
type ChannelToken struct {
BroadcasterID string
Token *helix.Token
}

channels := []ChannelToken{
{BroadcasterID: "111", Token: &helix.Token{AccessToken: "token-a"}},
{BroadcasterID: "222", Token: &helix.Token{AccessToken: "token-b"}},
{BroadcasterID: "333", Token: &helix.Token{AccessToken: "token-c"}},
}

results := make(chan error, len(channels))
for _, ch := range channels {
go func(ch ChannelToken) {
ctx := helix.WithToken(ctx, ch.Token)
_, err := client.GetChannelFollowers(ctx, &helix.GetChannelFollowersParams{
BroadcasterID: ch.BroadcasterID,
})
results <- err
}(ch)
}
```

This works with all context-aware features including caching (`NoCacheContext`), middleware, and batch operations.

## Low-Level Request Execution

For advanced use cases, you can execute raw requests directly.
Expand Down
23 changes: 23 additions & 0 deletions docs/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,29 @@ auth.SetToken(&helix.Token{
token := auth.GetToken()
```

### WithToken (Per-Request Override)

Override the client-level token for a single request. This is useful when making
concurrent requests that each require a different user token (e.g., fetching
followers for multiple channels where each requires `moderator:read:followers`).

```go
// Each request uses a different user's token
ctx := helix.WithToken(context.Background(), &helix.Token{
AccessToken: "user-specific-token",
})
followers, err := client.GetChannelFollowers(ctx, &helix.GetChannelFollowersParams{
BroadcasterID: "12345",
})
```

The token resolution order is:
1. Per-request token from `WithToken` context (if set)
2. Client-level `AuthClient` token
3. Client-level `TokenProvider` (e.g., Extension JWT)

See [Batch & Caching Examples](examples/batch-caching.md) for a complete concurrent multi-token example.

## OIDC (OpenID Connect)

Support for Twitch's OIDC implementation for identity verification.
Expand Down
2 changes: 2 additions & 0 deletions docs/cookbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ description: Comprehensive code examples covering all kappopher features.
| Feature | Example |
|---------|---------|
| OAuth Authentication | [Authentication](examples/authentication.md) |
| Per-Request Token Override | [Authentication](examples/authentication.md), [Batch & Caching](examples/batch-caching.md) |
| Get Users/Channels | [Basic](examples/basic.md), [API Usage](examples/api-usage.md) |
| Send Chat Messages | [Chat Bot](examples/chatbot.md), [IRC Client](examples/irc-client.md) |
| Handle Chat Events | [IRC Client](examples/irc-client.md), [EventSub WebSocket](examples/eventsub-websocket.md) |
Expand Down Expand Up @@ -72,6 +73,7 @@ description: Comprehensive code examples covering all kappopher features.

| Use Case | Start Here |
|----------|-----------|
| Concurrent multi-user requests | [Authentication](examples/authentication.md), [Batch & Caching](examples/batch-caching.md) |
| Build a chat bot | [Chat Bot](examples/chatbot.md) or [IRC Client](examples/irc-client.md) |
| Monitor stream events | [EventSub WebSocket](examples/eventsub-websocket.md) |
| Create a dashboard | [Batch & Caching](examples/batch-caching.md) |
Expand Down
65 changes: 65 additions & 0 deletions docs/examples/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,71 @@ func main() {
}
```

## Per-Request Token Override

When making concurrent requests that each require a different user token, use `WithToken` to override the client-level token on a per-request basis. This avoids creating multiple client instances.

**When to use**: Fetching scoped data for multiple channels concurrently (e.g., followers, subscriptions) where each channel requires its own user token.

```go
package main

import (
"context"
"fmt"
"log"
"sync"

"github.com/Its-donkey/kappopher/helix"
)

func main() {
ctx := context.Background()

// One client shared across all requests
authClient := helix.NewAuthClient(helix.AuthConfig{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
})
client := helix.NewClient("your-client-id", authClient)

// Each channel has its own user token (obtained via Authorization Code flow)
type Channel struct {
BroadcasterID string
Token *helix.Token
}

channels := []Channel{
{BroadcasterID: "111", Token: &helix.Token{AccessToken: "user-token-a"}},
{BroadcasterID: "222", Token: &helix.Token{AccessToken: "user-token-b"}},
{BroadcasterID: "333", Token: &helix.Token{AccessToken: "user-token-c"}},
}

// Fetch followers for all channels concurrently
var wg sync.WaitGroup
for _, ch := range channels {
wg.Add(1)
go func(ch Channel) {
defer wg.Done()

// Override the client token for this request
reqCtx := helix.WithToken(ctx, ch.Token)

followers, err := client.GetChannelFollowers(reqCtx, &helix.GetChannelFollowersParams{
BroadcasterID: ch.BroadcasterID,
})
if err != nil {
log.Printf("Error fetching followers for %s: %v", ch.BroadcasterID, err)
return
}

fmt.Printf("Channel %s has %d followers\n", ch.BroadcasterID, *followers.Total)
}(ch)
}
wg.Wait()
}
```

## Common Scope Combinations

The library provides pre-defined scope combinations for common use cases. These help you request the right permissions without having to look up individual scope names.
Expand Down
61 changes: 61 additions & 0 deletions docs/examples/batch-caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,67 @@ func main() {
}
```

## Concurrent Requests with Different Tokens

When endpoints require user-specific tokens (e.g., `Get Channel Followers` requires `moderator:read:followers`), use `WithToken` to override the client token per-request:

```go
package main

import (
"context"
"fmt"
"log"
"sync"

"github.com/Its-donkey/kappopher/helix"
)

func main() {
ctx := context.Background()

authClient := helix.NewAuthClient(helix.AuthConfig{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
})
client := helix.NewClient("your-client-id", authClient)

// Each channel's user token (obtained via Authorization Code flow)
type ChannelToken struct {
BroadcasterID string
Token *helix.Token
}

channels := []ChannelToken{
{BroadcasterID: "111", Token: &helix.Token{AccessToken: "token-a"}},
{BroadcasterID: "222", Token: &helix.Token{AccessToken: "token-b"}},
{BroadcasterID: "333", Token: &helix.Token{AccessToken: "token-c"}},
}

var wg sync.WaitGroup
for _, ch := range channels {
wg.Add(1)
go func(ch ChannelToken) {
defer wg.Done()

// Override token for this request only
reqCtx := helix.WithToken(ctx, ch.Token)
followers, err := client.GetChannelFollowers(reqCtx, &helix.GetChannelFollowersParams{
BroadcasterID: ch.BroadcasterID,
})
if err != nil {
log.Printf("Error: %v", err)
return
}
fmt.Printf("Channel %s: %d followers\n", ch.BroadcasterID, *followers.Total)
}(ch)
}
wg.Wait()
}
```

The client's rate limiter, cache, and middleware are shared across all requests regardless of which token is used. When caching is enabled, requests with different tokens produce different cache keys automatically.

## Complete Example: Efficient Multi-Channel Dashboard

```go
Expand Down
11 changes: 11 additions & 0 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,17 @@ batcher := helix.NewBatcher(client, helix.BatchConfig{
results := batcher.GetUsers(ctx, userIDs)
```

### Can I use different tokens for different requests?

Yes. Use `WithToken` to override the client-level token on a per-request basis:

```go
ctx := helix.WithToken(ctx, &helix.Token{AccessToken: "other-user-token"})
followers, err := client.GetChannelFollowers(ctx, params)
```

This is useful when making concurrent requests that each require a different user token (e.g., fetching followers for multiple channels). See the [Authentication Examples](examples/authentication.md) for a complete example.

---

## Troubleshooting
Expand Down
4 changes: 4 additions & 0 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ err := authClient.RevokeToken(ctx, token.AccessToken)
// Auto-refresh (starts background goroutine)
cancel := authClient.AutoRefresh(ctx)
defer cancel()

// Per-request token override (for concurrent multi-user requests)
ctx := helix.WithToken(ctx, &helix.Token{AccessToken: "other-user-token"})
followers, err := client.GetChannelFollowers(ctx, params)
```

## Creating the Helix Client
Expand Down
2 changes: 2 additions & 0 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ description: Solutions to common issues when using Kappopher.
- Use the correct Client ID that matches the token
- Generate a new token with your Client ID

**Note:** When using `WithToken` for per-request token overrides, all tokens must belong to the same Client ID as the client. Twitch requires the `Client-Id` header and `Authorization` token to match.

---

## EventSub Issues
Expand Down